Adventures in connecting a local network with IPv6
A little historySometime around 1994 I was using Linux at home to login to my workplace network, using a dial-up modem. I then got an ISDN internet connection, with an external modem connected with ethernet. After a period of inactivity, the connection would drop, but by keeping pinging, it would stay up. I could then connect back to my home computer from work.After a couple of years I got a cable modem, again an external device with an ethernet connection. The ISP gave me a quasi-static IP address - it stayed the same for months, unless they upgraded their network or my computer lost power for more than a day. So I could use SSH to connect to my home PC from work, equally as well as connecting to work from home. I'd do a lot of work from home, or occasionally home-related things from work, and had a lot of files and documents on both computers. It didn't really matter which, since I could access either from anywhere there was internet. I gave my home PC a DNS entry and ran a webserver with all my personal photos, as well as email for a while. When my kids needed computers in the 2000's, I got a second ethernet card and a network hub and set up masquerading - what is now called NAT - forwarding packets from the local network to the cable modem. Later I upgraded the hub to a switch, and later when I got a laptop and tablet with built-in WiFi, got an access point. I ran DHCP and DNS for my local network, so everything got a name and a static IP address. I've run the same configuration ever since, with a few hardware and software upgrades. My old desktop is still running CentOS6, relegated to being a file server and router. When I moved from the city to a rural area in 2019, I lost the cable modem. There's no fibre or cable or DSL. None of the ISP options are as reliable, and I could not get a static IPv4 address, even a quasi one, without paying more. The interface equipment does NAT or CGNAT internally so I can't get a public address of any kind. Since I retired shortly before moving, that wasn't so much of a problem, but I had to move some of my notes and photos to my cloud server. However,there's not enough room for everything. When I was stuck in hospital for months, though, it became more annoying. I was able to run a reverse tunnel in SSH, but it would not stay up - the internet connection had odd drop-outs and the tunnel needed to be re-started by someone at home. I had moved to Starlink to try and get a better internet connection, and found that it automagically supported IPv6, giving my computer a /64 quasi-static address. So in theory I could connect directly from the internet again. However, the hospital network only supported IPv4, and for that matter my primary cloud provider did not support IPv6 either. I had an Amazon ECS instance as a secondary DNS server, and switched that for a newer "Lightsail" instance that easily and by default supported IPv6. In fact, Amazon charge more for IPv4 connectivity. So by using SSH to connect to that via IPv4, I could login to my home PC over IPv6. Hooray. But if for some reason I wanted to connect to another device, I'd have to go through the dual-interface PC. I have a couple of IP cameras at home, and I was able to access them by firstly setting up a web proxy in Apache on my home PC, and then running Squid on the Amazon instance. By setting a proxy in Firefox, it would retrieve images via the IPv6 link from the webserver at home. Not exactly straightforward. A real IPv6 networkSome time in 2024 I thought I'd try and get a proper IPv6 connection for my home network. IPv6 has billions of possible addresses, enough for every tree on the planet, let alone every phone or refrigerator, so there's no need for NAT.Per Starlink's documentation, Starlink provides
IPv6 Router Advertisements from Starlink have the "M" flag clear and the "Other" flag set; this means that the prefix and router addresses are in the RA packet but DNS should be obtained via DHCP. How networking works in LinuxNetworking is done in the kernel, with some helper programs. Kernel parameters are viewable in /proc/sys/net, and can be set at boot time in /etc/sysctl.conf.Parameters are documented in the kernel source at Documentation/networking/ip-sysctl.txt Some IPv6 parameters are compiled into the kernel, and may be found in /boot/config-<kernel-version> Route information can be seen at /proc/net/route and /proc/net/ipv6_route; it's normal to interact with that using programs such as "ip" or "route".
In CentOS6, general network parameters are set in /etc/sysconfig/network and interface-specific
ones in /etc/sysconfig/network-scripts/ifcfg-<interface-name> IPv4 NetworkingAddresses and routes in IPv4 are either static, set manually, or dynamic using DHCP. For DHCP, there's an entry "BOOTPROTO=dhcp" in ifcfg-<interface-name>. CentOS6 uses a DHCP client "dhclient", with a config file /etc/dhcp/dhclient.conf. Normally you'd request routers and domain-name-servers.Out-of-the-box, normally this just works. To get NAT for my local network, I have the following in an init script echo 1 > /proc/sys/net/ipv4/ip_forwardand also a MASQUERADE rule in the nat table in iptables. IPv6 networkingAddresses and routes in IPv4 can be static, set dynamically by DHCP6, or set automatically by Router Advertisements in Stateless Address Auto Configuration (SLAAC). That's controlled by kernel parameters such as /proc/sys/net/ipv6/conf/all/accept_ra, and is normally the default. So IPv6 networking for a single device with dual stack "just works" on Starlink, getting DNS via DHCP4. IPv6 is by default the preferred protocol in Linux, so once IPv6 is configured you may start getting HTTP responses on IPv6 from e.g. google.com.Prefix DelegationGetting IPv6 running with global addresses (as opposed to Link-Local) on a local network is more complicated.What is currently working for me is running dhcp6c (from the wide-dhcpv6-20080615 package) to get a delegated prefix from Starlink, and running radvd to advertise routes on the local network. I also need to set /proc/sys/net/ipv6/conf/all/forwarding, This normally means that router advertisements are ignored, but since kernel 2.6 accept_ra can be set to 2, per ip-sysctl.txt, to accept Router Advertisements even if forwarding is enabled.
I have two network cards on my PC, eth0 on the local network and eth8 connected to the Starlink router, which
is set in bypass mode. SLAAC on eth8 gives it a public IP address and a default IPv6 route via Starlink. I have the following in /etc/wide-dhcpv6/dhcp6c.conf
interface eth8 {
send ia-pd 0;
request domain-name-servers;
};
id-assoc pd {
prefix-interface eth0 {
sla-id 0;
sla-len 8;
};
};
That sends a request for a prefix to the Starlink
router via eth8 (also specified in /etc/sysconfig/dhcp6c)
The router responds with
a prefix packet such as
Option: IA Prefix (26)
Length: 25
Preferred lifetime: 150
Valid lifetime: 300
Prefix length: 56
Prefix address: 2605:****:****:****::
DNS recursive name server
Option: DNS recursive name server (23)
Length: 32
DNS servers address: 2001:4860:4860::8888
DNS servers address: 2606:4700:4700::1111
That shows up in stdout for dhcp6c running in foreground mode with debugging enabled, but
is otherwise not obviously saved.
I have /etc/radvd.conf
interface eth0 {
AdvSendAdvert on;
MinRtrAdvInterval 30;
MaxRtrAdvInterval 100;
AdvDefaultLifetime 300;
# Globally routable prefix
# ::/64 - picks one non-link-local prefix assigned to the interface
prefix ::/64 {
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr off;
AdvValidLifetime 300;
AdvPreferredLifetime 150;
};
# Unique Local Address prefix
# also use ip -6 addr add fd24:xxxx:xxxx:0:1::1/64 dev eth0
prefix fd24:xxxx:xxxx::/64 {
AdvOnLink on;
AdvAutonomous on;
AdvValidLifetime 3000;
AdvPreferredLifetime 1500;
};
};
radvd advertises global public IPv6 addresses on the local network based on the address assigned to eth0, and local addresses based on the given prefix. The prefix in this case is generated using a pseudo-random algorithm per RFC 4193.
ip6tablesBy default, forwarding is blocked in ip6tables with Chain FORWARD (policy ACCEPT) target prot opt source destination REJECT all anywhere anywhere reject-with icmp6-adm-prohibitedThis block must be removed, ideally to allow only the configured global addresses access, or more crudely with ACCEPT all anywhere anywhereBy default, the INPUT rule is set to something like Chain INPUT (policy ACCEPT) target prot opt in out source destination ACCEPT all any any anywhere anywhere state RELATED,ESTABLISHED ACCEPT ipv6-icmp any any anywhere anywhere ACCEPT all lo any anywhere anywhere ACCEPT tcp any any anywhere anywhere state NEW tcp dpt:ssh REJECT all any any anywhere anywhere reject-with icmp6-adm-prohibitedThis allows outgoing traffic and replies to same, and incoming SSH traffic. For prefix delegation to work, it must also accept DHCPv6 packets. ACCEPT udp any any anywhere anywhere udp dpt:dhcpv6-client ACCEPT udp eth0 any anywhere anywhere udp dpt:dhcpv6-server My current working configuration, concatenated from various sources, is
/proc/sys/net/ipv6/conf/all/forwarding 1
/proc/sys/net/ipv6/conf/all/autoconf 1
/proc/sys/net/ipv6/conf/all/disable_ipv6 0
/proc/sys/net/ipv6/conf/all/accept_ra 0
/proc/sys/net/ipv6/conf/eth0/forwarding 1
/proc/sys/net/ipv6/conf/eth0/autoconf 1
/proc/sys/net/ipv6/conf/eth0/disable_ipv6 0
/proc/sys/net/ipv6/conf/eth0/accept_ra 0
/proc/sys/net/ipv6/conf/eth8/forwarding 1
/proc/sys/net/ipv6/conf/eth8/autoconf 1
/proc/sys/net/ipv6/conf/eth8/disable_ipv6 0
/proc/sys/net/ipv6/conf/eth8/accept_ra 2
On the router machine:
$ ip -6 addr
eth0 inet6 2605:****:***:****:****:****:****:61**/64 scope global
inet6 fe80::****:****:****:****/64 scope link
inet6 fd24:****:****:0:1::1/64 scope global
eth8 inet6 2605:****:***:****:***:***:****:33**/64 scope global dynamic
inet6 fe80::***:***:****:****/64 scope link
$ ip -6 route
fe80::/64 dev eth0 proto kernel metric 256 mtu 1500
fe80::/64 dev eth8 proto kernel metric 256 mtu 1500
fd24:****:***::/64 dev eth0 proto kernel metric 256 mtu 1500
default via fe80::200:****:****:101 dev eth8 proto kernel metric 1024 expires 193sec mtu 1500 hoplimit 64
/etc/sysconfig/network
NETWORKING=yes
IPV6FORWARDING=yes
/etc/sysconfig/network-scripts/ifcfg-Starlink_eth8
TYPE=Ethernet
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=yes
NAME="Starlink eth8"
ONBOOT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
PEERDNS=yes
PEERROUTES=yes
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
/etc/sysconfig/network-scripts/ifcfg-Local_eth0
TYPE=Ethernet
DOMAIN=local
DEFROUTE=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=no
NAME="Local eth0"
ONBOOT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
BOOTPROTO=none
IPADDR=192.168.*.*
PREFIX=24
DNS1=127.0.0.1
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
( * used for obfuscation )
With this, devices on my local network, such as my laptop and phone, get a global IPv6 address and will use it. I can reach them direct from the internet, from an IPv6-connected device.
wide-dhcpv6 is available from EPEL with source at fedoraproject
They also mention that the ISC dhcp client had a couple of bugs that prevented it working properly for this scenario; those appear to have been closed in 2019. I have not tried building that.
Privacy in IPv6CentOS6 and many other devices build an IPv6 address based on the device MAC address. Data aggregators could track your client device across multiple servers (not that they don't already with cookies). There are options to build an anonymous public, or temporary, address instead, which are used by default in Fedora 44 and in Android. This is defined in RFC8981 (2021, supersedes 4941 from 2007) and RFC7217 (2014).In Linux and Android, the kernel parameter net.ipv6.conf.eth0.use_tempaddr controls privacy extensions. 0 = Disabled 1 = Enabled, prefer public addresses 2 = Enabled, prefer temporary addresses default 0, -1 for point-to-pointIf enabled, the device will build a second, temporary, global IPv6 address in response to a router advertisement and use that for outgoing packets, e.g. inet6 2605:****:***:****:****:***:1238:bdfc/64 scope global temporary dynamicThere is still a more permanent global address that can be used for incoming packets to a server. It is common for interfaces to have multiple IPv6 addresses.
DNSIPv6 uses AAAA records in DNS rather than A records. So I have for instance$ORIGIN local. hp-mfp A 192.168.*.* hp-mfp AAAA fd24:f715:***::****:****:****:7416With that defined, devices will communicate with the local device hp-mfp preferentially over IPv6 rather than IPv4. I also have a public IPv6 address, which might change if Starlink changes my allocated block $ORIGIN my-domain hp-mfp AAAA 2605:****:***:****:****:****:****:7416so I can refer to the device as hp-mfp.my-domain. I also have reverse DNS records such as $ORIGIN 0.0.0.0.*.*.*.0.5.1.7.f.4.2.d.f.ip6.arpa. *.*.*.*.*.*.*.*.*.*.*.*.0.6.e.5 IN PTR hp-mfp.local.One can use the ipcalc program to convert the grouped notation used in AAAA records to the ungrouped form used in PTR, for instance ipv6calc --in ipv6addr --out revnibbles.arpa 2607:f8f0:660::8e5a:6a65to generate 5.6.a.6.a.5.e.8.0.0.0.0.0.0.0.0.0.0.0.0.0.6.6.0.0.f.8.f.7.0.6.2.ip6.arpa.and then use the ORIGIN directive to remove the common prefix (which must match the zone directive in named.conf)
For convenience, I also created DNS entries for the link-local addresses.
|