blog · git · desktop · images · contact & privacy · gopher


Eigener IPv6-Tunnel mit OpenVPN

Ich habe zuhause noch kein IPv6, aber einen Server im Internet mit einem /64-Netz. Weil ich trotzdem daheim damit experimentieren will, hatte ich mir vor einiger Zeit einen Tunnel gebaut, der SSH benutzt hat. Das war keine besonders tolle Lösung, aber sie hat erstmal funktioniert.

Jetzt habe ich mir die Sache noch einmal angeschaut und gesehen, dass OpenVPN mittlerweile (seit 2.3.0, also schon seit Januar 2013) nativ IPv6 kann. Das heißt, man kann direkt aus OpenVPN heraus IPv6-Adressen an die Clients vergeben und ist nicht mehr darauf angewiesen, mit OpenVPN nur eine IPv4-Verbindung aufzubauen, über die man dann zum Beispiel einen SIT-Tunnel aufbaut (siehe auch). Man braucht auch keinen radvd oder irgendwas in dieser Richtung, sondern OpenVPN verwaltet den IPv6-Adresskreis eigenständig.

Mal angenommen, man hat schon eine ganz einfache OpenVPN-Konfiguration so wie hier beschrieben, die dem Client eine private IPv4-Adresse zuweist. Dann kann man das auf Seite des Servers so erweitern:

tun-ipv6
server-ipv6 fd00:aaaa:bbbb:cccc::/112
push "route-ipv6 ::/0"

script-security 2
learn-address /etc/openvpn/scripts/learn-address

Ich habe einfach aus meinem /64er-Netz einen /112er-Block genommen, aus dem dann die Clients eine meiner öffentlichen IPv6-Adressen bekommen. Die gepushte Route erzeugt ein Default-Gateway für IPv6, sorgt also dafür, dass ein Client, der eigentlich nur in einem IPv4-Netz ist, seinen IPv6-Traffic über das VPN schickt.

Was hat es mit „learn-address“ auf sich? Angenommen, man hat auf eth0 eine öffentliche IPv4- und IPv6-Adresse des Servers:

$ ip -4 a show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 37.221.198.6/22 scope global eth0
       valid_lft forever preferred_lft forever

$ ip -6 a show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000
    inet6 2a03:4000:2:364:aec2:3146:f481:13f6/64 scope global 
       valid_lft forever preferred_lft forever
    ...

Durch das OpenVPN entsteht erst einmal nur zusätzlich ein tun0. Damit das ganze Konstrukt funktioniert, muss man dem Server noch sagen, dass er auch auf eth0 auf Pakete reagieren muss, die an eine der IPv6-Adressen der VPN-Clients adressiert sind. Erst dadurch landen Antworten von Remotes beim Client und de facto hat der Client dann eine IPv6-Adresse, über die er direkt aus dem Internet heraus erreichbar ist. Sprich, er hängt mit dem nackten Hintern im Internet ohne den üblichen „NAT-Schutz“ des DSL-Routers – you have been warned. Dafür ist einerseits das Skript „learn-address“ zuständig, das für jeden Client, der sich erfolgreich verbunden hat, einen entsprechenden NDP-Proxy-Eintrag auf eth0 anlegt und beim Disconnect wieder entfernt:

#!/bin/bash

action="$1"
addr="$2"
pubif=eth0

if [[ "${addr//:/}" == "$addr" ]]
then
    # not an ipv6 address
    exit
fi

case "$action" in
    add|update)
        ip neigh replace proxy "$addr" dev "$pubif"
        ;;
    delete)
        ip neigh del proxy "$addr" dev "$pubif"
        ;;
esac

(Wenn OpenVPN mit „user“ und „group“ Privilegien abgibt, dann muss hier natürlich „sudo“ im Spiel sein o.ä.)

Andererseits muss proxy_ndp und Forwarding aktiviert sein, was man zum Beispiel über eine /etc/sysctl.d/10-openvpn.conf lösen kann:

net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.all.proxy_ndp = 1

Das war’s.

Auf Seite des Clients muss man nur „tun-ipv6“ einfügen.

Ich finde, das ist eine recht schnucklige Lösung. Einfach aufzubauen. Bis auf das mit dem NDP-Proxy und abgesehen vom üblichen PKI-Overhead von OpenVPN auch recht elegant.