Configuring IPv6 Routing with DHCPv6-PD, RADVD, and ISC DHCP on Linux Router


2 views

When replacing ISP-provided CPE with a Linux router handling IPv6 via DHCPv6 Prefix Delegation, we need several components working in concert. Here's my working configuration on CentOS 6:

# /etc/dhcp/dhclient6.conf
script "/etc/dhcp/dhclient6-script";
interface "eth0" {
    send dhcp6.reconf-accept;
    request 
        dhcp6.name-servers,
        dhcp6.domain-search,
        dhcp6.sip-servers-addresses,
        dhcp6.vendor-opts;
    send dhcp6.oro 8,14,15,16,24;
}

The magic happens in the custom script that processes PD assignments:

#!/bin/bash
# /etc/dhcp/dhclient6-script

case $reason in
    BOUND6|RENEW6|REBIND6)
        # Process delegated prefix
        if [ -n "$new_ip6_prefix" ]; then
            # Assign first /64 to eth1
            ip -6 addr add ${new_ip6_prefix%::/*}1::1/64 dev eth1
            # Assign second /64 to eth2 
            ip -6 addr add ${new_ip6_prefix%::/*}2::1/64 dev eth2
            
            # Update radvd config
            cat > /etc/radvd.conf <

Dynamic RADVD configuration is handled by the dhclient script above. For a static fallback:

# /etc/radvd.conf
interface eth1 {
    AdvSendAdvert on;
    MinRtrAdvInterval 30;
    MaxRtrAdvInterval 100;
    prefix ::/64 {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
};

For serving DHCPv6 options to LAN clients:

# /etc/dhcp/dhcpd6.conf
option dhcp6.domain-search "example.com";
option dhcp6.name-servers 2001:db8::1;

subnet6 2001:db8:1::/64 {
    range6 2001:db8:1::1000 2001:db8:1::2000;
    option dhcp6.name-servers $new_dhcp6_name_servers;
}

For proper prefix transition during reconfiguration:

# In dhclient6-script
handle_reconfig() {
    # Keep old prefix running during DAD timeout
    ip -6 addr add $old_prefix deprecated
    
    # Start new prefix
    ip -6 addr add $new_prefix
    
    # Update radvd with both prefixes briefly
    sed -i "/prefix.*/a prefix $old_prefix { DeprecatePrefix on; };" /etc/radvd.conf
    service radvd reload
    
    # After 1 hour, clean up old prefix
    sleep 3600
    ip -6 addr del $old_prefix
    sed -i "/$old_prefix/d" /etc/radvd.conf
    service radvd reload
}

Essential troubleshooting commands:

# Check DHCPv6 traffic
tcpdump -i eth0 -vvv port 546 or port 547

# Verify prefix assignment
ip -6 addr show dev eth1

# Test router advertisements
rdisc6 eth1

# Check DHCPv6 server operation
dhcp6 -c /etc/dhcp/dhcpd6.conf -d -f eth1

Essential ip6tables rules for the router:

# Allow DHCPv6
ip6tables -A INPUT -i eth0 -p udp --dport 546 -j ACCEPT
ip6tables -A INPUT -i eth0 -p udp --dport 547 -j ACCEPT

# Allow ICMPv6 (essential for IPv6 operation)
ip6tables -A INPUT -p icmpv6 -j ACCEPT

# NAT64 if needed
ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

When replacing ISP-provided CPE with a Linux router handling dual-stack connectivity, proper IPv6 configuration becomes crucial. The key components we need to address:

Network Topology:
[ISP] --(WAN/eth0)-- [Linux Router] --(LAN/eth1,eth2)-- [Clients]

The existing dhclient6.conf is correctly configured for prefix delegation. Let's enhance it to handle route and prefix assignments:

# /etc/dhcp/dhclient6.conf
interface "eth0" {
    send dhcp6.reconf-accept;
    request dhcp6.vendor-opts, dhcp6.name-servers, 
            dhcp6.domain-search, dhcp6.sip-servers-addresses,
            dhcp6.sntp-servers;
    script "/etc/dhcp/dhclient6-script";
    send ia-pd 1;
}

Create /etc/dhcp/dhclient6-script with executable permissions to handle prefix delegation events:

#!/bin/bash
# /etc/dhcp/dhclient6-script

PREFIX_FILE="/var/lib/dhclient/dhclient6-prefixes"
RA_CONFIG="/etc/radvd.conf"
DHCPD6_CONFIG="/etc/dhcp/dhcpd6.conf"

case $reason in
    BOUND6|RENEW6|REBIND6)
        # Handle default route
        if [ -n "${new_routers}" ]; then
            ip -6 route del default 2>/dev/null
            ip -6 route add default via ${new_routers} dev ${interface}
        fi
        
        # Process delegated prefixes
        if [ -n "${new_ip6_prefix}" ]; then
            echo "${new_ip6_prefix}" > ${PREFIX_FILE}
            
            # Assign /64 subnets to LAN interfaces
            PREFIX64_1=$(echo ${new_ip6_prefix} | sed 's/\/48/\/64/')::1
            PREFIX64_2=$(echo ${new_ip6_prefix} | sed 's/\/48\//1:/')::1
            
            ip -6 addr add ${PREFIX64_1} dev eth1
            ip -6 addr add ${PREFIX64_2} dev eth2
            
            # Update radvd configuration
            generate_radvd_config ${new_ip6_prefix} > ${RA_CONFIG}
            systemctl restart radvd
            
            # Update dhcpd6 configuration
            generate_dhcpd6_config ${new_ip6_prefix} > ${DHCPD6_CONFIG}
            systemctl restart dhcpd6
        fi
        ;;
    EXPIRE6|RELEASE6)
        # Clean up old configuration
        ip -6 route del default 2>/dev/null
        rm -f ${PREFIX_FILE} 
        ;;
esac

Here's the generate_radvd_config function for proper Router Advertisement:

generate_radvd_config() {
    local prefix=$1
    local subnet1=${prefix/\/48/1:\/64}
    local subnet2=${prefix/\/48/2:\/64}
    
    cat << EOF
interface eth1 {
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    prefix ${subnet1} {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
        AdvValidLifetime 86400;
        AdvPreferredLifetime 14400;
    };
};

interface eth2 {
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    prefix ${subnet2} {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
        AdvValidLifetime 86400;
        AdvPreferredLifetime 14400;
    };
};
EOF
}

The DHCPv6 server needs to distribute additional information received from ISP:

generate_dhcpd6_config() {
    local prefix=$1
    local subnet1=${prefix/\/48/1:\/64}
    local subnet2=${prefix/\/48/2:\/64}
    
    cat << EOF
option dhcp6.name-servers ${new_dhcp6_name_servers};
option dhcp6.domain-search "${new_dhcp6_domain_search}";
option dhcp6.sip-servers-addresses ${new_dhcp6_sip_servers_addresses};

subnet6 ${subnet1} {
    range6 ${subnet1/64\//64::1000} ${subnet1/64\//64::2000};
    prefix6 ${subnet1};
}

subnet6 ${subnet2} {
    range6 ${subnet2/64\//64::1000} ${subnet2/64\//64::2000};
    prefix6 ${subnet2};
}
EOF
}

For seamless transition during prefix changes, modify the script to handle both old and new prefixes:

# Add to dhclient6-script
handle_reconfiguration() {
    # First advertise old prefix with short lifetimes
    sed -i 's/AdvValidLifetime [0-9]*/AdvValidLifetime 60/' ${RA_CONFIG}
    sed -i 's/AdvPreferredLifetime [0-9]*/AdvPreferredLifetime 30/' ${RA_CONFIG}
    systemctl reload radvd
    
    # Wait for clients to get deprecated prefix
    sleep 30
    
    # Then update with new configuration
    generate_radvd_config ${new_ip6_prefix} > ${RA_CONFIG}
    generate_dhcpd6_config ${new_ip6_prefix} > ${DHCPD6_CONFIG}
    systemctl restart radvd
    systemctl restart dhcpd6
}

Create a systemd service to ensure proper sequencing of network services:

# /etc/systemd/system/ipv6-pd.service
[Unit]
Description=IPv6 Prefix Delegation Handler
After=network.target
Requires=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/etc/dhcp/dhclient6-script BOUND6
ExecStop=/etc/dhcp/dhclient6-script RELEASE6

[Install]
WantedBy=multi-user.target