User Tools

Site Tools


doc:appunti:net:ipv6_on_ppp

IPv6 over PPP with a Debian GNU/Linux firewall

In this page we will see how to configure a GNU/Linux firewall to handle an IPv6 prefix delegation obtained from the internet provider, distributing it to the local area network. The firewall is configured as follow:

  • The internet connection is a PPPoE through a FTTC modem connected to a DSL provider. The Ethernet card is named net0 and the internet interface is named ppp0.
  • The local network LAN is connected through a second Ethernet card named lan0, but we use it in a logical bridge named br0.
  • The firewall runs the Debian 12 Bookworm operating system.
  • The firewall runs the shorewall and shorewall6 firewall progams and the bind9 Internet Domain Name Server.

The packages that we will install on the GNU/Linux firewall are:

  • wide-dhcpv6-client - This is required to obtain the IPv6 prefix delegation (IPv6 subnet) from the internet service provider.
  • radvd - This is required to do Router Advertisements on the LAN interface. Clients will be able to do Stateless Address Auto-configuration (SLAAC).
  • wide-dhcpv6-server - This is required to give persistent addresses to IPv6 clients independent of the MAC address and to register AAAA and PTR records into the dynamic DNS.

As for June 2024 these are the known limitations of radvd and wide-dhcpv6-server software:

  • The radvd server can do IPv6 stateless configuration of the clients using a persistent address (derived from the Ethernet MAC address) or a random one. It is not possibile to keep the same IPv6 address if you change the Ethernet device.
  • The radvd is not capable to register IPv6 clients to a dynamic DNS server.
  • Android clients connecting to the LAN can obtain IPv6 addresses from the radvd server, but not from the DHCPv6 server.
  • The wide-dhcpv6-server can assign persistent addresses to the IPv6 clients that do not depend on the MAC address. It also can register the clients to a dynamic DNS server.

PPPD options

To allow the ppp daemon to receive the basic IPv6 configuration from the internet service provider, you must add some options into the default configuration file /etc/ppp/peers/provider.

# Enable IPv6 configuration.
+ipv6
defaultroute6
# Interface ID (host part) of the IPv6 address to get.
ipv6 00:00:00:00:00:00:00:01
# Derive the interface ID of the IPv6 address from the Ethernet MAC address.
#ipv6cp-use-persistent

Generally the ISP will delegate a 64 bit prefix to be configured on the ppp0 interface. The default behaviour of the pppd is to generate a random interface ID (a 64 bit address) to form the full 128-bit IPv6 address. So whenever the ppp0 interface goes up, you have a new random IPv6 address automatically associated to it.

If you want to associate a persistent address, you can specify it using the option ipv6 or you may want to derive it from the MAC address of the Ethernet interface using the ipv6cp-use-persistent option.

WARNING: The assignment of an IPv6 address to the ppp0 interface is negotiated via the Router Advertisement protocol. The GNU/Linux kernel has a parameter that control if those messages are accepted or rejected; you can check the current setting using:

cat /proc/sys/net/ipv6/conf/ppp0/accept_ra

The meaning of that value is:

0 Do not accept Router Advertisements.
1 Accept Router Advertisements if forwarding is disabled (default).
2 Accept Router Advertisements even if forwarding is enabled.

Generally the ppp0 interface on a GNU/Linux firewall is the WAN one (connecting to the internet), so forwarding is enabled on it by some way; check the current forwarding status with:

cat /proc/sys/net/ipv6/conf/ppp0/forwarding
1

This is the reason why the IPv6 global (public) address is propbably not eccepted on that interface. To change (temporary) the accept_ra option for the ppp0 interface you can use the command:

sysctl -w net.ipv6.conf.ppp0.accept_ra=2

If you want that option to be enabled on each reboot, you can create a configuration file e.g. /etc/sysctl.d/99-local.conf with:

net.ipv6.conf.ppp0.accept_ra = 2

But even the last configuration does not work in our case, because the setting can be changed only when the interface ppp0 does exist. The ppp0 interface does not exist at bootstrap and it is volatile: it can be destroyed at any time if the connection with the ISP has a problem. And when the interface is created again it will have lost its configuration.

We need to hook a script to the ppp0 up event and configure the options there. Because we are using the Shorewall firewall it is possibile to set the required actions into the Shorewall configuration and simply restart the Shorewall service on the ppp0 interface up event. See below an example of a /etc/ppp/ipv6-up.d/ipv6-addresses-up script.

Shorewall firewall

If you want the IPv6 address automatically configured on the ppp0 interface and you need to receive a Prefix Delegation, you must pay attention to allow the protocols traffic to flow (Router Advertisements and DHCPv6).

If you use the Shorewall6 firewall everything can be configured with some options into the /etc/shorewall6/interfaces configuration file:

#ZONE  INTERFACE  OPTIONS
net    NET_IF     dhcp,tcpflags,forward=1,accept_ra=2,sourceroute=0,physical=ppp0
  • The dhcp option allows the traffic on ports 546/UDP and 547/UDP.
  • The forward=1 option enables the forwarding of traffic through the ppp0 interface, from LAN to internet and viceversa.
  • The accept_ra=2 option allows the Router Advertisements protocol to assing an IPv6 address to ppp0, even if routing is enabled.

However there is a big warning: if you start the shorewall6 service when the ppp0 interface is absent, all the above settings are ignored. You need to restart the service upon the IPv6 stack is ready on the ppp0 interface. Fortunately the restart is fast enough that the auto-configuration will succeed (indeed all the protocols will try more than one time, if the first attempt fails).

Here it is an example script /etc/ppp/ipv6-up.d/ipv6-addresses-up, which is executed whenever the link is available for sending and receiving IPv6 packets:

#!/bin/sh
# Accept Router Advertisements.
# Not actually required because "shorewall6 restart" will do that.
#sysctl -w "net.ipv6.conf.${PPP_IFACE}.accept_ra=2"
# Restart the Shorewall6 service to set some options on the ppp0 interface.
# Options that must be set every time that the interface is created are:
# forward=1,accept_ra=2,sourceroute=0
/usr/sbin/shorewall6 restart
# Restart the DHCPv6 client to get the Prefix Delegation.
/usr/bin/systemctl restart wide-dhcpv6-client.service

DHCPv6 client: the wide-dhcpv6-client.service

If your ISP assigns to you a Prefix Delegation (an IPv6 subnet), you may need to run a DHCPv6 client to obtain it. Generally it is not sufficient to configure manually the subnet because if the internet provider does not see the DHCPv6 client request, it does not propagate the routing information globally.

In Debian 12 you need to install the wide-dhcpv6-client package and configure it to obtain the prefix. The package must be configured in two files: the /etc/default/wide-dhcpv6-client and the /etc/wide-dhcpv6/dhcp6c.conf.

In the first file /etc/default/wide-dhcpv6-client declare what is the interface that gets DHCPv6 messages and eventually enable some debug logging:

INTERFACES="ppp0"
VERBOSE=2

Per default the main configuration file /etc/wide-dhcpv6/dhcp6c.conf contains only a profile default statement, which is used if you don't declare a more specific interface ppp0 statement.

# The "default" profile statement is enabled when a specific interface
# statement is not configured. Per Debian default the client is called
# with the option "dhcp6c -Pdefault".
profile default {
    # Exchange informational parameters only (e.g. DNS, no IPv6 addresses).
    information-only;
    # What to include in DHCPv6 option-request.
    request domain-name-servers;
    request domain-name;
    # Script executed when the daemon receives a reply message.
    script "/etc/wide-dhcpv6/dhcp6c-script";
};

This configuration above has the effect that no prefixes delegations are negotiated (information-only), but information about the IPv6 DNS servers (and eventually domain names) are added to /etc/resolv.con by the provided script.

We add the interface ppp0 statement to the file:

interface ppp0 {
    # Send and Identity Association for Non-temporary Addresses option, ID #0.
    #send ia-na 0;
    # Send an option for "Identity Association for Prefix Delegation" with ID=0.
    send ia-pd 0;
};

# Non-temporary Addresses ID=0.
#id-assoc na 0 {
#};

# Prefix Delegation ID=0.
id-assoc pd 0 {
    prefix-interface br0 {
        # A subset (subnet) of the delegated prefix is assigned to a LAN interface.
        # The subnet is identified by the SLA (Site-level aggregator) ID, e.g. the
        # bits that are added to the delegated prefix to form the full 64 bit prefix.
        #
        # Example:
        #   2a02:2427:513:1c00::/56   Delegated Prefix
        #   2a02:2427:513:1c03::/64   Delegated Prefix with 8 bits SLA ID=3
        #   2a02:2427:513:1c03::1     Interface Address ID=1
        #
        # sla-len       Length of the SLA address part, in bits.
        # sla-id        Site-level aggregator ID (subnet address).
        # ifid          The 64 bit address assigned to the interface.
        #
        sla-len 8;
        sla-id 3;
        ifid 1;
    };
};

As you can se we don't configure the Non-temporary Addresses (ia-na) option, because the IPv6 address is already assigned to the ppp0 interface through the router advertisements protocol.

Instead we configure the Prefix Delegation (ia-pd) option to obtain the IPv6 subnet. That PD has ID=0 and it is configured into the id-assoc pd 0 statement. The internet provider delegates to us a 56 bit prefix, we can subnet this by adding 8 bits of Site-level aggregator ID. Each one of those SLAs can have IPv6 hosts identified by further 64 bits of interface address.

In our example only one SLA is configured (with ID=3), and it is assigned to the internal interface br0. The interface gets its IPv6 address composed by the delegated prefix + sla ID + interface ID.

It is possible to test the configuration file executing the daemon in foreground:

systemctl stop wide-dhcpv6-client.service
dhcp6c -c /etc/wide-dhcpv6/dhcp6c.conf -d -D -f ppp0

The client daemon will communicate with the ISP server on ports 546/UDP and 547/UDP, you can see the packets using tcpdump:

tcpdump -i ppp0 -n '(udp port 546 or 547) or icmp6'
05:16:08.101646 IP6 fe80::1.546 > ff02::1:2.547: dhcp6 solicit
05:16:08.116599 IP6 fe80::d678:9bff:feee:5640.547 > fe80::1.546: dhcp6 advertise
05:16:09.103236 IP6 fe80::1.546 > ff02::1:2.547: dhcp6 request
05:16:09.114693 IP6 fe80::d678:9bff:feee:5640.547 > fe80::1.546: dhcp6 reply

If you have a firewall, you must allow UDP traffic on ports 546 and 547 (see above if you are using Shorewall6).

Restart the service whenever the ppp0 interface goes up or down

The service cannot be started before the interface ppp0 is up. The script /etc/ppp/ipv6-up.d/ipv6-addresses-up can be used to restart the DHCPv6 client service:

#!/bin/sh
# Restart the Shorewall6 service to set some options on the ppp0 interface.
# Options that must be set every time that the interface is created are:
# forward=1,accept_ra=2,sourceroute=0
/usr/sbin/shorewall6 restart
# Restart the DHCPv6 client to get the Prefix Delegation.
/usr/bin/systemctl restart wide-dhcpv6-client.service

When the ppp0 interface is turned off, the DHCPv6 client service is no longer needed and can be stopped. Here it is the script /etc/ppp/ipv6-down.d/ipv6-addresses-down:

#!/bin/sh
# Stop the DHCPv6 client to remove the Prefix Delegation.
/usr/bin/systemctl stop wide-dhcpv6-client.service

Checking current configuration

Checking IPv6 on the GNU/Linux firewall

Check if the ppp0 interface got the expected IPv6 address:

ifconfig ppp0
ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1488
        inet 145.56.63.214  netmask 255.255.255.255  destination 146.31.204.24
        inet6 fe80::1  prefixlen 128  scopeid 0x20<link>
        inet6 2a02:2425:134:1b0::1  prefixlen 64  scopeid 0x0<global>
        ppp  txqueuelen 3  (Point-to-Point Protocol)
        RX packets 147868  bytes 35436781 (33.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 142797  bytes 77749243 (74.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

The public IPv6 address is the one with the global scopeid.

To check if an IPv6 default route exists:

ip -6 route show
...
default via fe80::d678:9bff:feee:5640 dev ppp0 proto ra metric 1024
        expires 1766sec hoplimit 64 pref medium

Check if the br0 interface (the LAN) got the IPv6 prefix delegation:

ifconfig br0
br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.3.1  netmask 255.255.255.0  broadcast 192.168.3.255
        inet6 fe80::d003:a6ff:fef2:6fe8  prefixlen 64  scopeid 0x20<link>
        inet6 2a02:2427:513:1c03::1  prefixlen 64  scopeid 0x0<global>
        ether d2:03:a6:f2:6f:e8  txqueuelen 1000  (Ethernet)
        RX packets 119786  bytes 17445585 (16.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 148706  bytes 108552323 (103.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Configuring IPv6 manually on the hosts of the LAN

If we have an host connected to the LAN (interface br0 of the firewall), we can manually configure the IPv6 protocol with something like this:

ip -6 addr add '2a02:2420:503:1c03::2/64' dev eth0
ip -6 route add default via '2a02:2427:513:1c03::1'

Generally this is not required nor advisable: any modern operating system should automatically negotiate IPv6 configuration if it is available on the LAN.

Router Advertisement Daemon for IPv6: the radvd.service

Something similar to the DHCP service (which distributes IPv4 addresses to the LAN) for IPv6 addresses is the Router Advertisement Daemon. In Debian you can install the radvd package. An useful diagnostic tool is provided by the radvdump package.

This is the simpler approach that achieves the Stateless Address Auto-configuration (SLAAC); i.e. nobody keeps track of what address is assigned to whom. Clients are responsilble of asking for an unique IPv6 address and perform a duplicate address detection.

Sometimes a stateful configuration is required instead. For example when you want to assign a static IPv6 address independent of the MAC address, or when you need to register an AAAA name into a dynamic DNS server. In this case you need a DHCPv6 server, e.g. the one provided by the wide-dhcpv6-server Debian package. :!: Beware that Android clients can do SLAAC configuration with radvd, but they cannot get IPv6 from the dhcpv6 server.

To configure the radvd service crate a file named /etc/radvd.conf with the following:

interface br0 {
    # Enable advertisements and respond to solicitations.
    AdvSendAdvert on;
    # Minimum time in seconds between sending unsolicited advertisements.
    MinRtrAdvInterval 10;
    # Maximum time in seconds between unsolicited advertisements.
    MaxRtrAdvInterval 60;
    prefix 2a02:2420:503:1c03::/64 {
        # The address of interface is sent instead of network prefix,
        # as is required by Mobile IPv6.
        AdvRouterAddr on;
        # Disable autonomous configuration if you want to disable SLAAC
        # and use DHCPv6 only. You must install the wide-dhcpv6-server.
        #AdvAutonomous off;
    };
};

Once you start the service with systemctl start radvd, you can view the advertising messages on the network just running the tool radvdump; the program will reveal both the avertising from your Internet Provider on the ppp0 WAN interface and the avertising of the Linux firewall itself on the br0 LAN interface.

A modern host in the LAN (e.g. a GNU/Linux box) will pick-up automatically a global IPv6 address on the LAN interface. You can check the interface configuration:

ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.3.2  netmask 255.255.255.0  broadcast 192.168.3.255
        inet6 2a02:2427:513:1c03:3a2c:4aff:fe0e:feb2  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::3a2c:4aff:fe0e:feb2  prefixlen 64  scopeid 0x20<link>
        ...

The IPv6 address assigned to the interface have the delegated prefix and the interface ID is derived from the MAC address of the Ethernet address, so it will be unique and persistent.

The default IPv6 route is:

ip -6 route show
2a02:2427:513:1c03::/64 dev eth0 proto kernel
    metric 256 expires 86394sec pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::d003:a6ff:fef2:6fe8 dev eth0 proto ra
    metric 1024 expires 24sec hoplimit 64 pref medium

As you can see, the IPv6 default route is addressed to the GNU/Linux firewall through the link address, not the global address. You can verify that the routing is working by just pinging a public IPv6 address.

Advertising the DNS servers

It is advisable to include DNS server information into Router Advertising messages, so that clients can access network services that require name resolution. In general hosts on the LAN will have DNS servers already configured on IPv4, but it is preferable to let IPv6-only hosts to be fully configured.

To get this, just add a RDNSS statement into the interface statement of /etc/radvd.conf. In our case a recursive DNS service (Bind9) is running on the firewall itself:

interface br0 {
    ...
    RDNSS 2a02:2427:513:1c00::1 {
        AdvRDNSSLifetime 3600;
    };
};

If you want clients to be able to resolve not fully-qualified domain names by trying to add some suffixes, you can configure a DNS search list adding a DNSSL statement:

interface br0 {
    ...
    DNSSL lan rigacci.org lan.rigacci.org {
    };
};

If the hosts in the LAN are configured to obtain IPv6 configuration automatically, the new DNS server(s) and search list will be automatically added into their /etc/resolv.conf files.

Stateful configuration with DHCPv6 server

On the GNU/Linux firewall we installed the wide-dhcpv6-server Debian package, together with the radvd package seen above. This is beacuse we want to perform dynamic DNS registration of IPv6 clients, which is not provided by radvd.

Once we installed the wide-dhcpv6-server Debian package, we need to configure the /etc/wide-dhcpv6/dhcp6s.con file:

# DNS server IPv6 address.
option domain-name-servers 2a02:2420:503:1c03::1;

# Domain names of the DNS search path.
option domain-name "lan";
option domain-name "rigacci.org";
option domain-name "lan.rigacci.org";

# NOTICE: You have to send router advertisements on this
# interface (i.e. run radvd on it) otherwise a client cannot
# know the prefix-length and the default router.
# If you want to prevent stateless address configuration via RA,
# please set the AdvAutonomous to off in your RA configuration.
interface br0 {
    address-pool pool1 3600;
};

pool pool1 {
    range 2a02:2420:503:1c03::1000 to 2a02:2420:503:1c03::2000 ;
};

host delfi {
        duid 00:01:00:01:0f:5b:29:7f:00:04:5a:35:1e:a1;
        address 2a02:2420:503:1c03::2 infinity;
};

Verify also that into /etc/default/wide-dhcpv6-server the INTERFACES parameter includes your LAN network interface, which is named br0 in our case. To reload the configuration, execute systemctl restart wide-dhcpv6-server.service.

On a GNU/Linux client you can try an una-tantum DHCPv6 configuration using the following command (beware that the dhclient program from the isc-dhcp-client Debian package, will continue to run in background once obtained the address):

dhclient -6 eth0

If you sniff the traffic on the DHCPv6 server you will see:

tcpdump -i br0 -n 'icmp6 or (port 546 or port 547)'
IP6 fe80::3a2c:4aff:fe0e:feb2.546 > ff02::1:2.547: dhcp6 solicit
IP6 fe80::d003:a6ff:fef2:6fe8.50536 > fe80::3a2c:4aff:fe0e:feb2.546: dhcp6 advertise
IP6 fe80::3a2c:4aff:fe0e:feb2.546 > ff02::1:2.547: dhcp6 request
IP6 fe80::d003:a6ff:fef2:6fe8.50536 > fe80::3a2c:4aff:fe0e:feb2.546: dhcp6 reply
IP6 :: > ff02::1:ff00:1000: ICMP6, neighbor solicitation,
                            who has 2a02:2427:513:1c03::1000, length 32
...
IP6 fe80::3a2c:4aff:fe0e:feb2.546 > ff02::1:2.547: dhcp6 confirm
IP6 fe80::d003:a6ff:fef2:6fe8.50536 > fe80::3a2c:4aff:fe0e:feb2.546: dhcp6 reply
IP6 :: > ff02::1:ff00:1000: ICMP6, neighbor solicitation,
                            who has 2a02:2427:513:1c03::1000, length 32

As you can see the client sends several type of dhcp6 requests: solicit, request and confirm, the requests are sent to the all-nodes multicast address. The DHCPv6 server must accept packets destined to the 547/UDP port (dhcpv6-server) from the LAN.

If you have not disabled the SLAAC configuration in radvd, the client will obtain two IPv6 global addresses:

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.3.2  netmask 255.255.255.0  broadcast 192.168.3.255
        inet6 2a02:2427:513:1c03::1000  prefixlen 128  scopeid 0x0<global>
        inet6 2a02:2427:513:1c03:3a2c:4aff:fe0e:feb2  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::3a2c:4aff:fe0e:feb2  prefixlen 64  scopeid 0x20<link>

FIXME Give persistent addresses to clients using DUID.

FIXME Configure dynamic DNS to add AAAA and PTR records.

Firewall rules

What ports must be open to allow Router Advertisement messages and DHCPv6 traffic from the firewall to the LAN clients?

  • SLAAC - Stateless configuration uses ICMPv6 traffic:
    • A router sends Router Advertisement messages: they have a value of 134 in the Type field of the ICMPv6 packet header.
    • A client sends a Router Solicitation message to the all-routers multicast address; they have a value of 133 in the Type field of the ICMPv6 packet header.
  • DHCPv6 - Stateful configuration uses IPv6 UDP traffic:
    • The dhcpv6-client uses the port 546/UDP.
    • The dhcpv6-server uses the port 547/UDP.

FIXME Give Shorewall and Shorewall6 examples.

Configuring hosts in the LAN

  • FIXME: What Debian packages are required to get IPv6 automatically configured?
  • FIXME: What to do if we don't want IPv6 automatically configured with SLAAC?
  • FIXME: What to do if want IPv6 configured with DHCPv6?
  • FIXME: What to do to protect LAN hosts from the internet?

IPv6 Prefixes

FE80::/64 Link-local prefix.
FF02::1 All-nodes multicast address.
FF02::2 All-routers multicast address.
FF02::1:FFxx:xxxx Duplicate Address Detection multicast group.

Web References

doc/appunti/net/ipv6_on_ppp.txt · Last modified: 2024/06/05 16:22 by niccolo