Leiten Sie den gesamten Datenverkehr nur für einen bestimmten Netzwerk-Namespace über OpenVPN

16

Ich versuche, ein VPN (unter Verwendung von OpenVPN) so einzurichten, dass der gesamte Datenverkehr und nur der Datenverkehr zu / von bestimmten Prozessen über das VPN geleitet wird. Andere Prozesse sollten das physische Gerät weiterhin direkt verwenden. Ich verstehe, dass dies unter Linux mit Netzwerk-Namespaces möglich ist.

Wenn ich OpenVPN normal verwende (dh den gesamten Datenverkehr vom Client über das VPN leite), funktioniert dies einwandfrei. Konkret starte ich OpenVPN so:

# openvpn --config destination.ovpn --auth-user-pass credentials.txt

(Eine überarbeitete Version von destination.ovpn befindet sich am Ende dieser Frage.)

Ich stecke beim nächsten Schritt fest und schreibe Skripte, die das Tunnelgerät auf Namespaces beschränken. Ich habe versucht:

  1. Setzen Sie das Tunnelgerät direkt in den Namespace mit

    # ip netns add tns0
    # ip link set dev tun0 netns tns0
    # ip netns exec tns0 ( ... commands to bring up tun0 as usual ... )
    

    Diese Befehle werden erfolgreich ausgeführt, aber der im Namespace (z. B. mit ip netns exec tns0 traceroute -n 8.8.8.8) generierte Datenverkehr fällt in ein Schwarzes Loch.

  2. Unter der Annahme, dass " Sie einem Netzwerk-Namespace nur virtuelle Ethernet (veth) -Schnittstellen zuweisen können " (was, falls zutreffend, die diesjährige Auszeichnung für die lächerlichste unnötige API-Einschränkung erhält), erstellen Sie ein veth-Paar und eine Brücke, und Setzen Sie ein Ende des Veth-Paares in den Namespace. Das reicht nicht einmal, um den Verkehr auf den Boden fallen zu lassen: Ich kann den Tunnel nicht in die Brücke stecken! [EDIT: Dies scheint daran zu liegen, dass nur Zapfgeräte in Brücken gesteckt werden können. Im Gegensatz zu der Unfähigkeit, beliebige Geräte in einen Netzwerk-Namespace einzubinden, ist dies tatsächlich sinnvoll, da Bridges ein Ethernet-Layer-Konzept darstellen. Leider unterstützt mein VPN-Anbieter OpenVPN im Tap-Modus nicht, daher benötige ich eine Problemumgehung.]

    # ip addr add dev tun0 local 0.0.0.0/0 scope link
    # ip link set tun0 up
    # ip link add name teo0 type veth peer name tei0
    # ip link set teo0 up
    # brctl addbr tbr0
    # brctl addif tbr0 teo0
    # brctl addif tbr0 tun0
    can't add tun0 to bridge tbr0: Invalid argument
    

Die Skripte am Ende dieser Frage beziehen sich auf den veth-Ansatz. Die Skripte für die direkte Vorgehensweise finden Sie im Bearbeitungsverlauf. Variablen in den Skripten, die offenbar verwendet werden, ohne sie zuerst openvpnfestzulegen, werden vom Programm in der Umgebung festgelegt - ja, es ist schlampig und verwendet Namen in Kleinbuchstaben.

Bitte geben Sie konkrete Ratschläge, wie dies funktioniert. Mir ist schmerzlich bewusst, dass ich hier per Frachtkult programmiere - hat jemand eine umfassende Dokumentation für dieses Zeug geschrieben? Ich kann keine finden - daher wird auch eine allgemeine Codeüberprüfung der Skripts geschätzt.

Falls es darauf ankommt:

# uname -srvm
Linux 3.14.5-x86_64-linode42 #1 SMP Thu Jun 5 15:22:13 EDT 2014 x86_64
# openvpn --version | head -1
OpenVPN 2.3.2 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [eurephia] [MH] [IPv6] built on Mar 17 2014
# ip -V
ip utility, iproute2-ss140804
# brctl --version
bridge-utils, 1.5

Der Kernel wurde von meinem virtuellen Hosting-Anbieter ( Linode ) erstellt und enthält, obwohl er mit kompiliert wurde CONFIG_MODULES=y, keine tatsächlichen Module. Die einzige CONFIG_*Variable, die mlaut festgelegt /proc/config.gzwurde CONFIG_XEN_TMEM, ist, und ich habe dieses Modul tatsächlich nicht (der Kernel ist außerhalb meines Dateisystems gespeichert; /lib/modulesist leer und /proc/moduleszeigt an, dass es nicht irgendwie magisch geladen wurde). Auszüge aus /proc/config.gzauf Anfrage zur Verfügung gestellt, aber ich möchte nicht die ganze Sache hier einfügen.

netns-up.sh

#! /bin/sh

mask2cidr () {
    local nbits dec
    nbits=0
    for dec in $(echo $1 | sed 's/\./ /g') ; do
        case "$dec" in
            (255) nbits=$(($nbits + 8)) ;;
            (254) nbits=$(($nbits + 7)) ;;
            (252) nbits=$(($nbits + 6)) ;;
            (248) nbits=$(($nbits + 5)) ;;
            (240) nbits=$(($nbits + 4)) ;;
            (224) nbits=$(($nbits + 3)) ;;
            (192) nbits=$(($nbits + 2)) ;;
            (128) nbits=$(($nbits + 1)) ;;
            (0)   ;;
            (*) echo "Error: $dec is not a valid netmask component" >&2
                exit 1
                ;;
        esac
    done
    echo "$nbits"
}

mask2network () {
    local host mask h m result
    host="$1."
    mask="$2."
    result=""
    while [ -n "$host" ]; do
        h="${host%%.*}"
        m="${mask%%.*}"
        host="${host#*.}"
        mask="${mask#*.}"
        result="$result.$(($h & $m))"
    done
    echo "${result#.}"
}

maybe_config_dns () {
    local n option servers
    n=1
    servers=""
    while [ $n -lt 100 ]; do
       eval option="\$foreign_option_$n"
       [ -n "$option" ] || break
       case "$option" in
           (*DNS*)
               set -- $option
               servers="$servers
nameserver $3"
               ;;
           (*) ;;
       esac
       n=$(($n + 1))
    done
    if [ -n "$servers" ]; then
        cat > /etc/netns/$tun_netns/resolv.conf <<EOF
# name servers for $tun_netns
$servers
EOF
    fi
}

config_inside_netns () {
    local ifconfig_cidr ifconfig_network

    ifconfig_cidr=$(mask2cidr $ifconfig_netmask)
    ifconfig_network=$(mask2network $ifconfig_local $ifconfig_netmask)

    ip link set dev lo up

    ip addr add dev $tun_vethI \
        local $ifconfig_local/$ifconfig_cidr \
        broadcast $ifconfig_broadcast \
        scope link
    ip route add default via $route_vpn_gateway dev $tun_vethI
    ip link set dev $tun_vethI mtu $tun_mtu up
}

PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

set -ex

# For no good reason, we can't just put the tunnel device in the
# subsidiary namespace; we have to create a "virtual Ethernet"
# device pair, put one of its ends in the subsidiary namespace,
# and put the other end in a "bridge" with the tunnel device.

tun_tundv=$dev
tun_netns=tns${dev#tun}
tun_bridg=tbr${dev#tun}
tun_vethI=tei${dev#tun}
tun_vethO=teo${dev#tun}

case "$tun_netns" in
     (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;;
     (*) exit 1;;
esac

if [ $# -eq 1 ] && [ $1 = "INSIDE_NETNS" ]; then
    [ $(ip netns identify $$) = $tun_netns ] || exit 1
    config_inside_netns
else

    trap "rm -rf /etc/netns/$tun_netns ||:
          ip netns del $tun_netns      ||:
          ip link del $tun_vethO       ||:
          ip link set $tun_tundv down  ||:
          brctl delbr $tun_bridg       ||:
         " 0

    mkdir /etc/netns/$tun_netns
    maybe_config_dns

    ip addr add dev $tun_tundv local 0.0.0.0/0 scope link
    ip link set $tun_tundv mtu $tun_mtu up

    ip link add name $tun_vethO type veth peer name $tun_vethI
    ip link set $tun_vethO mtu $tun_mtu up

    brctl addbr $tun_bridg
    brctl setfd $tun_bridg 0
    #brctl sethello $tun_bridg 0
    brctl stp $tun_bridg off

    brctl addif $tun_bridg $tun_vethO
    brctl addif $tun_bridg $tun_tundv
    ip link set $tun_bridg up

    ip netns add $tun_netns
    ip link set dev $tun_vethI netns $tun_netns
    ip netns exec $tun_netns $0 INSIDE_NETNS

    trap "" 0
fi

netns-down.sh

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

set -ex

tun_netns=tns${dev#tun}
tun_bridg=tbr${dev#tun}

case "$tun_netns" in
     (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;;
     (*) exit 1;;
esac

[ -d /etc/netns/$tun_netns ] || exit 1

pids=$(ip netns pids $tun_netns)
if [ -n "$pids" ]; then
    kill $pids
    sleep 5
    pids=$(ip netns pids $tun_netns)
    if [ -n "$pids" ]; then
        kill -9 $pids
    fi
fi

# this automatically cleans up the the routes and the veth device pair
ip netns delete "$tun_netns"
rm -rf /etc/netns/$tun_netns

# the bridge and the tunnel device must be torn down separately
ip link set $dev down
brctl delbr $tun_bridg

destination.ovpn

client
auth-user-pass
ping 5
dev tun
resolv-retry infinite
nobind
persist-key
persist-tun
ns-cert-type server
verb 3
route-metric 1
proto tcp
ping-exit 90
remote [REDACTED]
<ca>
[REDACTED]
</ca>
<cert>
[REDACTED]
</cert>
<key>
[REDACTED]
</key>
zwol
quelle
Beginnen wir mit dem Offensichtlichen: Werden veth-Geräte unterstützt? sind die kernel module (veth) geladen?
Gegenmodus
@countermode grep veth /proc/moduleslistet nichts auf, aber ich weiß nicht, ob das schlüssig ist. Bei Linode-Instanzen ist in der Betriebssystempartition kein Kernel installiert, daher bin ich mir nicht sicher, ob ich trotzdem ein fehlendes Modul laden könnte.
zwol
Hat lsmodproduzieren überhaupt keine Ausgabe? Gibt es ein Verzeichnis /lib/modules?
Gegenmodus
lsmod: command not found. Es gibt ein /lib/modules, aber es enthält keine Module , nur eine Reihe von Kernel-Verzeichnissen, die leere modules.depDateien enthalten. Ich werde in Linode-spezifischer Hilfe stöbern und herausfinden, ob es so sein soll.
zwol
hmm ... sehr merkwürdig. Ich kenne Linode nicht, aber für mich sieht es so aus, als ob veth-Geräte nicht unterstützt werden.
Gegenmodus

Antworten:

9

Sie können die OpenVPN-Verknüpfung in einem Namespace starten und dann jeden Befehl ausführen, den Sie für diese OpenVPN-Verknüpfung im Namespace verwenden möchten. Details dazu (nicht meine Arbeit) hier:

http://www.naju.se/articles/openvpn-netns.html

Ich habe es versucht und es funktioniert; Die Idee ist, ein benutzerdefiniertes Skript bereitzustellen, um die Up- und Route-Up-Phasen der OpenVPN-Verbindung in einem bestimmten Namespace anstatt in einem globalen auszuführen. Ich zitiere aus dem obigen Link für den Fall, dass es in Zukunft offline geht:

Erstellen Sie zunächst ein --up-Skript für OpenVPN. Dieses Skript erstellt die VPN-Tunnelschnittstelle in einem Netzwerk-Namespace namens vpn anstelle des Standard-Namespaces.

$ cat > netns-up << EOF
#!/bin/sh
case $script_type in
        up)
                ip netns add vpn
                ip netns exec vpn ip link set dev lo up
                mkdir -p /etc/netns/vpn
                echo "nameserver 8.8.8.8" > /etc/netns/vpn/resolv.conf
                ip link set dev "$1" up netns vpn mtu "$2"
                ip netns exec vpn ip addr add dev "$1" \
                        "$4/${ifconfig_netmask:-30}" \
                        ${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"}
                test -n "$ifconfig_ipv6_local" && \
          ip netns exec vpn ip addr add dev "$1" \
                        "$ifconfig_ipv6_local"/112
                ;;
        route-up)
                ip netns exec vpn ip route add default via "$route_vpn_gateway"
                test -n "$ifconfig_ipv6_remote" && \
          ip netns exec vpn ip route add default via \
                        "$ifconfig_ipv6_remote"
                ;;
        down)
                ip netns delete vpn
                ;;
esac
EOF

Starten Sie dann OpenVPN und weisen Sie es an, unser --up-Skript zu verwenden, anstatt ifconfig und route auszuführen.

openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up

Jetzt können Sie Programme starten, die wie folgt getunnelt werden:

ip netns exec vpn command

Der einzige Haken ist, dass Sie root sein müssen, um aufzurufen, ip netns exec ...und dass Ihre Anwendung möglicherweise nicht als root ausgeführt werden soll. Die Lösung ist einfach:

Befehl sudo ip netns exec vpn sudo -u $ (whoami)
Asuranceturix
quelle
1
Hallo und willkommen auf der Seite! Wir empfehlen den Benutzern, zumindest die Inhalte der Links, die sie in Antworten einfügen, zusammenzufassen (falls möglich). Auf diese Weise bleibt die Antwortqualität für den Fall erhalten, dass der Link veraltet ist (z. B. wenn die Site nicht mehr zugänglich ist). Bitte verbessern Sie Ihre Antwort, indem Sie die wichtigsten Teile / Anweisungen aus dem verlinkten Artikel hinzufügen.
Erathiel
Das ist großartig, aber Sie müssen den öffnenden heredoc-Begrenzer in einfache Anführungszeichen setzen, um zu verhindern, dass die Shell alle Variablen erweitert.
Ewatt
7

Es stellt sich heraus , dass Sie kann eine Tunnel - Schnittstelle in ein Netzwerk - Namespace setzen. Mein gesamtes Problem war auf einen Fehler beim Aufrufen der Benutzeroberfläche zurückzuführen:

ip addr add dev $tun_tundv \
    local $ifconfig_local/$ifconfig_cidr \
    broadcast $ifconfig_broadcast \
    scope link

Das Problem ist "Scope Link", was ich falsch verstanden habe, da es nur das Routing betrifft. Es veranlasst den Kernel, die Quelladresse aller in den Tunnel gesendeten Pakete zu setzen 0.0.0.0. vermutlich würde der OpenVPN-Server sie dann gemäß RFC1122 als ungültig verwerfen; Selbst wenn dies nicht der Fall wäre, wäre das Ziel offensichtlich nicht in der Lage zu antworten.

In Abwesenheit von Netzwerk-Namespaces funktionierte alles ordnungsgemäß, da das integrierte Netzwerk-Konfigurationsskript von openvpn diesen Fehler nicht begangen hat. Und ohne "Scope Link" funktioniert mein ursprüngliches Skript auch.

(Wie habe ich das herausgefunden, fragst du? Indem du straceden openvpn-Prozess ausführst, stellst du ein, dass alles, was er aus dem Tunnel-Deskriptor liest, hexadezimal ausgegeben wird, und dekodierst dann die Paket-Header manuell.)

zwol
quelle
Gibt es eine Möglichkeit, einen Leitfaden dazu zu verfassen? Ich versuche, etwas Ähnliches einzurichten, aber es ist schwer zu sagen, von welchen Teilen Ihrer Frage aus man gut anfangen kann und welche Wege zum Scheitern geführt haben.
Tremby
@tremby Ich werde wahrscheinlich in naher Zukunft keine Zeit dafür haben, aber vielleicht finden Sie github.com/zackw/tbbscraper/blob/master/scripts/openvpn-netns.c nützlich.
zwol
Ja, ich bin mir nicht sicher, ob ein 1100-Zeilen-C-Programm helfen wird. Wie wäre es nur mit der endgültigen Konfiguration, Skripten und Beschwörungen, die den Job für Sie erledigt haben? ... Oder ist das C-Programm Ihre endgültige Implementierung davon?
Tremby
@tremby Ja, dieses C-Programm ist meine endgültige Implementierung. (In meinem Anwendungsszenario muss es eingestellt sein, wie Sie sehen.) Vielleicht können Sie das Ding einfach vorbeischauen - wenn der große Kommentar oben nicht erklärt, wie man es verwendet, lassen Sie es mich wissen.
zwol
@tremby Alternativ können Sie in den "Skripten, die von openvpn aus ausgeführt werden" unter github.com/zackw/tbbscraper/blob/master/scripts/… nachsehen, wie der Netzwerk-Namespace eingerichtet und abgerissen wird. Der eigentliche Aufruf des OVPN-Clients erfolgt unter github.com/zackw/tbbscraper/blob/master/scripts/… . Der Rest des Codes kann als Mini-Shell-Implementierung betrachtet werden, um das Schreiben dieser Operationen zu vereinfachen.
zwol
4

Der Fehler beim Versuch, die veth-Geräte zu erstellen, wird durch eine Änderung der ipInterpretation der Befehlszeilenargumente verursacht.

Der richtige Aufruf ipzum Erstellen eines Paares von veth-Geräten lautet

ip link add name veth0 type veth peer name veth1

( namestatt dev)

Wie kann der Datenverkehr vom Namespace in den VPN-Tunnel geleitet werden? Da Ihnen nur Tun-Geräte zur Verfügung stehen, muss der "Host" routen. Das heißt, Sie erstellen das veth-Paar und fügen eines in den Namespace ein. Verbinden Sie den anderen über das Routing mit dem Tunnel. Aktivieren Sie daher die Weiterleitung, und fügen Sie dann die erforderlichen Routen hinzu.

Angenommen, dies eth0ist Ihre Hauptschnittstelle, tun0Ihre VPN-Tunnelschnittstelle und veth0/ oder veth1das Schnittstellenpaar, das veth1sich im Namespace befindet. Im Namespace fügen Sie nur eine Standardroute für hinzu veth1.

Auf dem Host müssen Sie Richtlinienrouting anwenden, siehe hier zum Beispiel. Was musst du machen:

Hinzufügen / Anhängen eines Eintrags wie

1   vpn

zu /etc/iproute2/rt_tables. Auf diese Weise können Sie die (noch zu erstellende) Tabelle mit ihrem Namen aufrufen.

Verwenden Sie dann die folgenden Anweisungen:

ip rule add iif veth0 priority 1000 table vpn
ip rule add iif tun0 priority 1001 table vpn
ip route add default via <ip-addr-of-tun0> table vpn
ip route add <ns-network> via <ip-addr-of-veth0> table vpn

Ich kann das hier nicht mit einem Setup wie dem Ihren ausprobieren, aber das sollte genau das tun, was Sie wollen. Sie können dies durch Paketfilterregeln erweitern, so dass weder das VPN noch das "Gast" -Netz gestört werden.

NB Der Einstieg tun0in den Namespace scheint das Richtige zu sein. Aber wie du habe ich das nicht zum Laufen gebracht. Richtlinien-Routing scheint das nächste richtige Vorgehen zu sein. Die Lösung von Mahendra ist anwendbar, wenn Sie die Netzwerke hinter dem VPN kennen und alle anderen Anwendungen niemals auf diese Netzwerke zugreifen. Ihre Ausgangsbedingung ("Der gesamte Datenverkehr und nur der Datenverkehr zu / von bestimmten Prozessen wird über das VPN abgewickelt") scheint jedoch so zu sein, als ob letzterer nicht garantiert werden kann.

Gegenmodus
quelle
Danke, das bringt mich ein bisschen weiter, aber ich bleibe jetzt beim Teil "und dann benutzt du eine Brücke, um das veth-Gerät mit dem Tunnel zu verbinden" - siehe die überarbeitete Frage.
zwol
Gemäß der Antwort, die ich gerade gepostet habe, ist die ganze Sache auf einen dummen Fehler in meinem ursprünglichen Skript zurückzuführen - "Scope Link" bedeutet nicht, was ich dachte, dass es bedeutete. Aber ich gebe dir das Kopfgeld, weil du viel Arbeit in das Ausprobieren verschiedener Möglichkeiten gesteckt hast, und ich hätte wahrscheinlich ganz aufgegeben, wenn du es nicht getan hättest.
zwol
Hey Zack, vielen Dank. Namespaces und Policy Routing waren eine interessante Sache zu erforschen. Ich hätte mir nicht so viel Mühe gegeben, wenn es nicht aufregend gewesen wäre.
Gegenmodus
0

Wenn die Netzwerke bekannt sind, auf die Sie über das VPN zugreifen, können Sie Ihre Routing-Tabelle bearbeiten, um die gewünschten Ergebnisse zu erzielen.

  1. Notieren Sie sich Ihre aktuelle Standardroute.

    # ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024

  2. Wenn Sie VPN ausführen, wird ein Routing-Eintrag erstellt.

  3. Löschen Sie die aktuelle Standardroute (die vom VPN hinzugefügt wird), wobei die vorherige Standardroute der erste Standardeintrag in der Tabelle sein soll.

    # ip route | grep default default dev tun0 scope link default via 192.168.43.1 dev wlo1 proto static metric 1024

    # ip route del default dev tun0 scope link

  4. Fügen Sie den Netzwerken, die sich im VPN befinden, benutzerdefinierte Routen hinzu, um sie durch tun0 zu leiten.

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. Fügen Sie sowohl Nameserver-Einträge (in der resolv.conf) als auch für die VPN- und Direktverbindung hinzu.

Jetzt werden alle Net1- und Net2-Verbindungen über das VPN und das Zurücksetzen direkt durchgeführt (in diesem Beispiel über wlo1).

Mahendra
quelle
Leider sind die Netzwerke, auf die über das VPN zugegriffen wird, nicht im Voraus bekannt, daher funktioniert dies bei mir nicht.
zwol