Inhaltsverzeichnis

Offloader auf Basis eines Raspberry PI 4B (WireGuard)

Bild: Freifunk München Logo

Disclaimer: Die Anleitung ist nur für Leute, die sich bereits mit dem Thema Linux auseinander gesetzt haben oder sich intensiv damit beschäftigen wollen!

Diese Anleitung geht von einem Linux Hostrechner aus - sie funktioniert auf quasi allen Linux basierten Betriebssystemen, mit ein paar Anpassungen.

In den nachfolgenden Beispielen zu den Shell-/Konsolen-Befehlen gelt folgende Formatierungen:

  • Ein vorangestelltes Dollarzeichen $ bedeutet, dass der bzw. die Befehle mit Userrechten des gerade angemeldeten Benutzers ausgeführt werden können.
  • Ein vorangestelltes Raute-Symbol # hingegen bedeutet, dass zum Ausführen des Befehls bzw. der Befehle Root-Rechte benötigt werden, die man z.b. mit Hilfe von $ sudo su - erlangen kann.


Sollten trotz des eingehenden Studiums dieser Anleitung dennoch Fragen offen bleiben, oder unvorhergesehenen Problemstellungen auftreten, findet man im Chat sicher schnell Hilfe und Unterstützung.

Mit dem Raspberry PI 4 ist es das erste Mal möglich, einen Offloader sinnvoll auf einem PI zu betreiben. Denn in dieser Version bietet der Ethernet Anschluss genug Durchsatz, und die CPU ist leistungsfähig genug um eine ordentliche WireGuard Performance zu bieten. Dies war bei den Vorgänger Modellen so leider nicht der Fall.

Für kabelgebundene Clients und/oder Meshing per Kabel benötigt ihr einen VLAN-fähigen Switch!

Angeboten wird das PI in einer 1GB, 2GB und 4GB RAM Variante. Will man wirklich nur einen reinen Offloader ohne Zusatzfunktionen betreiben, reicht im Grunde die 1GB Version. Allerdings ist mehr RAM immer besser ;) und man weiß ja nicht, was man im Endeffekt noch alles darauf betreiben will.

Bild: Raspberry 4 - Test-/Installationsaufbau Bild: Raspberry 4 - Lieferung/Verpackung
Bild: Raspberry 4 - Platine in Verpackung

Raspberry Pi OS

Nachdem es aktuell1) noch kein fertiges Gluon Image für das Raspberry PI 4B gibt, holen wir uns nun das aktuelle Raspberry Pi OS (früher unter dem Namen Raspbian bekannt) auf unseren Rechner. Dies hat mitunter auch noch den Charme, dass wir bei Bedarf alle normalen Anwendungen wie Webserver, Chatserver oder z.B. den Unifi-Controller einfach installieren können.

Eine Anleitung zur manuellen Installation findet man auf der offiziellen Raspbian Seite.

Download

Wir laden uns also das betreffende ZIP-Archiv auf unseren Rechner.

 $ wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2022-04-07/2022-04-04-raspios-bullseye-arm64-lite.img.xz{,.sha256}

Bevor wir nun das Archiv entpacken überprüfen wir noch die Integrität der heruntergeladenen Datei.

 $ sha256sum --check 2022-04-04-raspios-bullseye-arm64-lite.zip.sha256
2022-04-04-raspios-bullseye-arm64-lite.zip: OK

Da unser heruntergeladenes Archiv soweit okay ist, können wir dieses entpacken.

 $ unzip 2022-04-04-raspios-bullseye-arm64-lite.zip
Archive:  2022-04-04-raspios-bullseye-arm64-lite.zip
  inflating: 2022-04-04-raspios-bullseye-arm64-lite.img

Kopieren des Images auf die MicroSD-Karte

Nun können wir das Image auf die MicroSD-Karte, die wir später in den Raspberry 4B stecken, kopieren. Wir werfen also am besten einmal einen Blick in das syslog unseres Arbeitsrechners und erkennen so das Device unserer Speicherkarte.

 # tail -f /var/log/messages

bzw.

 $ sudo tail -f /var/log/syslog
Sep  5 21:10:57 BOFHs-X230 kernel: [12795.867603] mmc0: new high speed SDHC card at address aaaa
Sep  5 21:10:57 BOFHs-X230 kernel: [12795.868313] mmcblk0: mmc0:aaaa SC16G 14.8 GiB 
Sep  5 21:10:57 BOFHs-X230 kernel: [12795.871017]  mmcblk0: p1 p2
Sep  5 21:10:58 BOFHs-X230 kernel: [12796.199093] FAT-fs (mmcblk0p1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.
Sep  5 21:10:58 BOFHs-X230 systemd[1]: Finished Clean the /media/django/boot mount point.
Sep  5 21:10:58 BOFHs-X230 udisksd[976]: Mounted /dev/mmcblk0p1 at /media/django/boot on behalf of uid 1001
Sep  5 21:10:58 BOFHs-X230 kernel: [12796.302402] EXT4-fs (mmcblk0p2): recovery complete
Sep  5 21:10:58 BOFHs-X230 kernel: [12796.303545] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
Sep  5 21:10:58 BOFHs-X230 systemd[1]: Finished Clean the /media/django/rootfs mount point.
Sep  5 21:10:58 BOFHs-X230 udisksd[976]: Mounted /dev/mmcblk0p2 at /media/django/rootfs on behalf of uid 1001
Sep  5 21:11:09 BOFHs-X230 gnome-terminal-[8119]: g_menu_insert_item: assertion 'G_IS_MENU_ITEM (item)' failed

Aus der zweiten Zeile ergibt sich, dass die MicroSD-Karte über die Gerätedatei /dev/mmcblk0 angesprochen werden kann. Die nachfolgenden Zeilen zeigen an, wie das Linux-System den bestehenden Inhalt der Karte interpretiert - tut aber für unsere Zwecke nichts zur Sache, weil wir die Karte eh gleich überschreiben. Achtung: schließt man die MicroSD-Karte z.B. über einen USB-Adapter an, kann sie auch unter einem anderen Namen erscheinen, z.B. /dev/sde. Und hier ist Gefahr in Verzug, weil die System-Platte des Host-Rechners auch unter /dev/sd<irgendwas> erscheint, und es auch erfahrenen Benutzern passiert, die Buchstaben zu verwechseln, und beim nachfolgenden Schreibvorgang sein System zerschießt.

Mit Hilfe der Gerätedatei können wir das heruntergeladene Debian Bullseye Image-Datei auf die MicroSD-Karte kopieren. In der Regel hat der „normale Nutzer“ keine Rechte um diese Gerätedatei anzusprechen, wir müssen also als Benutzer root oder mit den Rechten des Benutzers root den Kopiervorgang aufrufen:

 # dd if=~/2022-01-28-raspios-bullseye-arm64-lite.img of=/dev/mmcblk0  bs=4M status=progress conv=fsync

bzw.

 $ sudo dd if=/home/django/Freifunk/2022-01-28-raspios-bullseye-arm64-lite.img of=/dev/mmcblk0 bs=4M status=progress conv=fsync

Wenn der Kopiervorgang abgeschlossen ist, Karte entfernen und wieder einstecken. Das System erkennt die beiden Partitions auf der Karte. Je nach Systemeinstellungen mountet sie die Partitions automatisch - in der Regel im Verzeichnis /run/media/ oder auch /media/. Oder es erscheint in der Systemleiste ein Icon für einen neu erkannten Wechseldatenspeicher, den man dann per Mausklick mounten kann. Wer unsicher ist, kann

 # mount

aufrufen, und feststellen, wo die root-Partition und die boot-Partition zu finden ist.

Da wir später weder Tastatur noch Monitor an unseren Raspberry 4B anstecken wollen, diesen demnach im headless-Mode betreiben wollen und werden, legen wir noch eine leere Datei ssh auf der boot-Partition ab. Damit wird beim Hochfahren der ssh-Server auf dem RPi gestartet, mit dessen Hilfe wir uns über das Netzwerk einloggen können. Zum Anlagen dieser Datei ssh reicht folgender Aufruf, bei dem wir den Pfad, und insbesondere den Usernamen im Pfad, natürlich unseren Gegebenheiten entsprechend anpassen:

 # touch /run/media/django/boot/ssh

bzw.

 $ touch /media/django/boot/ssh

Anschließend können wir nach einem Unmounten der Partitions /dev/mmcblk0p* die Micro-SD-Karte in den Kartenslot des Raspberry 4B stecken und den Kleinstcomputer mit dem Netzwerk sowie dem zugehörigen Netzteil verbinden und starten.

Vorbereitende Konfigurationsschritte

Ändern des Default-Passwortes und kopieren des SSH-Public-Keys auf den Raspberry 4

Der Benutzername lautet piund das zugehörige Default-Passwort raspberry. Das Passwort dieses Nutzers ändern wir nun als erstes ab, da sonst die Gefahr besteht, dass Fremde sich unseres Offloaders bemächtigen!

Wir ändern also das Default-Passwort gleich mal ab und packen auch unseren SSH-Public-key auf den Raspberry 4B. Da wir die IP-Adresse, die unser Raspberry vom DHCP-Server zugewiesen bekommt in unserer SSH-Client-Konfigurationsdatei bereits hinterlegt haben, können wir nun den RaspBerry 4B direkt über den definierten Namen raspberry-ansible ansprechen.

 $ ssh -l pi raspberry-ansible -o IdentitiesOnly=yes "passwd" && \
       ssh-copy-id -i ~/.ssh/id_ed25519_ffmuc.pub -o IdentitiesOnly=yes pi@raspberry-ansible

Alternativ dazu müssten wir den Raspberry 4B über die IP-Adresse ansprechen, die dieser zugewiesen bekommt. In dem folgenden Beispiel wäre das die IP-Adresse: 192.168.0.25:

 $ ssh -l pi 192.168.0.25 -o IdentitiesOnly=yes "passwd" && \
       ssh-copy-id -i ~/.ssh/id_ed25519_ffmuc.pub -o IdentitiesOnly=yes pi@192.168.0.25

In dem folgenden Konfigurationsbeispiel vergeben wir für den Benutzer pi das Passwort gECzebzn7GYSLvXueECAxeGm7l7. Beim Ändern des bestehenden Passwortes müssen wir einmal das Default-Passwort raspberry eingeben und dann 2x das neue gECzebzn7GYSLvXueECAxeGm7l7. Beim Kopieren des Public-Keys müssen wir dann einmalig das neue geänderte Passwort gECzebzn7GYSLvXueECAxeGm7l7 verwenden.

The authenticity of host '192.168.0.25 (192.168.0.25)' can't be established.
ECDSA key fingerprint is SHA256:vXhdvud24tyTUtOan4XeTZ7GzxZDQn9Rj0mxuhimkH4.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.0.25' (ECDSA) to the list of known hosts.
pi@192.168.0.25's password: 
Current password: raspberry
New password: gECzebzn7GYSLvXueECAxeGm7l7
Retype new password: gECzebzn7GYSLvXueECAxeGm7l7
passwd: password updated successfully
Changing password for pi.
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/django/.ssh/id_ed25519_freifunk.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
pi@192.168.0.25's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh -o 'IdentitiesOnly=yes' 'pi@raspberry-ansible'"
and check to make sure that only the key(s) you wanted were added.
 $ ssh raspberry-ansible 

Linux raspberrypi 5.4.51-v7l+ #1333 SMP Mon Aug 10 16:51:40 BST 2020 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/STERNCHEN/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

Wi-Fi is currently blocked by rfkill.
Use raspi-config to set the country before use.

pi@raspberrypi:~ $ 

Mit Hilfe des Befehls exit verlassen wir bei Bedarf die gerade laufende SSH-Verbindung mit unserem Raspberry 4. Doch bevor wir dies tun, nehmen wir noch ein paar grundlegende Konfigurationsschritte vor, die nachfolgend beschrieben sind.

Installation von VIM

Für die weiteren Konfigurationsschritte werden Root-Rechte benötigen, schlüpfen wir also als erstes in dessen Rolle:

 $ sudo su -
Wi-Fi is currently blocked by rfkill.
Use raspi-config to set the country before use.

root@raspberrypi:~#

Anschließend installieren wir uns am besten die weiterentwickelte Variante vim des Standard-Editors vi - wir wollen uns ja später die Arbeit nicht unnötig schwer machen.

 # apt install vim -y

Änderung des Hostnamens

Unser neuer Offloader braucht einen eigenen Namen, mit dem er sich von anderen eigenen Raspberry P4 unterscheiden lässt. In unserem Konfigurationsbeispiel soll der Name rpb4-wg-ol lauten. Das Setzen des Nodenamens, mit der unser Offloader dann auch auf der Freifunk-Karte später erscheinen wird, erfolgt im Abschnitt Konfiguration des ext-respondd!

Wir setzen den Hostnamen wie folgt:

 # hostnamectl set-hostname rpb4-wg-ol

Damit es beim späteren Wechsel der Userrechte zu keiner Warnmeldung kommt, weil der Hostname nicht aufgelöst werden konnte, korrigieren wir gleich noch die Konfigurationsdatei /etc/hosts, und ändern auch dort den Standardnamen raspberrypi auf unseren neuen individuellen Namen rpb4-wg-ol ab.

 # vim /etc/hosts
/etc/hosts
127.0.0.1	localhost 
::1		localhost ip6-localhost ip6-loopback
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters
 
# Django : 2020-12-16
# default: 127.0.1.1	raspberrypi
127.0.1.1	rpb4-wg-ol

Will man später nachvollziehen, welche Änderungen durch wen und wann gemacht wurden, kann es sehr hilfreich und zweckmäßig sein, entsprechende Änderungen in den Konfigurationsdateien, wie in dem gezeigtem Beispiel nachvollziehbar zu dokumentieren.

Entsperren des WiFi-Unterstützung

Möchte später auch das WLAN des Raspberry verwenden, so ist es zwingend von Nöten, dass WIFI explizit via rfkill freizugeben! Hier loggen wir uns per SSH ein und setzen folgenden Befehl ab:

 # rfkill unblock wifi

Erst ein Reboot unseres Raspberry 4 aktiviert das WLAN tatsächlich!

 # systemctl reboot

Initialer Update/Upgrade

Doch bevor wir unseren Raspberry 4 neu starten nehmen wir noch einen Update bzw. Upgrade aller installierten Pakete und -quellen vor!

 # apt-get update
 # apt-get upgrade -y

Nun ist es dann doch an der Zeit, dass wir unseren Kleinstrechner rpb4-wg-ol neu zu starten.

 # systemctl reboot

BATMAN

Für das Routing der Layer-2-Verbindungen innerhalb des Freifunknetzes wird als Mesh-Protokoll B.A.T.M.A.N.-Advanced kurz batman-adv eingesetzt, welches wir uns nun in diesem Abschnitt installieren und konfigurieren wollen.

Am Besten vergewissern wir uns noch vorab, welches die aktuelle BATMAN Version ist. Anfang August 2022 wäre dies die Version 2020.2

Download und entpacken

Nach dem erfolgten Neustart des Rechners melden wir uns wieder mit Hilfe der SSH an unserem Ziel-Host an.

 $ ssh -l pi 192.168.0.25

Für die weiteren Konfigurationsschritte werden wie gewohnt Root-Rechte benötigt.

 $ sudo su -

Nachdem wir uns eingeloggt haben, wechseln wir in das Zielverzeichnis /usr/src/, laden dort das aktuelle B.A.T.M.A.N advanced Archiv herunter und entpacken es anschließend an Ort uns Stelle.

 # cd /usr/src/
 # wget https://downloads.open-mesh.org/batman/releases/batman-adv-2022.2/batman-adv-2022.2.tar.gz{,.sha1}

Bevor wir nun das Archiv entpacken überprüfen wir noch die Integrität der heruntergeladenen Datei.

 $ sha1sum --check batman-adv-2022.2.tar.gz.sha1
batman-adv-2022.2.tar.gz.sha1: OK
 # tar xzf batman-adv-2022.2.tar.gz

Kernelmodule

Nachdem das Kernelmodul manuell gebaut wird, wollen wir natürlich, dass das auch Bestand hat, wenn ein Kernelupdate eingespielt wird. Dazu brauchen wir nun einige Pakete, die wir erst einmal installieren werden.

 # apt update && apt install dkms raspberrypi-kernel-headers

Anschließend müssen wir die make Skripte neu generieren, weil der Raspberry PI Kernel ursprünglich crosscompiliert wurde.

 # cd linux-headers-$(uname -r)
 # make scripts

Es kann passieren, dass hier nach einiger Zeit mit der nachfolgenden Fehlermeldung abgebrochen wird, das stellt aber kein Problem dar:

  HOSTCC  scripts/sortextable
scripts/sortextable.c:31:10: fatal error: tools/be_byteshift.h: No such file or directory
 #include <tools/be_byteshift.h>
          ^~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[1]: *** [scripts/Makefile.host:107: scripts/sortextable] Error 1
make: *** [Makefile:1123: scripts] Error 2

Da nun alle nötigen vorbereitenden Installations- und Konfigurationsschritte abgearbeitet sind, machen wir uns an das Anlegen der für B.A.T.M.A.N. notwendigen Konfigurationsdatei dkms.conf.

 # cd ../batman-adv-2022.0/
 # vim dkms.conf

Der Inhalt der dkms.conf sieht wie folgt aus:

/usr/src/batman-adv-2022.2/dkms.conf
# Django : 2020-12-15
PACKAGE_NAME=batman-adv
PACKAGE_VERSION=2022.2
 
DEST_MODULE_LOCATION=/extra
BUILT_MODULE_NAME=batman-adv
BUILT_MODULE_LOCATION=net/batman-adv
 
MAKE="'make'"
CLEAN="'make' clean"
 
AUTOINSTALL="yes"

Zum Speichern und Verlassen des Editors vim benutzen wir den Vim-Syntax ESC :x.

Nun sind wir mit unserer Konfiguration soweit, dass wir die B.A.T.M.A.N-Kernelmodule bauen können.

 # dkms add -m batman-adv -v 2022.2
 # dkms build -m batman-adv -v 2022.2
 # dkms install -m batman-adv -v 2022.2

Als nächstes müssen wir dafür sorgen, dass die beiden Kernelmodule batman-adv und dummy beim Booten des Systems auch geladen werden. Dazu müssen wir die Datei „/etc/modules-load.d/batman-adv.module.conf“ wie folgt anpassen.

 # vim /etc/modules-load.d/batman-adv.module.conf
/etc/modules-load.d/batman-adv.module.conf
# Django : 2020-12-15
#
# Load batman-adv module on system boot
#
batman-adv
dummy

Zum Aktivieren kann man nun entweder rebooten oder die Module wie nachfolgend gezeigt manuell laden.

 # modprobe dummy 
 # modprobe batman_adv

Wollen wir überprüfen ob beide Module geladen sind, können wir dies mit den folgenden Befehl(en) abfragen:

 # lsmod | grep dummy && lsmod | grep batman_adv
dummy                  16384  0
batman_adv            184320  0
bridge                139264  1 batman_adv
cfg80211              675840  2 batman_adv,brcmfmac

Installation und Konfiguration von batctl

Um BATMAN verwalten zu können müssen wir nun noch „batctl“ installieren.

 # apt-key adv --keyserver hkps://keyserver.ubuntu.com --recv-key 04EE7237B7D453EC
 # apt-key adv --keyserver hkps://keyserver.ubuntu.com --recv-key 648ACFD622F3D138
 # echo "deb http://deb.debian.org/debian bullseye-backports main contrib non-free" | tee /etc/apt/sources.list.d/bullseye-backports.list
 # apt update
 # apt-get -t bullseye-backports install batctl

Anschließend überprüfen wir ob alles korrekt geladen wurde.

 # batctl ra
Selected routing algorithm (used when next batX interface is created):
 => BATMAN_IV

Available routing algorithms:
 * BATMAN_IV
 * BATMAN_V

Nachdem bei Freifunk München BATMAN_V verwendet wird, sehen wir dass aktuell der falsche Routing Algorithmus ausgewählt ist. Das korrigieren einmal sofort manuell.

 # batctl ra BATMAN_V

Eine erneute Abfrage zeigt nun auf den korrekten Routing Algorithmus BATMAN_V.

 # batctl ra
Selected routing algorithm (used when next batX interface is created):
 => BATMAN_V

Available routing algorithms:
 * BATMAN_IV
 * BATMAN_V

Nun kommen wir zur Interface Konfiguration. In diesem Beispiel werden wir den RPi-Offloader im Segment „welt“ ansiedeln, deswegen benennen wir das zugehörige Interface br-welt auch entsprechend. Hierzu benötigen wir noch die bridge-utils, die wir uns noch installieren:

 # apt install bridge-utils -y

Wir können mit nachfolgendem Befehl überprüfen, ob die Version von batctl auch zur installierten Version von batman-adv passt.

 # batctl -v
batctl debian-2022.2-1~bpo11+1 [batman-adv: 2022.2]

Da beides in der Version 2022.2 vorliegt, können wir mit der weiteren Installation und Konfiguration wie gewohnt fortfahren.

Interface-Konfiguration

Die Konfiguration selbst nehmen wir über die Datei /etc/network/interfaces vor, die wir nun entsprechend ergänzen werden.

 # vim /etc/network/interfaces
/etc/network/interfaces
# interfaces(5) file used by ifup(8) and ifdown(8)
 
# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'
 
# Include files from /etc/network/interfaces.d:
# Django : 2020-12-15
# default: source-directory /etc/network/interfaces.d
 
auto eth0
iface eth0 inet dhcp
 
auto br-welt
iface br-welt inet dhcp
        bridge-ports bat-welt
        pre-up /usr/sbin/batctl ra BATMAN_V
        pre-up /sbin/ip link add dummy-welt type dummy
        pre-up /sbin/ip link set address $(ip -br l | grep eth0 | egrep -o '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | head -1) dev dummy-welt
        pre-up /sbin/ip link set dummy-welt up
        pre-up /usr/sbin/batctl meshif bat-welt if add dummy-welt
        pre-up /sbin/ip link set bat-welt up
        pre-up /usr/sbin/batctl meshif bat-welt gw_mode client
        pre-up /sbin/ip link set address $(ip -br l | grep eth0 | egrep -o '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | head -1) dev bat-welt
        post-up /sbin/ip link set address $(ip -br l | grep eth0 | egrep -o '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | head -1) dev br-welt

In diesem Beispiel setzen wir die MAC Addresse für BATMAN fest auf die MAC von eth0, damit wir später auch Statistiken etc abrufen können. Anschließend starten wir den Raspberry einmal neu!

 # systemctl reboot

WIREGUARD

Grundlagen / Hintergründe

Für die Verbindung unseres Offloaders/Nodes zum Freifunk München Netz greifen wir auf den VPN-Tunnel Mechanismus von WireGuard zurück. Hintergründe zum Wechsel von FASTD hin zu WireGuard ist in diesem BlogPost ausführlich beschrieben.

Für die Definition und Einrichtung des WireGuard-VPN-Tunnels werden abhängig vom gewählten Segment folgende Daten und Informationen benötigt:

  • Domain : Domain/Segment-Name in dem der Node/Offloader betrieben werden soll.
  • Endpoint : Hostname des VPN-Endpunktes / Gateway.
  • Port : Port am Gateway
  • (remote) publickey : Öffentliche Schlüssel des VPN-Gatway-Endpunktes
  • lokale link-local-Adresse : IPv6 Adresse des VPN-Endpunktes am Offloader
  • (client) privatekey : Private :!: Schlüssel für den WireGuard-Endpunkt am Offloader
  • (client) publickey : Öffentliche Schlüssel für den WireGuard-Endpunkt am Offloader

Bitte informiert euch vorher in welches Segment ihr wollt, dementsprechend müssen die Daten aus nachstehender Tabelle entnommen bzw. lokal generiert werden!

Domain Tunnel endpoint Port link_address am Gateway publickey Gateway domain_seed mesh vpn vxlan id
ffmuc_muc_cty gw04.ext.ffmuc.net 40002 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= a2e8fb0dfb713f6020b893f1c505cee46abb0e63928e8ff1c1fbe3377f56e150 3836090
ffmuc_muc_cty gw05.ext.ffmuc.net 40002 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= a2e8fb0dfb713f6020b893f1c505cee46abb0e63928e8ff1c1fbe3377f56e150 3836090
ffmuc_muc_cty gw06.ext.ffmuc.net 40002 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= a2e8fb0dfb713f6020b893f1c505cee46abb0e63928e8ff1c1fbe3377f56e150 3836090
ffmuc_muc_cty gw07.ext.ffmuc.net 40002 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= a2e8fb0dfb713f6020b893f1c505cee46abb0e63928e8ff1c1fbe3377f56e150 3836090
ffmuc_muc_nord gw04.ext.ffmuc.net 40003 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= ef2c3c3336025a4cb3076a61b112a59a042781c7ce3359d96102d197b99ac89b 1920014
ffmuc_muc_nord gw05.ext.ffmuc.net 40003 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= ef2c3c3336025a4cb3076a61b112a59a042781c7ce3359d96102d197b99ac89b 1920014
ffmuc_muc_nord gw06.ext.ffmuc.net 40003 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= ef2c3c3336025a4cb3076a61b112a59a042781c7ce3359d96102d197b99ac89b 1920014
ffmuc_muc_nord gw07.ext.ffmuc.net 40003 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= ef2c3c3336025a4cb3076a61b112a59a042781c7ce3359d96102d197b99ac89b 1920014
ffmuc_muc_ost gw04.ext.ffmuc.net 40004 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 4d57658aaf4428149771a5decfa5d6b7d8b129e3915ebcd5d7329400a6dd48ff 12097488
ffmuc_muc_ost gw05.ext.ffmuc.net 40004 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 4d57658aaf4428149771a5decfa5d6b7d8b129e3915ebcd5d7329400a6dd48ff 12097488
ffmuc_muc_ost gw06.ext.ffmuc.net 40004 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 4d57658aaf4428149771a5decfa5d6b7d8b129e3915ebcd5d7329400a6dd48ff 12097488
ffmuc_muc_ost gw07.ext.ffmuc.net 40004 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 4d57658aaf4428149771a5decfa5d6b7d8b129e3915ebcd5d7329400a6dd48ff 12097488
ffmuc_muc_sued gw04.ext.ffmuc.net 40005 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 2461d70aa0453a1fc17131d6eb7ba3a60560d94b376d3039bd6c28a961cfa5e2 12815947
ffmuc_muc_sued gw05.ext.ffmuc.net 40005 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 2461d70aa0453a1fc17131d6eb7ba3a60560d94b376d3039bd6c28a961cfa5e2 12815947
ffmuc_muc_sued gw06.ext.ffmuc.net 40005 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 2461d70aa0453a1fc17131d6eb7ba3a60560d94b376d3039bd6c28a961cfa5e2 12815947
ffmuc_muc_sued gw07.ext.ffmuc.net 40005 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 2461d70aa0453a1fc17131d6eb7ba3a60560d94b376d3039bd6c28a961cfa5e2 12815947
ffmuc_muc_west gw04.ext.ffmuc.net 40006 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 4daea463c2c73009c9dffdd70282ccf5b36407e5179875beae66a5dc823eabe8 29149
ffmuc_muc_west gw05.ext.ffmuc.net 40006 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 4daea463c2c73009c9dffdd70282ccf5b36407e5179875beae66a5dc823eabe8 29149
ffmuc_muc_west gw06.ext.ffmuc.net 40006 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 4daea463c2c73009c9dffdd70282ccf5b36407e5179875beae66a5dc823eabe8 29149
ffmuc_muc_west gw07.ext.ffmuc.net 40006 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 4daea463c2c73009c9dffdd70282ccf5b36407e5179875beae66a5dc823eabe8 29149
ffmuc_uml_nord gw04.ext.ffmuc.net 40007 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= d4fe53ea5eb7d57cecb69b4be43b2eac760e18f10f2f26ff3db801c3b90ea650 403289
ffmuc_uml_nord gw05.ext.ffmuc.net 40007 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= d4fe53ea5eb7d57cecb69b4be43b2eac760e18f10f2f26ff3db801c3b90ea650 403289
ffmuc_uml_nord gw06.ext.ffmuc.net 40007 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= d4fe53ea5eb7d57cecb69b4be43b2eac760e18f10f2f26ff3db801c3b90ea650 403289
ffmuc_uml_nord gw07.ext.ffmuc.net 40007 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= d4fe53ea5eb7d57cecb69b4be43b2eac760e18f10f2f26ff3db801c3b90ea650 403289
ffmuc_uml_ost gw04.ext.ffmuc.net 40008 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 2560ad91705d2e5d259a7abc43a9f5f49631f06b75aae8e8c417c01bab229820 12645856
ffmuc_uml_ost gw05.ext.ffmuc.net 40008 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 2560ad91705d2e5d259a7abc43a9f5f49631f06b75aae8e8c417c01bab229820 12645856
ffmuc_uml_ost gw06.ext.ffmuc.net 40008 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 2560ad91705d2e5d259a7abc43a9f5f49631f06b75aae8e8c417c01bab229820 12645856
ffmuc_uml_ost gw07.ext.ffmuc.net 40008 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 2560ad91705d2e5d259a7abc43a9f5f49631f06b75aae8e8c417c01bab229820 12645856
ffmuc_uml_sued gw04.ext.ffmuc.net 40009 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= f14233863ee4ca3453914505c61f0e0417ed2da0b1695ba13a39bf67e6dabb04 12090508
ffmuc_uml_sued gw05.ext.ffmuc.net 40009 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= f14233863ee4ca3453914505c61f0e0417ed2da0b1695ba13a39bf67e6dabb04 12090508
ffmuc_uml_sued gw06.ext.ffmuc.net 40009 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= f14233863ee4ca3453914505c61f0e0417ed2da0b1695ba13a39bf67e6dabb04 12090508
ffmuc_uml_sued gw07.ext.ffmuc.net 40009 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= f14233863ee4ca3453914505c61f0e0417ed2da0b1695ba13a39bf67e6dabb04 12090508
ffmuc_uml_west gw04.ext.ffmuc.net 40010 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 649b28f2a3c71293d62609e3673d95987d39f4fd19e52f8df4602c80127223b3 935867
ffmuc_uml_west gw05.ext.ffmuc.net 40010 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 649b28f2a3c71293d62609e3673d95987d39f4fd19e52f8df4602c80127223b3 935867
ffmuc_uml_west gw06.ext.ffmuc.net 40010 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 649b28f2a3c71293d62609e3673d95987d39f4fd19e52f8df4602c80127223b3 935867
ffmuc_uml_west gw07.ext.ffmuc.net 40010 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 649b28f2a3c71293d62609e3673d95987d39f4fd19e52f8df4602c80127223b3 935867
ffmuc_welt gw04.ext.ffmuc.net 40011 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26 4831583
ffmuc_welt gw05.ext.ffmuc.net 40011 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26 4831583
ffmuc_welt gw06.ext.ffmuc.net 40011 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26 4831583
ffmuc_welt gw07.ext.ffmuc.net 40011 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26 4831583
ffmuc_gauting gw04.ext.ffmuc.net 40012 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= ae63f3daf9326f3b267b941caf790b2d20eadeacf9ef46d9ca53d7495f559db0 4681119
ffmuc_gauting gw05.ext.ffmuc.net 40012 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= ae63f3daf9326f3b267b941caf790b2d20eadeacf9ef46d9ca53d7495f559db0 4681119
ffmuc_gauting gw06.ext.ffmuc.net 40012 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= ae63f3daf9326f3b267b941caf790b2d20eadeacf9ef46d9ca53d7495f559db0 4681119
ffmuc_gauting gw07.ext.ffmuc.net 40012 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= ae63f3daf9326f3b267b941caf790b2d20eadeacf9ef46d9ca53d7495f559db0 4681119
ffmuc_freising gw04.ext.ffmuc.net 40013 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= e50c53517f25160c134711e4e39a0240d3cf4f4b0adfd950ac232bad2c5c264b 4669918
ffmuc_freising gw05.ext.ffmuc.net 40013 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= e50c53517f25160c134711e4e39a0240d3cf4f4b0adfd950ac232bad2c5c264b 4669918
ffmuc_freising gw06.ext.ffmuc.net 40013 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= e50c53517f25160c134711e4e39a0240d3cf4f4b0adfd950ac232bad2c5c264b 4669918
ffmuc_freising gw07.ext.ffmuc.net 40013 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= e50c53517f25160c134711e4e39a0240d3cf4f4b0adfd950ac232bad2c5c264b 4669918
ffmuc_augsburg gw04.ext.ffmuc.net 40014 fe80::27c:16ff:fec0:6c74 TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= a5fcde9c4e52267d204f473d053abb9bbcf49501200f0ddb57041c6624e8c544 2962115
ffmuc_augsburg gw05.ext.ffmuc.net 40014 fe80::281:8eff:fef0:73aa igyqOmWiz4EZxPG8ZzU537MnHhaqlwfa7HarB3KmnEg= a5fcde9c4e52267d204f473d053abb9bbcf49501200f0ddb57041c6624e8c544 2962115
ffmuc_augsburg gw06.ext.ffmuc.net 40014 fe80::2a2:e4ff:fef9:2269 pkRaUOoLuuHnUt9BEGeKrhF3OMYBPecc0iYkika6uhE= a5fcde9c4e52267d204f473d053abb9bbcf49501200f0ddb57041c6624e8c544 2962115
ffmuc_augsburg gw07.ext.ffmuc.net 40014 fe80::23b:d2ff:fe95:967f PcKkakZcTEx3LKh+G06Opb8/esg08aWK33A5/Ff1YXE= a5fcde9c4e52267d204f473d053abb9bbcf49501200f0ddb57041c6624e8c544 2962115

Installation

Das zu WireGuard gehörige Programm wg mit den entsprechenden Kernelmodulen installieren wir uns nun mit Hilfe von apt.

 # apt install wireguard --assume-yes

Konfiguration

In unserem Konfigurationsbeispiel wollen wir unseren Offloader im Segment ffmuc_welt betreiben und uns mit dem Gateway gw04 von Freifunk München verbinden. Demnach lauten die Daten für den VPN-Tunnelendpunkt:

    Qualifier   Value  
    Domain: ffmuc_welt
    Hostname/Endpoint:   gw04.ext.ffmuc.net
    Port: 40011
    (remote) publickey:   BJHuqV8mXsS9AlAY+x/oi1AsQCm5aMTLOQvLprNZLRo=
    link-local-Adresse:   errechnet sich aus dem nachfolgenden client_public.key
    (client) privatekey:   wird mit Hilfe des binaries wg local generiert und in /etc/wireguard/client_private.key gespeichert.
    (client) publickey:   wird mit Hilfe des binaries wg local generiert und in /etc/wireguard/client_public.key gespeichert.

Wir erzeugen nun im ersten Schritt das Schlüsselpaar (private-/public-key) für unseren VPN-Client.

 # wg genkey | tee /etc/wireguard/client_private.key | wg pubkey | tee /etc/wireguard/client_public.key

Im Konfigurationsverzeichnis von WireGuard finden wir nun beide Schlüssel. Die Dateiberechtigungen passen wir gleich mal noch an, damit nur der User root auf diese zugreifen kann.

 # chmod 600 /etc/wireguard/*.key

Die link-local IPv6-Adresse unseres lokalen VPN-Entpunktes ermitteln wir mit Hilfe folgenden Befehls, nachdem wir den public.key zuvor erfolgreich erstellt hatten.

 # cat /etc/wireguard/client_public.key | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/fe80::02\1:\2ff:fe\3:\4\5/' > /etc/wireguard/link-local

In unserem Konfigurationsbeispiel ergibt sich also aus dem öffentlichen Schlüssel HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU= die link-local-Adresse fe80::02c5:ddff:fee7:823b.

Zusammengefasst haben wir also nunmehr folgende Konfigurationsparameter:

    Qualifier   Value  
    Domain: ffmuc_welt
    Hostname:   gw04.ext.ffmuc.net
    Port: 40011
    (remote) publickey:   BJHuqV8mXsS9AlAY+x/oi1AsQCm5aMTLOQvLprNZLRo=
    lokale link-local-Adresse:   fe80::02c5:ddff:fee7:823b
    (client) privatekey:   WOzTMy6u2Tad4hqBK9jn1KR5aMiu1ZDcD0KLHWlK9nY=
    (client) publickey:   HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU=

WICHTIG: Der private Schlüssel unseres Clients (privatekey) ist natürlich nicht publik zu machen und ist nur hier im Rahmen dieses How2s abgedruckt, damit die weitere Konfiguration leichter zu verstehen ist. Im Relalife-Betrieb hüten wir diesen Schlüssel natürlich wie immer „wie unseren Augapfel“!

Damit nun das Gateway auch VPN-Verbindungen unseres WireGuard-clients akzeptieren kann benötigt dieses natürlich unseren public-key, den wir nun an das Gateway schicken müssen:

 # wget -q -O- --post-data='{"domain": "ffmuc_welt","public_key": "HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU="}' http://broker.ffmuc.net/api/v1/wg/key/exchange
{"Message":"OK"}

Die Bestätigung „Message“:„OK“ zeigt uns, dass der Broker auf der Zielseite, also auf den Gatewasy unseren Pblic-key akzeptiert hat und diesen beim nachfolgenden Tunnelaufbau auch verwenden wird!

Damit nun der Broker vor dem Starten unseres Tunnels auch immer unseren öffentlichen Schlüssel hat, stellen wir nun noch sicher, dass dieser automatisiert vor dem Starten des WireGuard-Daemon einmal zum Broker|Gateway übertragen wird. Hierzu legen wir uns ein systemd-Startscript an.

 # vim /etc/systemd/system/broker.service
/etc/systemd/system/broker.service
# Django : 2020-12-15
[Unit]
# see man systemd.unit
Description=Inform tunnel about our wireguard-public key
Documentation=https://wiki.mailserver.guru/doku.php/centos:ansible:ffmuc-rpb4-ol
Before=wg-quick.target
 
[Service]
# see man systemd.service, systemd.exec
ExecStart=/usr/bin/wget -q -O- --post-data='{"domain": "ffmuc_welt","public_key": "HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU="}' http://broker.ffmuc.net/api/v1/wg/key/exchange
StandardOutput=syslog
StandardError=syslog
 
[Install]
WantedBy=default.target

Damit systemd von unserem neuen Startscript Kenntnis hat, führen wir einen relaod des selbigen durch.

 # systemctl daemon-reload

Zum Starten verwenden wir den bekannten Mechanismus von systemd. Den manuellen Start führen wir mit Hilfe des nachstehenden Befehls aus.

 # systemctl start broker.service

Damit der Daemon beim Neustart entsprechend automatisch gestartet wird, setzen wir den Dienst auf enabled

 # systemctl enable broker.service
Created symlink /etc/systemd/system/default.target.wants/broker.service → /etc/systemd/system/broker.service.

Was unser Client noch benötigt ist eine rudimentäre Konfigurationsdatei für unseren WireGuard-client, die wir nun als nächstes anlegen müssen.

 # vim /etc/wireguard/wg-welt.conf
/etc/wireguard/wg-welt.conf
# Django : 2020-12-15
[Interface]
PrivateKey = WOzTMy6u2Tad4hqBK9jn1KR5aMiu1ZDcD0KLHWlK9nY=
Address = fe80::02c5:ddff:fee7:823b
 
[Peer]
PublicKey = TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g= 
AllowedIPs = fe80::27c:16ff:fec0:6c74
Endpoint = gw04.ext.ffmuc.net:40011
PersistentKeepalive = 2

Als ersten Test starten wir nun den WireGuard-Tunnel und fahren diesen mit Hilfe des Befehls wg-quick up hoch.

 # wg-quick up wg-welt
[#] ip link add wg-welt type wireguard
[#] wg setconf wg-welt /dev/fd/63
[#] ip link set mtu 1412 up dev wg-welt
[#] ip -6 route add fe80::27c:16ff:fec0:6c74/128 dev wg-welt

Den Status unseres WireGuard-clients können wir mit Hilfe der Option show beim Aufruf von wg bzw. nur dur Aufruf von wg ermitteln

 # wg show

oder einfach nur

 # wg

zeigen uns den Status des VPN-Tunnels:

interface: wg-welt
  public key: HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU=
  private key: (hidden)
  listening port: 45032

peer: TszFS3oFRdhsJP3K0VOlklGMGYZy+oFCtlaghXJqW2g=
  endpoint: 5.1.66.3:40011
  allowed ips: fe80::27c:16ff:fec0:6c74/128
  latest handshake: 18 seconds ago
  transfer: 55.46 MiB received, 212 MiB sent
  persistent keepalive: every 25 seconds 

Da sowohl bei transfer wie auch bei received die Werte steigen sobald wie den Status erneut abfragen, können wir im ersten Schritt davon ausgehen, dass der WireGuard-Tunnel steht und auch benutzt werden kann. Wir sollten auch nun den Tunnelenpunkt am Gateway anpingen können. In unserem Konfiguratiosnbeispiel wäre dies demnach die Ziel-IP-Adresse fe80::27c:16ff:fec0:6c74 auf dem Netzwerk-Device wg-welt.

 # ping6 -c4 fe80::27c:16ff:fec0:6c74%wg-welt
PING fe80::27c:16ff:fec0:6c74%wg-welt(fe80::27c:16ff:fec0:6c74%wg-welt) 56 data bytes
64 bytes from fe80::27c:16ff:fec0:6c74%wg-welt: icmp_seq=1 ttl=64 time=29.2 ms
64 bytes from fe80::27c:16ff:fec0:6c74%wg-welt: icmp_seq=2 ttl=64 time=28.9 ms
64 bytes from fe80::27c:16ff:fec0:6c74%wg-welt: icmp_seq=3 ttl=64 time=28.4 ms
64 bytes from fe80::27c:16ff:fec0:6c74%wg-welt: icmp_seq=4 ttl=64 time=28.10 ms

--- fe80::27c:16ff:fec0:6c74%wg-welt ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 28.426/28.884/29.201/0.308 ms

Möchten wir den Tunnel wieder per Hand stoppen, verwenden wir die Option down beim Aufruf des Befehls wg-quick.

 # wg-quick down wg-welt
[#] ip link delete dev wg-welt

Zum Starten und Stoppen verwenden wir aber besser die Mechanismen von systemd. Den manuellen Start führen wir mit Hilfe des nachstehenden Befehls aus.

 # systemctl start wg-quick@wg-welt

Der Stop des WireGuard-Deamon erfolgt entsprechend mit:

 # systemctl stop wg-quick@wg-welt

Den Status des WireGuard-Client-daemon fragen wir mit der Option status ab.

 # systemctl status wg-quick@wg-welt

wg-quick@wg-welt.service - WireGuard via wg-quick(8) for wg/welt
   Loaded: loaded (/lib/systemd/system/wg-quick@.service; disabled; vendor preset: enabled)
   Active: active (running)  since Wed 2020-12-16 18:20:05 GMT; 10s ago
     Docs: man:wg-quick(8)
           man:wg(8)
           https://www.wireguard.com/
           https://www.wireguard.com/quickstart/
           https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
           https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8
  Process: 6664 ExecStart=/usr/bin/wg-quick up wg-welt (code=exited, status=0/SUCCESS)
 Main PID: 6664 (code=exited, status=0/SUCCESS)

Dec 16 18:20:05 rpb4-wg-ol systemd[1]: Starting WireGuard via wg-quick(8) for wg/welt...
Dec 16 18:20:05 rpb4-wg-ol wg-quick[6664]: [#] ip link add wg-welt type wireguard
Dec 16 18:20:05 rpb4-wg-ol wg-quick[6664]: [#] wg setconf wg-welt /dev/fd/63
Dec 16 18:20:05 rpb4-wg-ol wg-quick[6664]: [#] ip link set mtu 1412 up dev wg-welt
Dec 16 18:20:05 rpb4-wg-ol wg-quick[6664]: [#] ip -6 route add fe80::27c:16ff:fec0:6c74/128 dev wg-welt
Dec 16 18:20:05 rpb4-wg-ol systemd[1]: Started WireGuard via wg-quick(8) for wg/welt.

Damit der Daemon beim Neustart entsprechend automatisch gestartet wird, setzen wir den Dienst auf enabled

 # systemctl enable wg-quick@wg-welt
Created symlink /etc/systemd/system/multi-user.target.wants/wg-quick@wg-welt.service → /lib/systemd/system/wg-quick@.service.

WireGuard-Healthcheck

Im Betrieb kann es ab und an passieren, dass der WireGuard-Tunnel zusammenbricht, weil z.B. die Netzwerkverbindung abgerissen ist, oder ein Reboot der Gateways notwendig war. Damit in einem solchen Fall unser Offloader nicht offload geht, müssen wir die Verfügbar- und Nutzbarkeit der Tunnelverbindung in regelmässigen Abständen prüfen und ggf. einschreiten und die Verbindung neu aufbauen.

Da wir das natürlich nicht manuell erledigen wollen und können, legen wir uns ein kleines bash-Script hierzu an.

 # vim /usr/local/bin/checkup
/usr/local/bin/checkup
#!/bin/bash
# Django : 2020-12-15
 
# Check connectivity to supernode
HTTP_STATUS_CODE=(`curl --silent --interface wg-welt --get --ipv6 --connect-timeout 5 --write-out '%{http_code}' --output /dev/null 'http://[fe80::27c:16ff:fec0:6c74]:80'`)
if [ ${HTTP_STATUS_CODE} != "200" ]; then
        logger -t checkuplink "curl --silent --interface wg-welt --get --ipv6 --connect-timeout 5 --write-out '%{http_code}' --output /dev/null 'http://[fe80::27c:16ff:fec0:6c74]:80' faild with HTTP-errorcode: ${HTTP_STATUS_CODE}"
        logger -t checkuplink "... better we restart the wireguard-tunnel!"
        ip link set nomaster bat-welt dev vxlan-mesh &> /dev/null
        ip link del dev mesh-vpn &> /dev/null
        ip link del wg-welt &> /dev/null
        systemctl stop wg-quick@wg-welt
        systemctl restart networking
        logger -t checkuplink "Sending public-key to the broker."
        /usr/bin/wget -q -O- --post-data='{"domain":"ffmuc_welt","public_key":"HjDRCwE4Q7UPAKXrmM4s6VOgMK+HJZCixWTaVC8KiRU="}' http://broker.ffmuc.net/api/v1/wg/key/exchange
        logger -t checkuplink "Starting wireguard-daemon."
        systemctl start wg-quick@wg-welt
        logger -t checkuplink "Starting vxlan-meshing."
        vxlan
else
        #logger -t checkuplink "wiregurad-tunnel is up an running : HTTP-statuscode: ${HTTP_STATUS_CODE}"
fi

Zum Ausführen statten wir das Script mit den x-Rechten aus.

 # chmod +x /usr/local/bin/checkup

Damit das Script nun minütlich ausgeführt wird, legen wir noch einen hierzu benötigten Eintrag in der /etc/crontab an.

 # vim /etc/crontab
/etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
 
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
 
# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
17 *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
 
# Django : 2020-12-15
# check wireguard-connection
* * * * *	root	/usr/local/bin/checkup 2>&1 /dev/null

Sollte der WireGuard-Tunnel nun zusammenbrechen, aus welchen Gründen das auch immer passieren möge, wird die Verbindung automatisch wieder aufgebaut und im syslog des Raspberry entsprechend dokumentiert.

Dec 16 19:40:09 rpb4-wg-ol checkuplink curl --silent --interface wg-welt --get --ipv6 --connect-timeout 5 --write-out '%{http_code}' --output /dev/null 'http://[fe80::27c:16ff:fec0:6c74]:80' faild with HTTP-errorcode: 500
Dec 16 19:40:10 rpb4-wg-ol checkuplink "... better we restart the wireguard-tunnel!"
Dec 16 19:40:12 rpb4-wg-ol checkuplink "Sending public-key to the broker."
Dec 16 19:40:16 rpb4-wg-ol checkuplink "Starting wireguard-daemon."
Dec 16 19:40:18 rpb4-wg-ol checkuplink "Starting vxlan-meshing."

Mesh per vxlan

Hintergründe

WireGuard selbst transportiert lediglich Layer-3-Verbindungen und keine Layer-2-Verbindungen, welches wir aber für das von uns eingesetzte Meshprotokoll, B.A.T.M.A.N.-Advanced batman-adv, benötigen. Hierzu transportieren wir nun durch den WireGuard-Tunnel ein Virtual eXtensible LAN (VXLAN, siehe RFC 7348) und in diesem VXLAN läuft dann batman-adv.

WICHTIG:
Die hierzu nötige vxlan id darf nicht mit der VX-LAN-ID verwechselt werden, die wir ggf. später zum Meshen hinter unserem Offloader anbieten möchten.

Berechnung der mesh vpn vxlan id

Die mesh vpn vxlan id berechnet sich aus dem domain_seed (siehe obige Tabelle).

Um die VXLAN ID aus einem domain_seed zu berechnen haben wir ein kleines Python Skript geschrieben.

get_mesh_vpn_vxlan_id_from_domain_seed.py
import sys
domain_seed = sys.argv[1]
 
def domain_seed_bytes(key, length):
	import hashlib
	ret = ''
	v = ''
	i = 0
 
	while len(ret) < 2 * length:
		i = i + 1
		hash_this = v + key + domain_seed + str(i)
		v = hashlib.md5(hash_this.encode('utf-8')).hexdigest()
		ret = ret + v
 
	return ret[: 2 * length]
 
stuff = int(domain_seed_bytes('gluon-mesh-vpn-vxlan', 3), 16)
print(stuff)

Im Beispiel Vom Segment ffmuc_welt ergibt bei einem domain-seed von 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26, die vxlan id von 4831583:

 $ python3 get_vxlan_id_from_domain_seed.py 1f0b31cecdf980317f453b162ac476ee09cf9997b03e684d4f2f484e75d9cd26
4831583

Konfiguration

Zum Aktivieren der Mesh-Verbindung per vxlan legen wir uns nun ein kleines Bash-Script an.

 # vim /usr/local/bin/vxlan
/usr/local/bin/vxlan
#!/bin/bash
# Django : 2020-12-15
 
# Bring up VXLAN
ip link add mesh-vpn type vxlan id 4831583 local fe80::02c5:ddff:fee7:823b remote fe80::27c:16ff:fec0:6c74 dstport 8472 dev wg-welt
ip link set up dev mesh-vpn
 
# Bind mesh-vpn to BATMAN-Device
/usr/sbin/batctl meshif bat-welt if add mesh-vpn
 
# If we have a BATMAN_V env we need to correct the throughput value now
/usr/sbin/batctl hardif mesh-vpn throughput_override 10000

Damit das Script später direkt ausgeführt werden kann statten wir es noch mit den x-Rechten aus.

 # chmod +x /usr/local/bin/vxlan

Damit das vxlan-Meshing automatisch beim Starten unseres Offloaders gestartet wird, legen wir uns auch hier ein entsprechendes systemd-Startscript an.

 # vim /etc/systemd/system/vxlan.service
/etc/systemd/system/vxlan.service
# Django : 2020-12-15
[Unit]
# see man systemd.unit
Description=Bringing up VXLAN Interface
Documentation=https://dokuwiki.nausch.org/doku.php/linux:ansible:ffmuc-rpb4-ol
After=wg-quick@wg-welt.service
 
[Service]
# see man systemd.service, systemd.exec
Type=oneshot
ExecStart=/usr/local/bin/vxlan
StandardOutput=syslog
StandardError=syslog
 
[Install]
WantedBy=multi-user.target

Wie schon zuvor bei unserem Broker-Informations-systemd-Startscript informieren wir hier nun systemd über unsere neue systemd-Startupdatei.

 # systemctl daemon-reload

Nun können wir das vxlan-Meshing via systemd starten.

 # systemctl start vxlan.service

Für den automatischen Start beim Hochfahren unseres Offloaders aktivieren wir nun noch das entsprechende Script.

 # systemctl enable vxlan.service 
Created symlink /etc/systemd/system/multi-user.target.wants/vxlan.service → /etc/systemd/system/vxlan.service.

ext-respondd

Dieser Teil ist wichtig, da es sein kann dass man gegen die Nutzungsbedingungen seiner Community verstößt, wenn man sich nicht als Knoten im Netz meldet. Außerdem trägt es generell zur Netzhygiene bei, wenn alle Knoten in den Statistiken auftauchen.

Werteermittlung

Wir ermitteln also am einfachsten die Geo-Koordinaten aus der Freifunk München Karte: https://map.ffmuc.net

Bild: Kartenausschnitt der Freifunk München Karte zur Ermittlung der Geo-Koordinaten

 
    Qualifier   Value  
    Breitengrad: 48.198104112
    Längenggrad:   11.798404455
    Kontaktadresse:   hier entlang => https://bit.ly/3ajjQC0
    hostname:   🧠 wiki-demo 🧠
    model:   Raspberry Pi 4B

Installation

Nachdem wir auch auf der Knotenkarte auftauchen wollen, installieren wir auch noch ext-respondd.

 # apt install git python3-netifaces -y
 # git clone https://github.com/freifunkMUC/ext-respondd /opt/ext-respondd/
 # cp /opt/ext-respondd/ext-respondd.service.example /etc/systemd/system/ext-respondd.service
 # systemctl daemon-reload
 # systemctl enable ext-respondd

Konfiguration

Bevor wir den Daemon starten, müssen wir diesen noch die Konfigurationsdateien von ext-respondd entsprechend anpassen.

WICHTIG:
Darauf achten, dass die Daten inklusive dem Segmentnamen korrekt eingetragen werden. Eine Übersicht der Segmentnamen gibt es hier.

Wir tragen demnach folgende Daten ein:

 
    Qualifier   Value  
    Breitengrad: 48.198104112
    Längenggrad:   11.798404455
    Kontaktadresse:   hier entlang => https://bit.ly/3ajjQC0
    hostname:   🧠 wiki-demo 🧠
    model:   Raspberry Pi 4B

 # vim /opt/ext-respondd/alias.json
/opt/ext-respondd/alias.json
{
  "nodeinfo": {
    "hostname": "🧠 wiki-demo 🧠",
    "hardware": {
      "model": "Raspberry Pi 4B"
    },
    "owner": {
      "contact": "hier entlang => https://bit.ly/3ajjQC0"
    },
    "system": {
      "site_code": "ffmuc_welt",
      "role": "client"
    },
    "location": {
    "latitude": 48.198104112,
    "longitude": 11.798404455
    }
  },
  "firstseen": "2020-12-15T12:15:18"
}
 # vim /opt/ext-respondd/config.json
opt/ext-respondd/config.json
{
  "batman": "bat-welt",
  "bridge": "br-welt",
  "mesh-vpn": [ "fastd-welt" ],
  "wan": "eth0",
  "rate_limit": 30,
  "rate_limit_burst": 10
}

Daemon starten

Nun starten wir unseren Daemon ext-respondd.

 # systemctl start ext-respondd.service

Den Status des Daemon fragen wir wie folgt ab.

 # systemctl status ext-respondd.service

ext-respondd.service - ext-respondd (respondd status for servers)
   Loaded: loaded (/etc/systemd/system/ext-respondd.service; enabled; vendor preset: enabled)
   Active: active (running)  since Wed 2020-12-16 20:40:11 GMT; 59s ago
 Main PID: 2184 (python3)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/ext-respondd.service
           └─2184 python3 /opt/ext-respondd/ext-respondd.py

Dec 16 20:40:11 rpb4-wg-ol systemd[1]: Started ext-respondd (respondd status for servers).
Dec 16 20:40:11 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future
Dec 16 20:40:31 rpb4-wg-ol ext-respondd.py[2184]: Warning - option -m was deprecated and will be removed in the future

Nach kurzer Zeit taucht unser Node dann auch auf der Karte auf. Bild: Freifunk München Karte mit eingetragenem neune Node

Konfigurations-/Netzwerkcheck

Nun wollen wir auch prüfen welche Verbindungen unser Offloader hält.

 # ip addr show dev br-welt
6: br-welt: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1280 qdisc noqueue state UP group default qlen 1000
    link/ether dc:a6:32:5c:46:07 brd ff:ff:ff:ff:ff:ff
    inet 10.80.204.2/21 brd 10.80.207.255 scope global dynamic br-welt
       valid_lft 409sec preferred_lft 409sec
    inet6 2001:678:e68:109:dea6:32ff:fe5c:4607/64 scope global dynamic mngtmpaddr 
       valid_lft 7118sec preferred_lft 3518sec
    inet6 fe80::dea6:32ff:fe5c:4607/64 scope link 
       valid_lft forever preferred_lft forever

Wie sehen also, dass unser Node die Adressen 10.80.204.2 sowie 2001:678:e68:109:dea6:32ff:fe5c:4607 hält. Das Gleiche erreichen wir mit Hilfe dieses Befehls:

 # ip -br a | grep br-welt
br-welt          UP             10.80.204.2/21 2001:678:e68:109:dea6:32ff:fe5c:4607/64 fe80::dea6:32ff:fe5c:4607/64

Wir sehen somit die Adressen auf unseren Interfaces aus dem gewählten Segment.

Als nächstes prüfen wir, ob die beiden Interfaces dummy-welt und mesh-vpn aktiv sind.

 # batctl meshif bat-welt if
dummy-welt: active
mesh-vpn: active

Den Neighbor unseres Nodes finden wir wie folgt heraus:

 # batctl meshif bat-welt n
[B.A.T.M.A.N. adv 2020.4, MainIF/MAC: dummy-welt/dc:a6:32:5c:46:07 (bat-welt/dc:a6:32:5c:46:07 BATMAN_V)]
IF             Neighbor              last-seen
8a:5d:44:50:c3:f8    0.220s (       10.0) [  mesh-vpn]

Zu guter Letzt sehen wir uns noch die Gateway-Liste an über bzw. mit denen unser Offloader verbunden ist.

 # batctl meshif bat-welt gwl
[B.A.T.M.A.N. adv 2020.4, MainIF/MAC: dummy-welt/dc:a6:32:5c:46:07 (bat-welt/dc:a6:32:5c:46:07 BATMAN_V)]
  Router            ( throughput) Next Hop          [outgoingIf]  Bandwidth
* f2:00:25:10:00:00 (       10.0) 8a:5d:44:50:c3:f8 [  mesh-vpn]: 1000.0/200.0 MBit
  f2:00:26:10:00:00 (       10.0) 8a:5d:44:50:c3:f8 [  mesh-vpn]: 1000.0/200.0 MBit

Meshing per Kabel

Möchte man per Kabel meshen, so ist dies auf Grund dessen, dass unser Raspberry nur eine Netzwerkschnittstelle hat, im Grunde nur per VLAN realisierbar.

Dazu ist es erforderlich, dass zuerst ein VLAN Interface in /etc/network/interfaces angelegt und an das BATMAN Interface anghängt wird.

 # vim /etc/network/interfaces
/etc/network/interfaces
auto eth0.666
iface eth0.666 inet manual
      post-up /usr/sbin/batctl ra BATMAN_V
      post-up /usr/sbin/batctl meshif bat-welt if add eth0.666

Bitte beachten:
Das Setup funktioniert nur in Umgebungen ohne VXLAN Meshing. Mit VXLAN Meshing braucht es auch noch ein VXLAN Interface. Details hierzu finden sich im Nachgang!

Mesh per vxlan

Für andere Communities benötigt man die entsprechenden VXLAN IDs zum Meshen - also bei Bedarf bei der lokalen Community nachhaken!

Um meshing per Kabel nutzen zu können, muss bei manchen Communities (wie zum Beispiel bei FFMUC) VXLAN benutzt werden.

Bei uns gibt es momentan folgende VXLAN IDs, diese werden von GLUON dynamisch aus dem domain_seed erzeugt.

Domain vxlan id
ffmuc_muc_cty 10758607
ffmuc_muc_nord 15521492
ffmuc_muc_ost 2948862
ffmuc_muc_sued 8599288
ffmuc_muc_west 7318933
ffmuc_uml_nord 5705961
ffmuc_uml_ost 4892713
ffmuc_uml_sued 16544703
ffmuc_uml_west 16677749
ffmuc_gauting 16175732
ffmuc_freising 12937858
ffmuc_welt 16306234
ffmuc_augsburg 10700201

Um die VXLAN ID aus einem domain_seed zu berechnen haben wir ein kleines Python Skript geschrieben.

python3 get_vxlan_id_from_domain_seed.py domain_seed

Die Konfiguration in /etc/network/interfaces sieht so aus, um per VLAN666 VXLAN Meshing für das Segment Welt zu machen. Wie ihr seht, wird dafür die ID aus der obigen Tabelle benötigt.

/etc/network/interfaces
auto eth0.666
iface eth0.666 inet manual
        pre-up /sbin/ip link add vxlan-mesh type vxlan id 16306234 group ff02::15c dstport 4789 port 32768 61000 no udpcsum udp6zerocsumtx udp6zerocsumrx dev eth0.666 || true
        up /sbin/ip link set vxlan-mesh up
        post-up /usr/sbin/batctl ra BATMAN_V
        post-up /usr/sbin/batctl -m bat-welt if add vxlan-mesh
        down ip link set vxlan-mesh down
        post-down ip link del vxlan-mesh || true

Damit das vxlan-mesh Interface auch der bevorzugte Meshingpoint in BATMAN_V wird noch folgendes in die /etc/rc.local hinzufügen.

 # vim /etc/rc.local
/etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
 
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi
 
# Django : 2020-12-15
/usr/sbin/batctl hardif vxlan-mesh throughput_override 1000000
 
exit 0

Zum Aktivieren unserer Konfigurationsänderung fahren wir entweder das Netzwerkiinterface hoch oder starten unseren Offloader einfach einmal neu:

 # systemctl reboot

Wenn alles geklappt hat und ihr bereits einen Router zum Meshen in diesem VLAN habt, sollte danach folgendes in batctl zu sehen sein.

 batctl meshif bat-welt n
[B.A.T.M.A.N. adv 2020.4, MainIF/MAC: dummy-welt/dc:a6:32:5c:46:07 (bat-welt/dc:a6:32:5c:46:07 BATMAN_V)]
IF             Neighbor              last-seen
8e:89:98:45:6c:70    0.380s (        1.0) [vxlan-mesh]
a2:ca:66:65:30:40    0.050s (        1.0) [vxlan-mesh]
9a:9b:66:30:d9:38    0.110s (        1.0) [vxlan-mesh]
6a:b4:d0:73:15:47    0.100s (       10.0) [  mesh-vpn]

Sollte sich kein Client am Mesh VLAN befinden, wird die vxlan-mesh Zeile nicht angezeigt.

Außerdem sollten die Interfaces wie folgt aussehen:

 # batctl -m bat-welt if
dummy-welt: active
vxlan-mesh: active
fastd-welt: active

Leider ist der Link zum Gateway auf der Map nicht zu sehen.

Wifi Clients

Soll der Offloader auch ein WiFI-Clientnetz ausstrahlen, müssen wir hostapd installieren und konfigurieren. Bei der Vergabe der SSID ist zu beachten:

WICHTIG: Das Abändern der zum Segment passenden SSID ist gemäß der Nutzungsbedingungen nicht gestattet!

  • 5. Lokale Zusätze für Freifunk München
    Das Betreiben von Routern in Verbindung mit der Freifunk-München Netzwerkinfrastruktur ist nur gestattet, solange die folgenden zwei Bedingungen erfüllt sind:
    • die SSID des Client-Netzes auf “muenchen.freifunk.net/(Segmentname)” lautet
    • die Mesh-BSSID/SSID denen, der in den offiziellen Firmware-Images veröffentlichten, entspricht

Die Liste der SSIDs findet ihr hier:

Aktuell2) gibt es folgende Segmente:

SSID Segment-Name IPv4-Prefix IPv6-Prefix Wien IPv6-Prefix München
muenchen.freifunk.net/muc_cty ffmuc_muc_cty 10.80.128.0/21 2001:678:e68:100::/64 2001:678:ed0:100::/64
muenchen.freifunk.net/muc_nord ffmuc_muc_nord 10.80.136.0/21 2001:678:e68:101::/64 2001:678:ed0:101::/64
muenchen.freifunk.net/muc_ost ffmuc_muc_ost 10.80.144.0/21 2001:678:e68:102::/64 2001:678:ed0:102::/64
muenchen.freifunk.net/muc_sued ffmuc_muc_sued 10.80.152.0/21 2001:678:e68:103::/64 2001:678:ed0:103::/64
muenchen.freifunk.net/muc_west ffmuc_muc_west 10.80.160.0/21 2001:678:e68:104::/64 2001:678:ed0:104::/64
muenchen.freifunk.net/uml_nord ffmuc_uml_nord 10.80.168.0/21 2001:678:e68:105::/64 2001:678:ed0:105::/64
muenchen.freifunk.net/uml_ost ffmuc_uml_ost 10.80.176.0/21 2001:678:e68:106::/64 2001:678:ed0:106::/64
muenchen.freifunk.net/uml_sued ffmuc_uml_sued 10.80.184.0/21 2001:678:e68:107::/64 2001:678:ed0:107::/64
muenchen.freifunk.net/uml_west ffmuc_uml_west 10.80.192.0/21 2001:678:e68:108::/64 2001:678:ed0:108::/64
muenchen.freifunk.net/welt ffmuc_welt 10.80.200.0/21 2001:678:e68:109::/64 2001:678:ed0:109::/64
muenchen.freifunk.net/gauting ffmuc_gauting 10.80.208.0/21 2001:678:e68:10a::/64 2001:678:ed0:10a::/64
muenchen.freifunk.net/freising ffmuc_freising 10.80.216.0/21 2001:678:e68:10b::/64 2001:678:ed0:10b::/64
muenchen.freifunk.net/augsburg ffmuc_augsburg 10.80.224.0/21 2001:678:e68:10c::/64 2001:678:ed0:10c::/64

Installation

 # apt install hostapd -y
 # echo 'DAEMON_OPTS="-d"' >> /etc/default/hostapd

Konfiguration

Nun legen wir uns eine entsprechende Konfigurationsdatei an.

 # vim /etc/hostapd/hostapd.conf
/etc/hostapd/hostapd.conf
# Django : 2020-12-15
 
ssid=muenchen.freifunk.net/welt
 
country_code=DE
 
interface=wlan0
driver=nl80211
 
macaddr_acl=0
 
logger_syslog=0
logger_syslog_level=4
logger_stdout=-1
logger_stdout_level=0
 
hw_mode=a
wmm_enabled=1
 
# N
ieee80211n=1
require_ht=1
ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]
 
# AC
ieee80211ac=1
require_vht=1
ieee80211d=0
ieee80211h=0
vht_capab=[MAX-AMSDU-3839][SHORT-GI-80]
vht_oper_chwidth=1
channel=36
vht_oper_centr_freq_seg0_idx=42

Anschließend aktivieren und starten wir den hostapd-Daemon.

 # systemctl unmask hostapd
 # systemctl enable hostapd
 # systemctl start hostapd

Das Netzwerkinterface wlan0 müssen wir nun noch in die Bridge packen. Dazu passen wir die /etc/rc.local an.

 # vim /etc/rc.local
/etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
 
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi
# Django : 2020-12-15
sleep 10; /sbin/brctl addif br-welt wlan0
 
exit 0

Am Besten nochmal rebooten um sicher zugehen, dass alles passt, danach einfach mit dem ClientWifi verbinden.

 # systemctl reboot

LAN Clients

Will man LAN Clients versorgen, so erstellt man am einfachsten ein VLAN getaggtes Clientnetz. Dazu erstellt man in der Konfigurationsdatei /etc/network/interfaces die zugehörige Konfiguration. Im folgenden Beispiel nutzen wir die VLAN-ID 333 und definieren zunächst einmal das VLAn-Interface und fügen es anschließend in der vorhandenen Bridge hinzu.

 # vim /etc/network/interfaces
/etc/network/interfaces
auto eth0.333
iface eth0.333 inet manual
 
auto br-welt
iface br-welt inet dhcp
        bridge-ports bat-welt eth0.333

Geschwindigkeiten

Nun zur wichtigsten Frage, welche Performance kann man erwarten.

Hier ein paar Speedtest Ergebnisse:

WiFi Client an VDSL 100:
Bild Speedtest am Mobiltelefon

Kabel Client an VDSL 100:

Kabel Client an 1Gbit/s Glas:

 $ wget -O /dev/null https://speed.hetzner.de/10GB.bin --report-speed=bits
--2019-07-01 09:12:45-- https://speed.hetzner.de/10GB.bin
Resolving speed.hetzner.de (speed.hetzner.de)... 88.198.248.254
Connecting to speed.hetzner.de (speed.hetzner.de)|88.198.248.254|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760000 (9.8G) [application/octet-stream]
Saving to: ‘/dev/null’
/dev/null            2%[===>           ] 288.96M   115Mb/s    eta 14m 34s

Nebenbei gibt es noch die Möglichkeit, Tethering z.B. mit iOS Geräten zu machen. Dazu muss usbmuxd installiert werden. Und in der /etc/network/interfaces ein Interface angelegt werden.

 # apt install usbmuxd libimobiledevice6 -y
 # vim /etc/network/interfaces
/etc/network/interfaces
...
allow-hotplug eth1
iface eth1 inet dhcp
 
...

Am Besten rebootet man den Raspberry Offloader einmal. Danach steckt ihr euer iOS Gerät per USB an während ihr im Hotspot Menü seid und werdet gefragt, ob das PI Zugriff bekommen kann. Das beantwortet ihr mit „Ja“.

Euer PI bezieht sich nun eine IP per DHCP vom iOS Gerät und kann darüber Verbindungen zu den Freifunk Gateways aufbauen.

Und so sieht ein Mobile PI mit iPad als Uplink aus:

Bild: Raspberry 4 Offloader mit Tethering via iOS-Device

OLED Display

Bild: Raspberry Reise-Offloader Bild: Testaufbau des Raspberry mit dem OLED

Im nachfolgenden Beispiel verwenden wir ein OLED Display von AZDelivery mit 128×64 Pixeln. Das schöne an dem Display ist, es funktioniert auch mit 5V und ist damit noch einfacher anzuschließen.

Anschluss/Verkabelung

Angeschlossen wird das Ganze wie folgt:

OLED Pin   GPIO Pin  Notes
Vcc45V
Gnd6Ground
SCL5I2C SCL
SDA3I2C SCA

I2C-Bus aktivieren

Als nächstes aktivieren wir die I2C Schnittstelle und installieren die notwendige Software.

 # echo i2c-bcm2708 >> /etc/modules
 # echo i2c-dev >> /etc/modules
 # apt install python3-dev python3-smbus i2c-tools python3-pil python3-pip python3-setuptools python3-rpi.gpio -y

Bevor wir nun einen Reboot durchführen kontrollieren wir noch, ob in der /boot/config.txt das I2C-Interface auch gestartet wird.

 root@raspberrypi:~# vim /boot/config.txt
...

# Uncomment some or all of these to enable the optional hardware interfaces
# Django : 2020-12-15 - für OLED Display Support aktiviert
# default: # dtparam=i2c_arm=on
dtparam=i2c_arm=on

...

Nun am Besten einmal rebooten.

 # systemctl reboot

Funktionstest

Danach überprüfen wir ob das Display erkannt wird.

 # i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --   

Wie man sieht meldet sich das Display unter der Adresse 3c.

Installation der Software-Bibliotheken

Jetzt installieren wir die notwendige Python Bibliotheken um das Display ansteuern zu können.

 # apt install git fonts-freefont-ttf -y
 # cd /usr/local/src/
 # git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git
 # cd Adafruit_Python_SSD1306
 # python3 setup.py install

Anschließend können wir mit ein paar Beispielen probieren ob das Display korrekt funktioniert.

 # cd examples
 # python3 /usr/local/src/Adafruit_Python_SSD1306/examples/stats.py

Auf dem Display werden ein paar Systemdaten angezeigt:

Bild: Anzeige von Systemdaten auf dem OLED nach Start des Scripts stats.py

Script installieren und anpassen

Nachdem alles alles soweit funktioniert, können wir als nächstes das Bandbreiten Skript installieren, welches auch gleichzeitig die verbundenen BATMAN Clients anzeigt.

Anschließend das Python Skript, welches das Display steuert installieren und anpassen.

 # cd /usr/local/src
 # git clone https://github.com/awlx/raspberry-oled-bandwidth
 # cd raspberry-oled-bandwidth

Nun passen wir in dem Script folgende Variabelen unseren Gegebenheiten an:

  • wifi : Die definierte Schnittstelle unseres WiFi-Devices
  • primary_mac : MAC des Ethernet-Ports unseres Raspberry 4 # ip addr show eth0 | grep link/ether
 # vim /usr/local/src/raspberry-oled-bandwidth/bandwidth.py
/usr/local/src/raspberry-oled-bandwidth/bandwidth.py
#
# Inspired by https://github.com/DarrenBeck/rpi-oled-bandwidth and https://photochirp.com/r-pi/use-raspberry-pi-oled-bandwidth-monitor/
#
# Maintained by awlnx - aw@awlnx.space
#
 
import subprocess
import time
import re
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import math
 
# Adjust to your needs
wifi = 'wlan0'
vpn = 'fastd-welt'
batman = 'bat-welt'
# Django : 2020-12-15
# default: primary_mac = 'dc:a6:32:00:6b:59'
primary_mac = 'dc:a6:32:01:7f:f0'
 
# We assume 100mbit/s max bandwidth
maxRateIn = 10000000
maxRateOut = 10000000
PImaxRateIn = 10000000
PImaxRateOut = 10000000
 
### DO NOT EDIT BELOW THIS POINT ###
 
# Raspberry Pi pin configuration:
# Django : 2020-15-12
# default: RST = 24
RST = 'P9_15'
 
# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
 
# Initialize library.
disp.begin()
 
# Clear display.
disp.clear()
disp.display()
 
# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
 
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
 
font = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 12)
fontsmall = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 10)
fontverysmall = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 8)
fontmedium = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 12)
 
#Display Image
disp.image(image)
disp.display()
 
#Functions
 
def get_network_bytes(interface):
    for line in open('/proc/net/dev', 'r'):
        if interface in line:
            data = line.split('%s:' % interface)[1].split()
            rx_bytes, tx_bytes = (data[0], data[8])
            return (int(rx_bytes), int(tx_bytes))
 
def drawBar (x, barHeight):
    # parameters are x, y, end x, end y
    # draw.rectangle ((x, height - barHeight, x + 10, height -1), outline=255, fill=255)
    draw.rectangle ((x, 32 - barHeight, x + 8, height - 32), outline=255, fill=255)
 
def drawBarLOW (x, barLOWHeight):
        # parameters are x, y, end x, end y
        draw.rectangle ((x, 32 + barLOWHeight, x + 8, height - 32), outline=255, fill=255)
 
def textRate(rate):
    # rate -> raw bitrate
    # Returns: SI formatted bitrate
    if rate == 0:
        return "0B"
    rate = rate * 8
    size_name = (
        "b/s", "kb/s", "mb/s", "gb/s", "tb/s", "pb/s", "eb/s")
    i = int(math.floor(math.log(rate , 1024)))
    p = math.pow(1024, i)
    s = round(rate / p, 1)
    return "%s %s" % (s, size_name[i])
 
lastInBytes = get_network_bytes(vpn)[0];
lastOutBytes = get_network_bytes(vpn)[1];
lastPIInBytes = get_network_bytes(wifi)[0];
lastPIOutBytes = get_network_bytes(wifi)[1];
lastTime = time.time()
 
#timed array vars
timerTime = time.time()
highestSpeedIn = 0
highestSpeedOut = 0
PIhighestSpeedIn = 0
PIhighestSpeedOut = 0
speedArrayIn = []
speedArrayOut = []
PIspeedArrayIn = []
PIspeedArrayOut = []
inMax = 0
outMax = 0
PIinMax = 0
PIoutMax = 0
 
while (1):
    time.sleep(2)
    draw.rectangle((0, 0, width, height), outline=0, fill=0)
 
    now = time.time()
    elapsed = now - lastTime
    lastTime = now
 
    #calculate rates in and out
    inBytes = get_network_bytes(vpn)[0]
    currInBytes = (inBytes - lastInBytes) / elapsed
    lastInBytes = inBytes
 
    outBytes = get_network_bytes(vpn)[1]
    currOutBytes = (outBytes - lastOutBytes) / elapsed
    lastOutBytes = outBytes
 
    PIinBytes = get_network_bytes(wifi)[0]
    currPIInBytes = (PIinBytes - lastPIInBytes) / elapsed
    lastPIInBytes = PIinBytes
 
    PIoutBytes = get_network_bytes(wifi)[1]
    currPIOutBytes = (PIoutBytes - lastPIOutBytes) / elapsed
    lastPIOutBytes = PIoutBytes
 
 
    #max rate last 24 hours calculations
 
    if currInBytes > highestSpeedIn:
        highestSpeedIn = currInBytes
    if currOutBytes > highestSpeedOut:
        highestSpeedOut = currOutBytes
    if currPIInBytes > PIhighestSpeedIn:
        PIhighestSpeedIn = currPIInBytes
    if currPIOutBytes > PIhighestSpeedOut:
        PIhighestSpeedOut = currPIOutBytes
 
    if now > timerTime + 3600:
        print('-----------------------------------------------------------------  time expired')
        timerTime = now
 
        speedArrayIn.append (highestSpeedIn)
        if len (speedArrayIn) > 23:
            del speedArrayIn[0]
        inMax = max(speedArrayIn)
 
        speedArrayOut.append (highestSpeedOut)
        if len (speedArrayOut) > 23:
            del speedArrayOut[0]
        outMax = max(speedArrayOut)
 
        highestSpeedIn = 0
        highestSpeedOut = 0
 
        PIspeedArrayIn.append (PIhighestSpeedIn)
        if len (PIspeedArrayIn) > 23:
            del PIspeedArrayIn[0]
        PIinMax = max(PIspeedArrayIn)
 
        PIspeedArrayOut.append (PIhighestSpeedOut)
        if len (PIspeedArrayOut) > 23:
            del PIspeedArrayOut[0]
        PIoutMax = max(PIspeedArrayOut)
 
        PIhighestSpeedIn = 0
        PIhighestSpeedOut = 0
 
    #adjust these in each loop in case we find a faster speed
    inMax = max(inMax, highestSpeedIn)
    outMax = max(outMax, highestSpeedOut)
    PIinMax = max(PIinMax, PIhighestSpeedIn)
    PIoutMax = max(PIoutMax, PIhighestSpeedOut)
 
    #draw graph
    inHeight = 0.0
    outHeight = 0.0
    PIinHeight = 0.0
    PIoutHeight = 0.0
 
    if currInBytes > 0:
        inHeight = float(currInBytes / maxRateIn) * 32 
 
    if currOutBytes > 0:
        outHeight = float(currOutBytes / maxRateOut) * 32
 
    if currPIInBytes > 0:
        PIinHeight = float(currPIInBytes / PImaxRateIn) * 32
 
    if currPIOutBytes > 0:
        PIoutHeight = float(currPIOutBytes / PImaxRateOut) * 32
 
    drawBar (0, inHeight)
    drawBar (10, PIinHeight)
    drawBarLOW (0, outHeight)
    drawBarLOW (10, PIoutHeight)
    #write rates
    draw.text((26,38), textRate(currInBytes), font=font, fill=255)
    draw.text((26,50), textRate(currOutBytes), font=font, fill=255)
 
    draw.text((81,38), textRate(currPIInBytes), font=font, fill=255)
    draw.text((81,50), textRate(currPIOutBytes), font=font, fill=255)
 
    # Batman Clients
    clients = subprocess.check_output("batctl -m " + batman + " tl | egrep -v '(MainIF|" + primary_mac + ")' | egrep -o '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | wc -l", shell=True).split().pop()
    draw.text((0,48), "Clients", font=fontverysmall, fill=255)
    draw.text((10,55), clients.decode("utf-8"), font=fontverysmall, fill=255)
 
    #max rates
    draw.text((36,0), "VPN", font=fontsmall, fill=255)
    draw.text((26,10), textRate(inMax), font=fontsmall, fill=255)
    draw.text((26,20), textRate(outMax), font=fontsmall, fill=255)
 
    draw.text((90,0), "Wifi", font=fontsmall, fill=255)
    draw.text((81,10), textRate(PIinMax), font=fontsmall, fill=255)
    draw.text((81,20), textRate(PIoutMax), font=fontsmall, fill=255)
 
    disp.image(image)
    disp.display()

Nun können das Script erst einmal manuell anstarten und prüfen ob die gewünschten Informationen richtig angezeigt werden.

 # /usr/bin/python3 /usr/local/src/raspberry-oled-bandwidth/bandwidth.py

Bild: OLED mit Anzeige der Verbindungsdaten

Um nun nicht jedes mal beim Starten des Offloaders/Gateways manuell starten müssen, legen wir uns noch ein systemd-Script an.

 # vim /etc/systemd/system/oled-bandwidth.service
/etc/systemd/system/oled-bandwidth.service
# Django : 2020-12-15
[Unit]
After=network.target
 
[Service]
ExecStart=/usr/bin/python3 /usr/local/src/raspberry-oled-bandwidth/bandwidth.py
 
[Install]
WantedBy=default.target

Anschließend informieren wir unser System über unser definiertes Daemon-Startscript und aktivieren dies auch gleich noch.

 # systemctl daemon-reload
 # systemctl enable oled-bandwidth.service

Zu guter Letzt rebooten wir nun unseren Rechner.

 # systemctl reboot

Bild: OLED in Betrieb

1)
Stand: Dezember 2020
2)
Stand Ende Oktober 2021