Przesyłaj cały ruch przez OpenVPN tylko dla określonej sieciowej przestrzeni nazw


16

Usiłuję skonfigurować VPN (używając OpenVPN) tak, aby cały ruch i tylko ruch do / z określonych procesów przechodzi przez VPN; inne procesy powinny nadal korzystać bezpośrednio z urządzenia fizycznego. Rozumiem, że sposobem na to w Linuksie są sieciowe przestrzenie nazw.

Jeśli normalnie korzystam z OpenVPN (tj. Kieruje cały ruch z klienta przez VPN), działa dobrze. W szczególności uruchamiam OpenVPN w następujący sposób:

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

(Zredagowana wersja pliku destination.ovpn znajduje się na końcu tego pytania).

Utknąłem na następnym etapie, pisząc skrypty ograniczające urządzenie tunelowe do przestrzeni nazw. Próbowałem:

  1. Umieszczanie urządzenia tunelowego bezpośrednio w przestrzeni nazw za pomocą

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

    Te polecenia są wykonywane pomyślnie, ale ruch generowany w przestrzeni nazw (np. Za pomocą ip netns exec tns0 traceroute -n 8.8.8.8) wpada do czarnej dziury.

  2. Przy założeniu, że „ nadal można przypisywać wirtualne interfejsy Ethernet (veth) do przestrzeni nazw sieci ” (która, jeśli jest prawdziwa, odbiera tegoroczną nagrodę za najbardziej absurdalnie niepotrzebne ograniczenie interfejsu API), tworząc parę veth i most oraz umieszczenie jednego końca pary veth w przestrzeni nazw. Nie dochodzi nawet do zrzucania ruchu na podłogę: nie pozwala mi umieścić tunelu w moście! [Edycja: Wydaje się, że, ponieważ tylko tap urządzenia mogą być wprowadzane do mostów. W przeciwieństwie do niemożności umieszczenia dowolnych urządzeń w przestrzeni nazw sieci, ma to sens, ponieważ mosty są koncepcją warstwy Ethernet; niestety mój dostawca VPN nie obsługuje OpenVPN w trybie dotknij, więc potrzebuję obejścia.]

    # 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
    

Skrypty na końcu tego pytania dotyczą podejścia veth. Skrypty do bezpośredniego podejścia można znaleźć w historii edycji. Zmienne w skryptach, które wydają się być używane bez wcześniejszego ich ustawienia, są ustawiane w środowisku przez openvpnprogram - tak, jest niechlujny i używa małych liter.

Proszę podać konkretne porady, jak to zrobić. Jestem boleśnie świadomy, że programuję tutaj według kultu ładunku - czy ktoś napisał wyczerpującą dokumentację na ten temat? Nie mogę ich znaleźć - docenia się także ogólną ocenę kodu skryptów.

W przypadku, gdy ma to znaczenie:

# 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

Jądro zostało zbudowane przez mojego wirtualnego dostawcę hostingu ( Linode ) i chociaż skompilowane z nim CONFIG_MODULES=y, nie ma żadnych rzeczywistych modułów - jedyną CONFIG_*zmienną ustawioną mzgodnie z tym /proc/config.gzbyło CONFIG_XEN_TMEMi ja tak naprawdę nie mam tego modułu (jądro jest przechowywane poza moim systemem plików; /lib/modulesjest pusty i /proc/moduleswskazuje, że nie został w jakiś sposób magicznie załadowany). Fragmenty /proc/config.gzdostarczone na życzenie, ale nie chcę tutaj wklejać całej treści.

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>

Zacznijmy od oczywistego: czy obsługiwane są urządzenia veth? są ładowane moduły jądra (veth)?
kontr-

@countermode grep veth /proc/modulesnic nie wymienia, ale nie wiem, czy to rozstrzygające. Instancje Linode nie mają jądra zainstalowanego wewnątrz partycji systemu operacyjnego, więc nie jestem pewien, czy i tak mógłbym załadować brakujący moduł.
zwolnienie

Czy w lsmodogóle generuje jakieś wyniki? Czy istnieje katalog /lib/modules?
kontr-

lsmod: command not found. Jest /lib/modules, ale nie ma w nim żadnych modułów , tylko kilka katalogów na jądro zawierających puste modules.deppliki. Przeszukam pomoc dotyczącą Linode i dowiem się, czy tak właśnie powinno być.
zwolnienie

hmm ... bardzo dziwne. Nie znam Linode, ale dla mnie wygląda to tak, jakby urządzenia veth nie były obsługiwane.
kontr-

Odpowiedzi:


9

Możesz uruchomić łącze OpenVPN w przestrzeni nazw, a następnie uruchomić każdą komendę, której chcesz użyć z linkiem OpenVPN w przestrzeni nazw. Szczegóły jak to zrobić (nie moja praca) tutaj:

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

Próbowałem i to działa; pomysł polega na zapewnieniu niestandardowego skryptu, który przeprowadzi etapy zwiększania i zwiększania połączenia OpenVPN w określonej przestrzeni nazw zamiast globalnej. Cytuję powyższy link na wypadek, gdyby w przyszłości został wyłączony:

Najpierw utwórz skrypt --up dla OpenVPN. Ten skrypt utworzy interfejs tunelu VPN w sieciowej przestrzeni nazw o nazwie vpn, zamiast domyślnej przestrzeni nazw.

$ 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

Następnie uruchom OpenVPN i powiedz mu, aby używał naszego skryptu --up zamiast wykonywania ifconfig i route.

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

Teraz możesz uruchomić programy do tunelowania w następujący sposób:

ip netns exec vpn command

Jedynym haczykiem jest to, że musisz być rootem, aby się wywoływać, ip netns exec ...a być może nie chcesz, aby aplikacja działała jako root. Rozwiązanie jest proste:

polecenie sudo ip netns exec vpn sudo -u $ (whoami)

1
Witam na stronie! Zachęcamy użytkowników do przynajmniej streszczenia (jeśli to możliwe) treści linków, które wklejają w odpowiedziach. Pomaga to zachować jakość odpowiedzi na wypadek, gdyby link stał się nieaktualny (np. Strona nie jest już dostępna). Popraw swoją odpowiedź, dołączając najważniejsze części / instrukcje z powiązanego artykułu.
Erathiel,

Jest to świetne, ale musisz umieścić pojedyncze cudzysłowy wokół otwierającego ogranicznika heredoc, aby zapobiec rozszerzaniu przez powłokę wszystkich zmiennych.
ewatt

7

Okazuje się, że można umieścić interfejs tunelu w przestrzeni nazw sieci. Cały mój problem polegał na błędzie podczas uruchamiania interfejsu:

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

Problemem jest „zakres zasięgu”, który źle zrozumiałem jako mający wpływ tylko na routing. Powoduje, że jądro ustawia adres źródłowy wszystkich pakietów wysyłanych do tunelu do 0.0.0.0; przypuszczalnie serwer OpenVPN odrzuciłby je jako nieprawidłowe zgodnie z RFC1122; nawet gdyby nie, odbiorca nie byłby w stanie odpowiedzieć.

Wszystko działało poprawnie przy braku sieciowych przestrzeni nazw, ponieważ wbudowany skrypt konfiguracji sieci openvpn nie popełnił tego błędu. I bez „linku zakresu” działa również mój oryginalny skrypt.

(Jak to odkryłem, pytasz? Uruchamiając straceproces openvpn, ustaw heksdump wszystko, co odczytał z deskryptora tunelu, a następnie ręcznie dekoduj nagłówki pakietów.)


Czy jest jakaś szansa na napisanie przewodnika na ten temat? Próbuję stworzyć coś podobnego, ale trudno jest określić, od których części pytania warto zacząć, a które ścieżki doprowadziły do ​​niepowodzenia.
tremby

@tremby Nie mam czasu, aby to zrobić w najbliższej przyszłości, ale może się okazać, że github.com/zackw/tbbscraper/blob/master/scripts/openvpn-netns.c jest użyteczny.
zwolnienie

Tak, nie jestem pewien, czy program w 1100 liniach C pomoże. Co powiesz na ostateczną konfigurację, skrypty i inkantacje, które wykonały zadanie za Ciebie? ... Czy ten program w C jest twoją ostateczną implementacją?
tremby

@tremby Tak, ten program C jest moją ostateczną implementacją. (Widzisz, w moim scenariuszu użytkowania musi to być setuid.) Możesz być w stanie po prostu to rzucić - jeśli duży komentarz na górze nie wyjaśnia, jak go używać, daj mi znać.
zwolnienie

@tremby Alternatywnie, spójrz na „Skrypty wykonywane z poziomu openvpn”, zaczynając od github.com/zackw/tbbscraper/blob/master/scripts/... , aby zobaczyć, jak konfiguruje się i rozrywa przestrzeń nazw sieci; a faktyczne wywołanie klienta ovpn znajduje się na stronie github.com/zackw/tbbscraper/blob/master/scripts/… . Pozostałą część kodu można traktować jako implementację mini-powłoki, dzięki czemu operacje te są mniej męczące w pisaniu.
zwolnienie

4

Błąd przy próbie utworzenia urządzeń veth jest spowodowany zmianą sposobu ipinterpretacji argumentów wiersza poleceń.

Prawidłowe wywołanie w ipcelu utworzenia pary urządzeń veth to

ip link add name veth0 type veth peer name veth1

( namezamiast dev)

Teraz, jak uzyskać ruch z przestrzeni nazw do tunelu VPN? Ponieważ masz do dyspozycji tylko urządzenia tun, „host” musi kierować. Czyli stwórz parę veth i umieść jedną w przestrzeni nazw. Połącz drugi przez routing do tunelu. Dlatego włącz przekazywanie, a następnie dodaj niezbędne trasy.

Dla przykładu załóżmy, że eth0jest to twój główny interfejs, tun0jest to twój tunel VPN i veth0/ lub veth1para interfejsów veth1znajduje się w przestrzeni nazw. W przestrzeni nazw dodajesz tylko domyślną trasę veth1.

Na hoście musisz zastosować routing zasad, zobacz tutaj na przykład. Co musisz zrobić:

Dodaj / dodaj wpis jak

1   vpn

do /etc/iproute2/rt_tables. W ten sposób możesz wywołać tabelę (jeszcze do utworzenia) według nazwy.

Następnie użyj następujących instrukcji:

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

Nie mogę tego wypróbować tutaj z taką konfiguracją, ale powinno to zrobić dokładnie to, co chcesz. Możesz zwiększyć to, że dzięki regułom filtrowania pakietów nie zakłóca to ani sieci VPN, ani sieci „guest”.

Uwaga: Przeniesienie tun0się do przestrzeni nazw wydaje się właściwą rzeczą. Ale tak jak ty nie udało mi się tego. Rutowanie zasad wydaje się następną właściwą rzeczą. Rozwiązanie Mahendra ma zastosowanie, jeśli znasz sieci VPN i wszystkie inne aplikacje nigdy nie będą miały dostępu do tych sieci. Ale stan początkowy („cały ruch i tylko ruch do / z określonych procesów przechodzi przez VPN”) brzmi, jakby nie można było zagwarantować tego drugiego.


Dzięki, to prowadzi mnie trochę dalej, ale utknąłem w części „a następnie używasz mostu, aby podłączyć urządzenie veth do tunelu” - zobacz poprawione pytanie.
zwolnienie

Według odpowiedzi, którą właśnie zamieściłem, cała sprawa sprowadza się do głupiego błędu w moim oryginalnym skrypcie - „link do zakresu” nie znaczy, co myślałem, że to znaczy. Ale dam ci nagrodę, ponieważ wkładasz dużo pracy w pomaganie mi w wypróbowywaniu różnych możliwości, a pewnie zrezygnowałbym całkowicie, gdybyś tego nie zrobił.
zwolnić

Hej Zack, wielkie dzięki. Przestrzenie nazw i routing zasad były ciekawą rzeczą do zbadania. Nie włożyłbym w to tyle wysiłku, gdyby nie było to samo w sobie ekscytujące.
kontr-

0

Jeśli sieci, do których uzyskujesz dostęp przez VPN, są znane, możesz edytować tabelę routingu, aby osiągnąć to, co chcesz.

  1. Zanotuj swoją domyślną trasę.

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

  2. Uruchom VPN, a to wprowadzi pozycję routingu.

  3. Usuń bieżącą domyślną trasę (dodaną przez VPN), gdzie jako poprzednia domyślna trasa jest pierwszą domyślną pozycją w tabeli.

    # 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. Dodaj niestandardowe trasy do sieci w sieci VPN, aby trasować przez tun0.

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. Dodaj oba wpisy serwera nazw (w resolv.conf), jak również dla VPN i bezpośredniego połączenia.

Teraz wszystkie połączenia net1 i net2 przejdą przez VPN, a reset przejdzie bezpośrednio (przez wlo1 w tym przykładzie).


Niestety sieci dostępne za pośrednictwem VPN nie są wcześniej znane, więc nie będzie to dla mnie działać.
zwolnić
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.