Linux Router with VPN on a Raspberry Pi (IPv6)

From Alpine Linux

Work in Progress: Following does not work and won't work!.

I have split this off the main article Linux Router with VPN on a Raspberry Pi as that works. IPv6 implementation requires a few changes to the initial article to work.

Introduction

IPv6 introduces a number of new complexities into our network. If you've completed previous IPv4 only guide Linux Router with VPN on a Raspberry Pi then read on.

Your VPN provider may only offers you a single stack connection (no IPv6). You won't be able to implement IPv6 addressing on VLAN 3 to carry your IPv6 traffic out of the VPN. If your ISP gives you IPv6 addressing you may still implement addressing on VLAN2 to carry traffic directly to your ISP. In this example I do both.

If you don't know much about IPv6 then these pages might be of interest to get you up to speed.


Network Diagram IPv4 and IPv6
Network Diagram IPv4 and IPv6

Enabling IPv6 support

Assuming you're using the Alpine Linux kernel, IPv6 support is available separately as a module.

modprobe ipv6

To add the module to our startup configuration.

echo "ipv6" >> /etc/modules

/etc/sysctl.d/local.conf

Modify the sysctl section to include IPv6 support:

# Controls IP packet forwarding
net.ipv4.ip_forward = 1

# Needed to use fwmark
net.ipv4.conf.all.rp_filter = 2

# http://vk5tu.livejournal.com/37206.html
# What's this special value "2"? Originally the value was "1", but this 
# disabled autoconfiguration on all interfaces. That is, you couldn't appear 
# to be a router on some interfaces and appear to be a host on other 
# interfaces. But that's exactly the mental model of a ADSL router. 

# Controls IP packet forwarding
net.ipv6.conf.all.forwarding = 2
net.ipv6.conf.default.forwarding = 2

# Accept Router Advertisments
net.ipv6.conf.all.accept_ra = 2
net.ipv6.conf.default.accept_ra = 2

# We are a router so disable temporary addresses
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0

/etc/network/interfaces

Add an IPv6 interface for each VLAN. Note we don't need to add one for VLAN2 because dhcpcd will take care of that for us using our ISPs router advertisements. Also note the . (dot notation) represents a VLAN interface where as : (colon notation) used in the previous article represented an IP address aliased on an interface.

The reason we need VLANs here is because each VLAN has it's own broadcast and we don't want our router advertisements to be putting routes and addresses on all the interfaces. It also helps us with a more secure design, but requires a managed switch.

# VLAN 2 - DESTINED FOR ISP
auto eth0.2
iface eth0.2 inet static
    address 192.168.2.1
    netmask 255.255.255.0
    broadcast 192.168.2.255
    post-up /etc/network/fwmark_rules

# VLAN 3 - DESTINED FOR VPN
auto eth0.3
iface eth0.3 inet static
    address 192.168.3.1
    netmask 255.255.255.0
    broadcast 192.168.3.255

iface eth0.3 inet6 static
    address fde4:8dba:82e1:fff3::1
    netmask 64
    autoconf 0
    accept_ra 0
    privext 0

# VLAN 4 - LAN ONLY
auto eth0.4
iface eth0.4 inet static
    address 192.168.4.1
    netmask 255.255.255.0
    broadcast 192.168.4.255
    post-up /etc/network/route_LAN

iface eth0.4 inet6 static
    address fde4:8dba:82e1:fff4::1
    netmask 64
    autoconf 0
    accept_ra 0
    privext 0

/etc/ppp/peers/yourISP

Add this to your ppp configuration. This tells PPP to get an ipv6 address. Note the comma is needed.

# Enable IPV6
+ipv6 ipv6cp-use-ipaddr
ipv6 ,

Check system log

Restart ppp.

poff yourISP

pon yourISP

In /var/log/messages you should see something like

pppd[]: Plugin rp-pppoe.so loaded.
pppd[]: RP-PPPoE plugin version 3.8p compiled against pppd 2.4.7
pppd[]: pppd 2.4.7 started by root, uid 0
pppd[]: PPP session is 49969
pppd[]: Connected to 00:53:00:ff:ff:f0 via interface eth1
pppd[]: Using interface ppp0
pppd[]: Connect: ppp0 <--> eth1
pppd[]: CHAP authentication succeeded
pppd[]: CHAP authentication succeeded
pppd[]: peer from calling number 00:53:00:FF:FF:F0 authorized
pppd[]: local  LL address fe80::0db8:ffff:ffff:fff1
pppd[]: remote LL address fe80::0db8:ffff:ffff:fff0
pppd[]: local  IP address 192.0.2.1
pppd[]: remote IP address 192.0.2.0
pppd[]: primary   DNS address 192.0.2.10
pppd[]: secondary DNS address 192.0.2.20

You should be able to now ping things such as

ping6 ipv6.google.com

from your router.

Prefix Delegation

The next step will be to configure DHCPv6 Prefix Delegation with your ISP. Install dhcpcd. While many guides do use the wide-dhcpv6-client it should be noted this is unmaintained and not included in Alpine Linux.

Don't use the ISC's dhclient either as this does not support Prefix Delegations on PPP links without a patch.

apk add dhcpcd

You can check out the manual for dhcpcd.conf. Installing dhcpcd-doc will allow you to read the man file. Eg:

apk add dhcpcd-doc

/etc/dhcpcd.conf

apk add dhcpcd

# Enable extra debugging
# debug

# Allow users of this group to interact with dhcpcd via the control
# socket.
#controlgroup wheel

# Inform the DHCP server of our hostname for DDNS.
hostname gateway

# Use the hardware address of the interface for the Client ID.
#clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as 
# per RFC4361. Some non-RFC compliant DHCP servers do not reply with
# this set. In this case, comment out duid and enable clientid above.
duid

# Persist interface configuration when dhcpcd exits.
persistent

# Rapid commit support.
# Safe to enable by default because it requires the equivalent option
# set on the server to actually work.
option rapid_commit

# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes

# Most distributions have NTP support.
option ntp_servers
# Respect the network MTU.
# Some interface drivers reset when changing the MTU so disabled by
# default.
#option interface_mtu

# A ServerID is required by RFC2131.
require dhcp_server_identifier

# Generate Stable Private IPv6 Addresses instead of hardware based 
# ones
slaac private

# A hook script is provided to lookup the hostname if not set by the
# DHCP server, but it should not be run by default.
nohook lookup-hostname

# IPv6 Only
ipv6only

# Disable solicitations on all interfaces
noipv6rs

# Wait for IP before forking to background
waitip 6

# Don't install any default routes. 
# PPP has already set a default route
nogateway

# Don't touch DNS
nohook resolv.conf

# Use the interface connected to WAN
interface ppp0
    ipv6rs # enable routing solicitation get the default IPv6 route
    iaid 1
    ia_pd 1/::/64 eth0/1/64 # Assign a prefix delegated route to our LAN

Add dhcpcd to the default run level:

rc-update add dhcpcd default

Configure ip6tables with a basic ruleset

A basic rule set for ip6tables. It is commented so feel free to read it.

You'll need to modify your prefix in one of the rules.

#########################################################################
# Basic iptables IPv6 routing rule set
#
# 2001:0db8:1234:0001::/64 hosts   routed directly to ppp0
#
#########################################################################

*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT

#
# Filter Table
# This is where we decide to ACCEPT, DROP or REJECT things
#
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]

# Create rule chain per input interface for forwarding packets
:FWD_ETH0 - [0:0]
:FWD_ETH1 - [0:0]
:FWD_PPP0 - [0:0]
:FWD_TUN0 - [0:0]

# Create ICMPFLOOD chain
:ICMPFLOOD - [0:0]

# Create rule chain per input interface for input packets (for host itself)
:IN_ETH0 - [0:0]
:IN_ETH1 - [0:0]
:IN_PPP0 - [0:0]

# Create a drop chain
:LOG_DROP - [0:0]

# Accept all from localhost
-A INPUT -i lo -j ACCEPT

# Create rule chain per input interface for input packets (for host itself)
-A INPUT -i eth0 -j IN_ETH0
-A INPUT -i eth1 -j IN_ETH1
-A INPUT -i ppp0 -j IN_PPP0

# Block remote packets claiming to be from a loopback address
-A INPUT -s ::1/128 ! -i lo -j DROP

# Permit needed ICMP packet types for IPv6 per RFC 4890
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 1 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 2 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 3 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 4 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 133 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 135 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 136 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 137 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 141 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 142 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 130 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 131 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 132 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 143 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 148 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 149 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 151 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 152 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 153 -j ACCEPT

# Permit ICMP echo requests (ping) and use ICMPFLOOD chain for preventing ping flooding.
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 128 -j ICMPFLOOD

# Track forwarded packets
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Pass forwarded packet to corresponding rule chain
-A FORWARD -i eth0 -j FWD_ETH0
-A FORWARD -i eth1 -j FWD_ETH1
-A FORWARD -i ppp0 -j FWD_PPP0

# Permit needed ICMP packet types for IPv6 per RFC 4890
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 1 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 2 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 3 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 4 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 133 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 135 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 136 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 137 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 141 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 142 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 130 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 131 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 132 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 143 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 148 -j ACCEPT
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 149 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 151 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 152 -j ACCEPT
-A FORWARD -s fe80::/10 -p ipv6-icmp -m icmp6 --icmpv6-type 153 -j ACCEPT

# Permit ICMP echo requests (ping) and use ICMPFLOOD chain for preventing ping flooding.
-A FORWARD -p ipv6-icmp -m icmp6 --icmpv6-type 128 -j ICMPFLOOD

# Forward LAN subnet
-A FWD_ETH0 -s 2001:0db8:1234:0001::/64 -j ACCEPT

# Chain for preventing ping flooding - up to 6 pings per second from a single 
# source, again with log limiting. 
-A ICMPFLOOD -m hashlimit --hashlimit-name ICMP --hashlimit-above 6/second --hashlimit-mode srcip -j DROP
-A ICMPFLOOD -j ACCEPT

# DHCPv6 to Router
-A IN_ETH0 -p udp -m udp --dport 547 -m conntrack --ctstate NEW -j ACCEPT

# LAN traffic out
-A IN_ETH0 -s 2001:0db8:1234:0001::/64 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# Accept tracked connections from outside
-A IN_PPP0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Drop and log everything else
-A IN_PPP0 -j LOG --log-prefix "DROP:INPUT (ipv6) " --log-level 6
-A IN_PPP0 -j LOG_DROP
COMMIT

Add ip6tables to the default run level:

rc-update add ip6tables default

Router Advertisements

apk add radvd

Once radvd is installed, you may configure it:

/etc/radvd.conf

Note this will cause an IPv6 address to be routed to your systems. Those systems will now leak via IPv6 to the Internet if you're on a subnet like 192.168.2.0/24 using an aliased connection.

To mitigate this we would use VLANs, which behave as separate network interfaces. For that you need to replaced the unmanaged switch with a managed one, and each interface, eg eth0:2 with eth0.2.

interface eth0 {

  # We are sending advertisements (route)
  AdvSendAdvert on;

  # Suggested Maximum Transmission setting for using the
  # Hurricane Electric Tunnel Broker.
  # AdvLinkMTU 1480;

  # We have native Dual Stack IPv6 so we can use the regular MTU
  # http://blogs.cisco.com/enterprise/ipv6-mtu-gotchas-and-other-icmp-issues
  AdvLinkMTU 1500;

  prefix 2001:0db8:1234:b001::/64 {
    AdvOnLink on;
    AdvAutonomous on; # SLAAC based on EUI
    AdvRouterAddr on;
  };

  RDNSS 2001:0db8:1234:0001::1 {
  };
  # DNSSL example.id.au {
  # };
};

Add radvd to the default run level:

rc-update add radvd default

Enable Privacy extensions in /etc/sysctl.conf

When a client acquires an address through SLAAC its IPv6 address is derived from the advertised prefix and the MAC address of the network interface of the client. This may raise security concerns as the MAC address of the computer can be easily derived by the IPv6 address. In order to tackle this problem the IPv6 Privacy Extensions standard (RFC 4941) has been developed. With privacy extensions the kernel generates a temporary address that is mangled from the original autoconfigured address. Private addresses are preferred when connecting to a remote server so the original address is hidden. To enable Privacy Extensions reproduce the following steps:

# Enable IPv6 Privacy Extensions
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
net.ipv6.conf.nic0.use_tempaddr = 2
...
net.ipv6.conf.nicN.use_tempaddr = 2

Using DHCPv6

You may decide you want more control over your network address assignment. For this you'll need to use DHCPv6. DHCPv4 and DHCPv6 need to run on separate instances of DHCPD.

Make a symlink for the init script:

ln -s /etc/init.d/dhcpd /etc/init.d/dhcpdv6

Include it in the router provision file:

lbu include /etc/init.d/dhcpdv6

Copy the DHCP Daemon configuration file:

cp /etc/conf.d/dhcpd /etc/conf.d/dhcpdv6

Enable it to run on IPv6. DHCPD can only run on one IP protocol at a time. By default it defaults to IPv4.

sed -i 's/# DHCPD_OPTS=""/DHCPD_OPTS="-6"/g' /etc/conf.d/dhcpdv6

Copy the DHCP configuration file:

cp /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpdv6.conf

Change the owner of the configurations to the dhcp user and group

chown -R dhcp:dhcp /etc/dhcp

/etc/dhcp/dhcpdv6.conf

You will want to edit your MAC address in the host declarations. The client-id or DUID can be found in /etc/dhcpcd.duid when you've installed dhcpcd on your client.

You can also see it in /var/log/messages on your router when a client tries to authenticate on your network eg:

dhcpd: Advertise NA: address 2001:0db8:1234:0001::240 to client with duid <DEVICE DUID> iaid = <DEVICE IAID> valid for 43200 seconds

Currently Android does not have DHCPv6 support and Google seem unwilling to add it.

If you're using a version of dhcpcd below 6.9.3 you may need to set "ipv6ra_accept_nopublic" in your /etc/dhcpcd.conf.

authoritative;
ddns-update-style interim;

shared-network home {
  subnet6 2001:0db8:1234:0001::/64 {
    range6 2001:0db8:1234:0001::10 2001:0db8:1234:0001::240;
    range6 2001:0db8:1234:0001:: temporary;
    option dhcp6.name-servers 2001:0db8:1234:0001::1;
    option dhcp6.sntp-servers 2001:0db8:1234:0001::1;
    allow unknown-clients;
  }

  subnet6 fde4:8dba:82e1:ffff::/64 {
    range6 fde4:8dba:82e1:ffff::10 fde4:8dba:82e1:ffff::240;
    range6 fde4:8dba:82e1:ffff:: temporary;
    option dhcp6.name-servers 2001:0db8:1234:0001::1;
    option dhcp6.sntp-servers 2001:0db8:1234:0001::1;
    ignore unknown-clients;
  }
}

host Gaming_Computer {
  hardware ethernet 00:53:00:FF:FF:11;;
  host-identifier option dhcp6.client-id <YOUR_DUID>;
  fixed-address6 2001:0db8:1234:0001::20;
  fixed-prefix6 2001:0db8:1234:0001::/64;
  option dhcp6.name-servers 2001:0db8:1234:0001::1;
  option dhcp6.sntp-servers 2001:0db8:1234:0001::1;
}

host Linux Workstation {
  hardware ethernet 00:53:00:FF:FF:22;;
  host-identifier option dhcp6.client-id <YOUR_DUID>;
  fixed-address6 fde4:8dba:82e1:ffff::21;
  fixed-prefix6 2001:0db8:1234:0001::/64;
  option dhcp6.name-servers 2001:0db8:1234:0001::1;
  option dhcp6.sntp-servers 2001:0db8:1234:0001::1;
}

/etc/radvd.conf

Finally you'll want to change add "AdvManagedFlag", and "AdvOtherConfigFlag". You will also want to toggle "AdvAutonomous" to off if you do not want IPs generated by SLAAC based on EUI.

interface eth0 {

  # We are sending advertisements (route)
  AdvSendAdvert on;

  # When set, host use the administered (stateful) protocol
  # for address autoconfiguration. The use of this flag is
  # described in RFC 4862
  AdvManagedFlag on;

  # When set, host use the administered (stateful) protocol
  # for address autoconfiguration. For other (non-address)
  # information.
  # The use of this flag is described in RFC 4862
  AdvOtherConfigFlag on;

  # Suggested Maximum Transmission setting for using the
  # Hurricane Electric Tunnel Broker.
  # AdvLinkMTU 1480;

  # We have native Dual Stack IPv6 so we can use the regular MTU
  # http://blogs.cisco.com/enterprise/ipv6-mtu-gotchas-and-other-icmp-issues
  AdvLinkMTU 1500;

  prefix 2001:0db8:1234:0001::/64 {
    AdvOnLink on;
    AdvAutonomous off;
    AdvRouterAddr on;
  };

  # RDNSS 2001:0db8:1234:0001::1 {
  # };
  # DNSSL example.id {
  # };
};