Das selbe IP-Subnetz an zwei Orten, per VPN gebrückt

Wenn man per VPN zwei Netzwerke miteinander verbindet, achtet man normalerweise darauf, dass jeder Standort ein eigenes Subnetz hat. Die VPN-Verbindung wird meistens vom Router verwaltet, der auch als Standard-Gateway eingetragen ist, dh. über ihn haben die Stationen im lokalen Subnetz Zugriff auf das Internet und auch auf das Netzwerk auf der anderen Seite. Der Router hat dann drei Netzwerkschnittstellen:

  • sein lokales Subnetz
  • Internetverbindung mit Standard-Gateway des Internet Providers
  • eine virtuelle Schnittstelle der VPN-Software, welche per VPN-Tunnel zur Gegenstelle, dem VPN-Router auf der anderen Seite führt

Das sieht dann beispielsweise so aus:

Bridging

Die Herausforderung, die Nils uns gestellt hat:

  • Wenn man auf dem Windows-Rechner oder Mac nach Netzwerkfreigaben sucht, so findet man diese nur aus dem lokalen Subnetz. Freigaben auf Rechnern im entfernten Subnetz werden nicht gefunden.
  • Nils möchte, dass auch Broadcasts durch das VPN übertragen werden. Die Freigabe-Protokolle (SMB, AFP…) erkennen Server und Client über Broadcast-Pakete.
  • Damit Broadcasts vom anderen Standort überhaupt verarbeitet werden können, müssen beide Standorte das selbe IP-Subnetz verwenden. Dann erkennen alle Stationen alle Broadcast-Pakete als lokale Netzwerkpakete.

Testaufbau

  • ein gemeinsames Subnetz 192.168.32.0/24
    – für besseren Überblick verwenden wir die Adressen 192.168.32.1 – 128 für das linke Netzwerk und 192.168.32.129 – 255 für das rechte Netzwerk, es bleibt aber ein Subnetz mit /24 Prefix bzw. einer Subnetzmaske von 255.255.255.0
  • für das VPN je Standort einen Raspberry Pi mit Raspbian
    • links 192.168.32.2
    • rechts 192.168.32.130
  • als Gateways je Standort eine Fritz!Box:
    • links 192.168.32.1
      DHCP-Pool 192.168.32.20 – 99
      Port-Weiterleitung 1194/udp auf 192.168.32.2
    • rechts 192.168.32.129
      DHCP-Pool 192.168.32.148 – 227
  • für die Internetanbindung stellen wir im Labor den Internetzugang der Fritz!Boxen jeweils auf LAN1 mit DHCP ein und schließen den LAN1-Port jeweils an unser LAN an, so dass die Fritz!Boxen über unseren normalen Router eine IP erhalten und auch ins Internet kommen
    • je einen Raspberry Pi schließen wir an einen der weiteren LAN-Ports an die passende Fritz!Box an
    • weitere Netzwerkclients schließen wir ebenfalls an die Fritz!Boxen an
  • für die VPN-Verbindung verwenden wir OpenVPN, da es sehr einfach zu installieren ist, mit einem einzelnen Port (1194 udp oder tcp) auskommt und problemlos durch ein NAT und eine Firewall mit Portweiterleitung geleitet werden kann, außerdem kann OpenVPN routen (tun) oder bridgen (tap)

links

Der Raspberry Pi auf der linken Seite ist unser OpenVPN-Server.

/etc/network/interfaces

source-directory /etc/network/interfaces.d

auto eth0

auto br0
iface br0 inet static
bridge_ports eth0 tap0
bridge_stp off
bridge_maxwait 0
address 192.168.32.2/24
gateway 192.168.32.1

Erklärung:

Wir wollen den Datenverkehr von unserer Netzwerkkarte – inkl. aller Broadcasts – direkt ins virtuelle VPN-Netzwerkinterface bekommen. Daher bedienen wir uns einer Bridge – sie verbindet alle an sie angehängte Netzwerkinterfaces im Betriebssystem. Dh. br0 ist das Netzwerkinterface für das Betriebssystem (mit IP-Adresse), an dem die „Hardware“ eth0 und tap0 hängt – auch wenn tap0 natürlich keine echte Hardware ist sondern vom OpenVPN-Server kommt. eth0 und tap0 sind Slaves von br0.

Damit og. Konfiguration funktioniert muss das Paket

bridge-utils

installiert werden.

/etc/openvpn/server/links.conf

server-bridge
dev tap0
port 1104
proto udp
persist-key
persist-tun
ca /etc/openvpn/easy-rsa2/keys/ca.crt
cert /etc/openvpn/easy-rsa2/keys/pi-links.crt
key /etc/openvpn/easy-rsa2/keys/pi-links.key
dh /etc/openvpn/easy-rsa2/keys/dh20148.pem
cipher AES-256-CBC
tls-auth /etc/openvpn/ta.key 0

topology subnet
keepalive 10 120
verb 3
status /var/log/openvpn/status.log
log /var/log/openvpn/links.log

Erklärung:

Wir definieren den Server als Bridge, ohne dass er Clients selbst eine IP-Adresse vergibt, daher lassen wir alle Parameter weg.

Wir verwenden als virtuelles Netzwerkinterface für die VPN-Verbindung ein tap-Device, da es wie eine Bridge funktioniert. Was wir auf der einen Seite reinstecken kommt auf der anderen unverändert heraus. Das Gegenstück zum tap-Device ist das tun-Device, es wird für geroutete Verbindungen genutzt.

Für die Authentifizierung haben wir easy-rsa aufgesetzt, das kann man sich auch sparen und nur mit der TLS-Authentifizierung arbeiten. Im Quickstart-Guide ist die Einrichtung erklärt: https://github.com/OpenVPN/easy-rsa/blob/master/README.quickstart.md

Den TLS-Schlüssel generieren wir mit

openvpn --genkey --secret ta.key

auf beiden Seiten muss derselbe Schlüssel verwendet werden – also nicht vergessen, ihn nachher auf den pi-rechts zu kopieren.

Pakete:

openvpn
easy-rsa
uml-utilities

OpenVPN-Server automatisch starten

systemctl enable openvpn-server@links.service

rechts

Der Raspberry Pi auf der rechten Seite ist unser OpenVPN-Client, er verbindet sich zum pi-links.

Auch hier installieren wir die folgenden Pakete:

openvpn
bridge-utils
uml-utilities

Auf easy-rsa kann verzichtet werden, da wir hier keine Zertifikate verwalten müssen, die bekommen wir vom Server links.

/etc/network/interfaces

source-directory /etc/network/interfaces.d

auto eth0

auto br0
iface br0 inet static
    pre-up /usr/sbin/openvpn --mktun --dev tap0
    bridge_ports eth0 tap0
    bridge_stp off
    bridge_maxwait 0
    address 192.168.32.130/24
    gateway 192.168.32.1

Erklärung:

Die Netzwerkonfiguration ist fast identisch zu links – natürlich eine andere IP-Adresse auf br0, ansonsten verbinden wir auch hier das physische Netzwerk eth0 mit der VPN-Verbindung auf tap0. Da der Client sich aber erst noch zum Server verbinden muss, müssen wir tap0 erst einmal „auf Vorrat“ erzeugen – daher der pre-up-Befehl.

/etc/openvpn/client/links.conf

client
dev tap0
port 1104
proto udp
persist-key
persist-tun
remote links.dyndns.org
resolve-retry infinite
ca /etc/openvpn/easy-rsa2/keys/ca.crt
cert /etc/openvpn/easy-rsa2/keys/pi-rechts.crt
key /etc/openvpn/easy-rsa2/keys/pi-rechts.key
dh /etc/openvpn/easy-rsa2/keys/dh20148.pem
cipher AES-256-CBC
tls-auth /etc/openvpn/ta.key 1

verb 3
status /var/log/openvpn/status.log
log /var/log/openvpn/links.log

Erklärung:

Hier definieren wir den Client. Die Konfigurations- und Logdateien haben wir nach der Gegenstelle benannt – theoretisch könnte dieser Client auch noch weitere VPN-Verbindungen zu anderen Gegenstellen haben, deren Konfigurationen und Logfiles man dann entsprechend nach ihnen benennt.

Unter remote trägt man den Namen oder die IP-Adresse der Internet-Verbindung der anderen Fritz!Box ein. Das muss man ggf. an der Fritz!Box (DynDNS unter Internet – Freigaben) einstellen. Portweiterleitung nicht vergessen, damit die Verbindung von aussen auf diesen Namen / diese IP-Adresse überhaupt beim Raspberry Pi hinter der Fritz!Box ankommt.

Die SSL-Zertifikate und der TLS-Schlüssel kommen vom Server.

OpenVPN-Client automatisch starten

systemctl enable openvpn-client@links.service

Clients

An den Clients müssen wir gar nichts einstellen. Sie erhalten von ihrer lokalen Fritz!Box per DHCP alle Einstellungen und verwenden auch die Fritz!Box als Gateway.

Ihr fragt Euch nun, wie die Clients in den unterschiedlichen Netzwerken sich überhaupt erreichen können, wenn die Clients gar nicht mit den Rasperry Pis kommunizieren.

Jeder Netzwerkteilnehmer baut sich anhand der IP-Adresse und Subnetzmaske eine Route ins lokale Netz auf die lokale Schnittstelle. Per DHCP kommt in der Regel noch ein Standard-Gateway dazu. Dahin leitet der Host alle Datenpakete weiter, für die er keine andere Route hat.

Möchte ein Host einen Teilnehmer über das lokale Netzwerkinterface erreichen – also ohne ein Gateway zu benutzen – dann sendet er einen ARP-Request über das entsprechende Interface als Broadcast aus.

In unserem Fall erhalten die Raspberries alle Broadcasts in ihrem jeweiligen Netzwerk. Sie haben nur ein Netzwerkinterface (br0) – mit zwei physischen Verbindungen – eth0 und tap0. Dh. ein ARP-Request aus dem lokalen Netz kommt per eth0 herein und wird direkt via tap0 durch das VPN auf die andere Seite gespiegelt. Dort antwortet ggf. der gewünschte Teilnehmer und OpenVPN cached – wie ein Netzwerkswitch – wo sich welche MAC-Adresse befindet und leitet die Datenpakete weiter.

Dh. pingt man einen Host am entfernten Standort an, dann dauert es einige Sekunden – durch die höhere Latenz via Internet und VPN – bis die Antwort des ARP-Requests angekommen ist. Danach können die Clients sich erreichen, als wären sie im selben Netz. Die Raspberries übernehmen die Funktion eines Patch-Kabels von einem Netzwerkswitch zum anderen.

Weitergedacht

  • Man könnte ggf. rechts ganz auf eine IP-Konfiguration für br0 verzichten und auf das DHCP an der Fritz!Box verzichten.
    Dann würde der OpenVPN-Client bei der Einwahl für seine br0 eine IP-Adresse von der linken Fritz!Box erhalten, die Hosts im rechten Subnetz würden – wie bei den ARP-Requests – per DHCP von der linken Fritz!Box bedient werden.
    Solange die VPN-Verbindung nicht steht wäre das rechte Netzwerk aber für DHCP-Clients nicht benutzbar.
  • Für die OpenVPN-Logfiles sollte man noch eine Konfiguration in /etc/logrotate.d/openvpn.conf anlegen, damit die Logs wöchentlich rotiert werden und nicht vollaufen können.
    Die Status-Datei wird immer wieder frisch geschrieben, dort ist nur der aktuelle Zustand eingetragen. Die muss man daher nicht rotieren.
  • Um den Datenverkehr zwischen den Standorten zu optimieren und zu minimieren könnte man via Wireshark prüfen, welcher Verkehr über das VPN läuft und ob man da mittels iptables unnötiges herausfiltern kann. Sonst kann es passieren, dass ein Großteil des lokalen Netzwerkverkehrs via VPN und Internet hin- und herübertragen wird.