Roadwarrior Setup für Notebook: LAN/WLAN/WWAN/VPN mit dhcpv4, slaac, dhcpv6

Rosendoktor

Well-Known Member
Hallo,

ich habe mir vor ein paar Wochen FreeBSD 10.3 auf ein IBM/Lenovo x61s Notebook installiert, auf dem ausserdem noch Debian GNU/Linux 8, Windows 10 und OSX 10.6.8 laufen.

Das Notebook ist oft unterwegs und wechselt on-the-fly zwischen verschiedenen WLAN Netzwerken, mal wird auch ein LAN Kabel angesteckt oder eine Mobilfunkverbindung aufgebaut über das interne UMTS Modem. Dazu kommen bei Bedarf noch VPN Verbindungen. Also ein klassisches Roadwarrior Gerät.

Nun habe ich bald festgestellt, dass es bei FreeBSD (im Gegensatz zu den anderen drei Systemen) an allen Ecken und Enden gewaltig hapert. Dass es keinerlei grafische Statusanzeige und Konfigurationsmöglichkeit des Netzwerks gibt ist weniger das Problem, aber auch wenn man Verbindungen am Terminal auf- und abbaut gibt es manchmal ziemlich Durcheinander.

* dhcpv6 scheint in der Standardkonfiguration überhaupt nicht unterstützt zu werden.

* Baut man LAN oder WLAN Verbindungen mittels "service netif stop <interface>" ab, dann wird die /etc/resolv.conf nicht bereinigt. Zumindest die über SLAAC und dhcpv6 bezogenen Nameserver bleiben eingetragen.

* Wird das Netzwerkkabel einer LAN Verbindung gezogen, oder schaltet man bei WLAN den AP aus oder entfernt sich aus dem Empfangsbereich eines AP, so bleiben dauerhaft die Netzwerkadressen und Nameserver des Interfaces erhalten.

* Bei einer Einwahlverbindung über Modem, z.B. über das UMTS Modem, wird die /etc/resolv.conf gnadenlos überschrieben statt sauber über das resolvconf Skript zu gehen. Weiterhin wird beim Abbau die ipv4 Defaultroute gelöscht, was eine gegebenenfalls parallel bestehende LAN oder WLAN Verbindung unbrauchbar macht.

* ... (mehr fällt mir gerade nicht mehr ein)

Also habe ich mich daran gemacht, die Netzwerkkonfiguration mal zu analysieren und meinen Bedürfnissen entsprechend anzupassen.

Die Ziele dabei im Einzelnen:

* Sauberer Auf- und Abbau aller Verbindungen inklusive fliegendem Wechsel zwischen LAN/WLAN/Modem und darüber laufender VPN Verbindungen.

* Volle ipv6 Unterstützung inklusive dhcpv6 auf allen Verbindungen.

* Sauberes Management der /etc/resolv.conf, NUR und AUSSCHLIESSLICH über das resolvconf Skript, um auch dessen Regeln betreffend Priorität der Interfaces zu nutzen (soll heissen, über VPN bezogene Nameserver stehen immer VOR den über die darunterliegende physikalische Verbindung bezogenen in der /etc/resolv.conf).

* Mehrfachverbindungen (gewollt oder versehentlich) sollen sauber gemanaged werden ohne dass dabei Chaos entsteht.

Dazu kommen noch meine recht speziellen Anforderungen bezüglich der Nameserver:

* Es soll grundsätzlich der lokale, DNSSEC validierende Unbound verwendet werden.

* Ausser in bekannten, vertrauenswürdigen Netzen, z.B. dem WLAN Zuhause oder am Arbeitsplatz, oder bei VPN Verbindungen.

* Der lokale Unbound soll explizit NICHT über irgendwelche Forwarder gehen, sondern die Auflösung selbst machen, also direkt über die Rootserver gehen.

Das Ganze habe ich über eine ganze Reihe von Skripten realisiert, die entweder über devd Regeln getriggert oder irgendwo im Verbindungsauf- und -abbau eingehängt werden. Leider musste ich auch Systemskripte wie das /sbin/resolvconf Skript dazu leicht anpassen.

Mich würde interessieren, ob sowas ähnliches schon mal jemand gemacht hat, und wenn ja, wie, und ob ich als Anfänger hier nicht irgendwas ganz einfaches übersehen und es letztlich viel zu kompliziert gemacht habe.


Hier dann mal die Dokumentation wie ich es gemacht habe. Ich hoffe es ist soweit vollständig und nicht zu chaotisch, Dokumentation ist nicht so meine Stärke. Manche Codefagmente sind auch, nun ja, nicht so schön... ;)

Anmerkung: em0 ist bei mir das LAN Interface, wlan0 das WLAN Interface.

1. dhcpv6

Als dhcpv6 Client kommt der wide-dhcpv6-client zum Einsatz. Der hat allerlei Probleme damit wenn Interfaces noch nicht verbunden sind oder Verbindungen abgebaut werden, die einzige saubere Möglichkeit die ich gefunden habe war, den dhcp6c nach dem Start einer Verbindung zu starten und vor dem Abbau einer Verbindung zu beenden. Dazu habe ich die LINK_UP Regeln in devd.conf angepasst, eine eigene Regel für LINK_DOWN sowie ein neues Skript angelegt. In der /etc/rc.conf muss der Client deaktiviert werden.

/etc/rc.conf
Code:
...
dhcp6c_enable="NO"
...

/etc/devd.conf
Code:
#notify 0 {
#       match "system"          "IFNET";
#       match "type"            "LINK_UP";
#       media-type              "ethernet";
#       action "/etc/rc.d/dhclient quietstart $subsystem";
#};
notify 0 {
        match "system"          "IFNET";
        match "type"            "LINK_UP";
        media-type              "ethernet";
        action "/usr/local/sbin/link_up $subsystem";
};

#notify 0 {
#       match "system"          "IFNET";
#       match "type"            "LINK_UP";
#       media-type              "802.11";
#       action "/etc/rc.d/dhclient quietstart $subsystem";
#};
notify 0 {
        match "system"          "IFNET";
        match "type"            "LINK_UP";
        media-type              "802.11";
        action "/usr/local/sbin/link_up $subsystem";
};

Hier werden die auskommentierten Zeilen durch die jeweils direkt folgenden ersetzt. Im wesentlichen also wird ein neues Skript aufgerufen statt der alten action Regel.

/usr/local/sbin/link_up
Code:
#!/bin/sh

/etc/rc.d/dhclient quietstart $1 || true
/usr/local/sbin/dhcp6c -c /usr/local/etc/dhcp6c.$1.conf -p /var/run/dhcp6c.$1.pid $1 || true

exit 0

Die action regel für den dhcpv4 Client wandert ins Skript, dazu kommt der Aufruf für den Start des dhcp6c Clients, jeweils mit eigener Instanz pro Interface mit eigenem pid File.

Die Konfigurationsdateien für den dhcp6c Client sind recht simpel:

/usr/local/etc/dhcp6c.em0.conf
Code:
id-assoc na 0 {
};

interface em0 {
        send ia-na 0;
        request domain-name-servers,domain-name;
        script "/usr/local/etc/dhcp6c-script-em0";
};

/usr/local/etc/dhcp6c.wlan0.conf
Code:
id-assoc na 1 {
};

interface wlan0 {
        send ia-na 1;
        request domain-name-servers,domain-name;
        script "/usr/local/etc/dhcp6c-script-wlan0";
};

Zu den hier eingetragenen Skripten komme ich später, es geht dabei um die Nameserver.

Der Verbindungsabbau erwies sich allerdings auch als etwas problematisch, da der dhcp6c Client sich nicht beendet und nur mit kill -9 abgeschossen werden kann. Um das in den Griff zu bekommen bedurfte es einer neuen devd Regel:

/etc/devd/interface.conf
Code:
notify 0 {
       match "system"           "IFNET";
       match "subsystem"        "(wlan0|em0)";
       match "type"             "LINK_DOWN";
       action "/usr/local/sbin/link_down $subsystem";
};

Hier wird beim Verbindungsabbau ein weiteres, neues Skript aufgerufen:

/usr/local/sbin/link_down
Code:
#!/bin/sh

if [ -f /var/run/dhcp6c.$1.pid ]; then
    kill `cat /var/run/dhcp6c.$1.pid` || true
fi

exit 0

Hier wird der dhcp6c Client sauber beendet.

So, damit funktioniert die Interfacekonfiguration über dhcpv6 für LAN und WLAN.


2. Weitere Aufräumarbeiten beim Verbindungsabbau von LAN und WLAN

Wie anfangs bereits erwähnt hatte ich beobachtet, dass beim Verbindungsabbau bzw. -abriss von LAN und WLAN Verbindungen nicht sauber aufgeräumt wird. Insbesondere beim Abziehen des LAN Kabels und beim Verlust einer WLAN Verbindung blieb reichlich Müll übrig. Um das zu bereinigen habe ich das link_down skript ein wenig erweitert:

/usr/local/sbin/link_down
Code:
#!/bin/sh

if [ -f /var/run/dhcp6c.$1.pid ]; then
    kill `cat /var/run/dhcp6c.$1.pid` || true
fi

resolvconf -d $1 || true
resolvconf -d $1:slaac || true
resolvconf -d $1:dhcpv6 || true

e=`ifconfig $1 | grep "media: Ethernet" | wc -l` || true
w=`ifconfig $1 | grep "media: IEEE 802.11" | wc -l` || true
if [ $e -eq 1 ] || [ $w -eq 1 ]; then
    ifconfig $1 0.0.0.0 || true
    l=`ifconfig $1 | grep inet6 | grep -v "inet6 fe80" | awk '{print $2}'` || true
    for i in $l; do
        ifconfig $1 inet6 $i delete || true
    done
fi

exit 0

Hier werden also nach dem Beenden des dhcpv6 Clients alle resolvconf Einträge des betreffenden Interfaces gelöscht (und damit die /etc/resolv.conf bereinigt), ausserdem werden alle noch vorhandenen ip Adressen des LAN oder WLAN Interfaces entfernt (ausser der link local Adresse).

Leider bleibt ein Problem übrig das ich momentan nicht lösen kann: Die devd Regel für LINK_DOWN wird beim LAN Interface nach einem Aufruf von "service netif stop em0" nicht direkt nach dem Verbindungsabbau aufgerufen, sondern erst wenn man beliebig viel später das Interface mit "service netif start em0" wieder aktiviert. Solange bleiben die ip Adressen und die resolv.conf Einträge erhalten, man muss sie manuell entfernen. Beim WLAN Interface habe ich dieses Problem nicht, die devd Regel wird hier sofort korrekt ausgeführt.


3. Modemverbindungen

Wie bereits erwähnt wir bei Modemverbindungen per user ppp die /etc/resolv.conf knallhart überschrieben. Das lässt sich recht leicht ändern indem man zum einen die ppp.conf anpasst um das Überschreiben zu verhindern:

/etc/ppp/ppp.conf
Code:
Provider:
 ...
 disable dns            # do NOT set dns servers in resolv.conf
 ...

Zum Zweiten braucht man ppp.linkup und ppp.linkdown Konfigurationen, um die Nameserverkonfiguration selbst zu managen:

/etc/ppp/ppp.linkup
Code:
Provider:
 !bg /etc/ppp/if_up INTERFACE DNS0 DNS1

/etc/ppp/ppp.linkdown
Code:
Provider:
 !bg /etc/ppp/if_down INTERFACE

Hier werden also Skripten aufgerufen, in denen die Nameserver über das resolvconf Skript gesetzt bzw. gelöscht werden:

/etc/ppp/if_up
Code:
#!/bin/sh
echo "nameserver 127.0.0.1" | /sbin/resolvconf -a $1

/etc/ppp/if_down
Code:
#!/bin/sh
/sbin/resolvconf -d $1

Im /etc/ppp/if_up Skript setze ich hier den lokalen Resolver, will man die vom Provider übermittelten Nameserver verwenden so sind deren Adressen in den Parametern $2 und $3 zu finden.

4. VPN Verbindungen

Ich verwende hier momentan nur OpenVPN, andere VPN Dienste (StrongSWAN) habe ich in FreeBSD noch nicht sauber zum Laufen bekommen.

OpenVPN erweist sich hier als völlig unproblematisch, es setzt und löscht ipv4 und ipv6 Adressen, Routen und Nameserver (über resolvconf) absolut sauber.

Lediglich bezüglich der Reihenfolge der Nameserver in /etc/resolv.conf muss man etwas tricksen, das aber auch nur wenn die VPN Verbindung über eine Modemverbindung (UMTS) läuft. Der user ppp verwendet nämlich wie OpenVPN ein tun Interface. Das resolvconf Skript bevorzugt nun Nameserver, die einem tun Interface zugeordnet sind, das funktioniert aber nur zuverlässig, wenn die physikalische Verbindung über LAN oder WLAN läuft und nicht auch über ein tun Interface. Damit das funktioniert muss man z.B. dafür sorgen, dass ppp IMMER tun0 benutzt (-unit tun0), und openvpn IMMER tun1 (--dev tun1), und im resolvconf Skript die dynamic_order anpassen:

/sbin/resolvconf
Code:
#local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
local_nameservers=""

#dynamic_order="tap[0-9]* tun[0-9]* ng[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"
dynamic_order="tap[0-9]* tun1 tun[0-9]* ng[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"

Dabei habe ich auch gleich die Sonderbehandlung der lokalen Nameserver rausgeschmissen, sonst werden bei Vorhandensein eines lokalen Nameservers alle anderen nicht in die /etc/resolv.conf eingetragen. Und genau dies will ich ja bei VPN Verbindungen (ppp=lokal, VPN=remote).

5. Lokaler Nameserver und die /etc/resolv.conf

Hier wird's jetzt etwas speziell. Wie oben erwähnt möchte ich grundsätzlich den lokalen Unbound verwenden, jedoch nicht in bekannten Netzwerken und bei VPN Verbindungen. Was VPN angeht ist dies in Punkt 4. bereits geklärt, für Modemverbindungen in Punkt 3. Für dhcpv4, slaac und dhcpv6 bei LAN und WLAN Verbindungen müssen dazu weitere Massnahmen getroffen werden.

Im Grunde läuft es darauf hinaus, Skripte einzuhängen die beim Verbindungsaufbau die übermittelten Nameserver überprüfen, bekannte, vertrauenswürdige Nameserver durchlassen und unbekannte durch 127.0.0.1 bzw. ::1 ersetzen.

Zunächst muss aber die durch das local-unbound-setup erzeugte resolvconf.conf geändert werden, damit resolvconf wieder die /etc/resolv.conf behandelt und nicht die forward.conf:

/etc/resolvconf.conf
Code:
# This file was generated by local-unbound-setup.
# Modifications will be overwritten.
#resolv_conf="/dev/null" # prevent updating /etc/resolv.conf
#unbound_conf="/var/unbound/forward.conf"
#unbound_pid="/var/run/local_unbound.pid"
#unbound_service="local_unbound"
#unbound_restart="service local_unbound reload"

Alles auskommentieren also.

Für den ipv4 dhclient braucht man dann nur ein Skript anzulegen:

/etc/dhclient-enter-hooks
Code:
#!/bin/sh

ndns=""

case $reason in
BOUND|RENEW|REBIND|REBOOT)
        for nameserver in $new_domain_name_servers; do
            if [ "$nameserver" == "88.123.123.123" ]; then
           ndns="${ndns}$nameserver "
            else
           ndns="${ndns}127.0.0.1 "
            fi
        done
        new_domain_name_servers=$ndns
        ;;
esac

Fertig. Das Skript wird, wenn vorhanden, automatisch aufgerufen und ersetzt alle ipv4 Nameserver ausser 88.123.123.123 durch 127.0.0.1

Für dhcpv6 kommen folgende Skripten zum Einsatz, die in den dhcp6c.<interface>.conf oben bereits eingetragen sind:

/usr/local/etc/dhcp6c-script-em0
Code:
#!/bin/sh
# dhcp6c-script for Debian/Ubuntu. J�©r�©mie Corbier, April, 2006.
# resolvconf support by Mattias Guns, May 2006.

RESOLVCONF="/sbin/resolvconf"
INTERFACE="em0:dhcpv6"

ndns=""

if [ -n "$new_domain_name_servers" ]; then
    for nameserver in $new_domain_name_servers; do
        echo "$nameserver" | grep -q "2001:abc:abc"
        if [ $? -eq 0 ]; then
            ndns="${ndns}$nameserver "
        else
            echo "$nameserver" | grep -q "2001:abc:def:123::1"
            if [ $? -eq 0 ]; then
                ndns="${ndns}$nameserver "
            else
                ndns="${ndns}::1 "
            fi        
        fi
    done
fi

new_domain_name_servers=$ndns

if [ -n "$new_domain_name" -o -n "$new_domain_name_servers" ]; then
    old_resolv_conf=/etc/resolv.conf
    new_resolv_conf=/etc/resolv.conf.dhcp6c-new
    rm -f $new_resolv_conf
    if [ -n "$new_domain_name" ]; then
        echo search $new_domain_name | sed 's/\.$//' >> $new_resolv_conf
    fi
    if [ -n "$new_domain_name_servers" ]; then
        for nameserver in $new_domain_name_servers; do
            # No need to add an already existing nameserver
            #res=$(grep "nameserver $nameserver" $old_resolv_conf)
            #if [ -z "$res" ]; then
                echo nameserver $nameserver >> $new_resolv_conf
            #fi
        done
    fi

    # Use resolvconf if available
    if [ -x "$RESOLVCONF" ]; then
        cat $new_resolv_conf | $RESOLVCONF -a $INTERFACE
    fi
fi

exit 0

Das gleiche in /usr/local/etc/dhcp6c-script-wlan0. Das Skript selbst ist aus Debian 8, lediglich der Teil welcher die Nameserver ersetzt ist von mir.


Auch für SLAAC kann man Hooks definieren, hierzu muss rtsold installiert sein:

/etc/rtsold-script
Code:
#!/bin/sh

resolvorig="$(cat)"

resolv=`echo "$resolvorig" | awk '{if ($1=="nameserver" && $2~/\./ && $2!="88.123.123.123") print "nameserver 127.0.0.1"; else print}' | awk '{if ($1=="nameserver" && $2~/:/ && !($2~/2001:abc:def:123/ || $2~/2001:abc:def/)) print "nameserver ::1"; else print}'`

echo "$resolv" | /sbin/resolvconf $@

(wieso ich hier ipv4 behandle frage ich mich gerade selbst)...

Damit das Skript auch aufgerufen wird, muss man leider das /etc/rc.d/rtsold Skript anpassen:

Code:
...
command_args="-m -R /etc/rtsold-script"
...


So, das war's soweit. Bis auf die noch ungelösten Probleme mit der devd Regel beim LAN Verbindungsabbau und der gelöschten Defaultroute beim Abbau von Modemverbindungen kann man nun so wild wie man will zwischen LAN, WLAN, Modem hin- und herwechseln, und wenn man will noch VPN nutzen.

Grüsse,

Robert
 
Zurück
Oben