Friday, May 6, 2022

IPv6, WiFi access point, nftables, and proxy_ndp

 For reasons I don't care to defend, my internet connection looks something like this:

vague network topology

Since my firewall is connected to the hotspot via WiFi, bridging to my internal network is out of the question. While there are many tutorials and approaches to getting IPv6 into a topology that looks like this, none of them work for me for various reasons. eg: the hotspot will not delegate a prefix via dhcpv6

I really want IPv6 so ... let's see what we can do!

proxy_ndp

First of all, we need to fool the hotspot into thinking that the Linux WiFi link has a bunch of addresses that actually belong to other computers behind it. IPv6 uses neighbor discover protocol (ndp) and the linux kernel provides a feature called proxy_ndp. When enabled and configured, it does exactly what we want!

I couldn't find any real automation surrounding this technology. However, it does allow my wifi interface to expose a number of IPv6 addresses that are behind the Linux firewall. Manual configuration looks something like this:

# enable proxy_ndp on the wireless link
sysctl -w net.ipv6.conf.wlan0.proxy_ndp=1
 
# tell the kernel to be discoverable for an IP it
# doesn't actually have
ip -6 neigh add proxy 2001:db8::100

There is a user space daemon which looks somewhat unmaintained called ndppd that I didn't have a lot of luck with. In theory, it automatically provides neighbor discovery between two network interfaces. We'll bridge that gap in a bit.


radvd

For our second step, we need to configure the nodes on the internal network to use the same prefix as we are getting from the hotspot. This is going to make routing a little fun but we'll get back to that in a moment.

There are a lot of things which will advertise routes for you. For example, dnsmasq can do it. I'm going to use radvd since that's all it does. Feel free to get fancy with something else here.

# Find the current prefix for wlan0
ip -6 route show dev wlan0
(for illustration, maybe it shows 2001:db8::/64)

# install/configure radvd
apt install radvd
vi /etc/radvd.conf
systemctl start radvd


sample radvd.conf:

# eth0 is the internal network interface
interface eth0
{
   AdvSendAdvert on;
   AdvDefaultPreference low;
   AdvHomeAgentFlag off;

    # Auto-configured prefix from wlan0
    prefix 2001:db8::/64
    {
       AdvOnLink on;
       AdvAutonomous on;
       AdvAutonomous on;
    };
};

more-specific routes

For our next step, we need to get the linux firewall to pass packets in the right directions. Since we only have a single IPv6 /64 prefix to work with, our default route and prefix is configured on the WiFi link while the internal network nodes are also provisioned on our internal link. This means we need a bunch of specific routes. Manual configuration looks something like this:

# Add a specific route for 2001:db8::100 to the internal network
ip -6 route add 2001:db8::100 dev eth0


nftables

Next, we need to get the kernel to actually forward packets. Let's use some firewall rules (these are quite loose, you'll probably want to tighten them up for your needs) and get our internal network online.


example /etc/nftables.conf:

define DEV_INTERNAL = eth0
define DEV_INTERNET = wlan0
define V6_PREFIX = { 2001:db8::/64 } 

table inet filter {
    chain forward {
        type filter hook forward priority 0;
        policy drop;

        # stateless acceptance of all traffic to/from the
        # internal network on the configured prefix
        ip6 saddr $V6_PREFIX iifname $DEV_INTERNAL accept;
        ip6 daddr $V6_PREFIX oifname $DEV_INTERNAL accept;

        # these can help with debugging
        ip6 saddr $V6_PREFIX log prefix "filter-forward (s) " drop;
        ip6 daddr $V6_PREFIX log prefix "filter-forward (d) " drop;
        log prefix "inet-forward (dropped) ";
    }
}

You will also need to enable forwarding and the nftables service for this.
 
# Enable ipv6 forwarding for all interfaces
sysctl -w net.ipv6.conf.all.forwarding=1
# reload firewall rules
systemctl restart nftables


Adding more nodes

At this point, only one node (2001:db8::/64) is online. It would be annoying to add each node manually, so we'll grab connection attempts and turn those into the requisite commands to run.


example nftables.conf:

define DEV_VETH veth0 

table netdev v6traffic {
    chain ingress-internal {
        type filter hook ingress device $DEV_INTERNAL priority 0;
        ip6 saddr $V6_PREFIX tcp flags syn dup to $DEV_VETH;
    }
}


example /etc/network/interfaces.d/veth0:

auto veth0
iface veth0 inet manual
    pre-up ip link add veth0 type veth peer veth1
    pre-up ip link set veth1 up
    pre-down ip link del veth0 type veth peer veth1


example hackish script (ymmv):

#!/bin/sh

tcpdump --immediate-mode -i veth1 'ip6' -l 2> /dev/null | \
  awk -W interactive -F. '
    /IP6/ {
    sub(/.*IP6 /, "");
    print "ip -6 neigh add proxy " $1 " dev wlan0";
    print "ip -6 route add " $1 " dev eth0";
  }' | sh -v

Save the above script somewhere (heck, rewrite it and make it better!) and let it run in the background. As nodes attempt to connect through the linux firewall, the nftables-dup-to-veth0 will trigger the proxy_ndp and specific route lines. There will be lots of duplication but fixing that is an exercise left to the comments :)