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:
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.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 openvpn
program - 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ą m
zgodnie z tym /proc/config.gz
było CONFIG_XEN_TMEM
i ja tak naprawdę nie mam tego modułu (jądro jest przechowywane poza moim systemem plików; /lib/modules
jest pusty i /proc/modules
wskazuje, że nie został w jakiś sposób magicznie załadowany). Fragmenty /proc/config.gz
dostarczone 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>
grep veth /proc/modules
nic 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ł.lsmod
ogóle generuje jakieś wyniki? Czy istnieje katalog/lib/modules
?lsmod: command not found
. Jest/lib/modules
, ale nie ma w nim żadnych modułów , tylko kilka katalogów na jądro zawierających pustemodules.dep
pliki. Przeszukam pomoc dotyczącą Linode i dowiem się, czy tak właśnie powinno być.Odpowiedzi:
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:
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:źródło
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:
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
strace
proces openvpn, ustaw heksdump wszystko, co odczytał z deskryptora tunelu, a następnie ręcznie dekoduj nagłówki pakietów.)źródło
Błąd przy próbie utworzenia urządzeń veth jest spowodowany zmianą sposobu
ip
interpretacji argumentów wiersza poleceń.Prawidłowe wywołanie w
ip
celu utworzenia pary urządzeń veth to(
name
zamiastdev
)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
eth0
jest to twój główny interfejs,tun0
jest to twój tunel VPN iveth0
/ lubveth1
para interfejsówveth1
znajduje 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
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:
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
tun0
się 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.źródło
Jeśli sieci, do których uzyskujesz dostęp przez VPN, są znane, możesz edytować tabelę routingu, aby osiągnąć to, co chcesz.
Zanotuj swoją domyślną trasę.
# ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024
Uruchom VPN, a to wprowadzi pozycję routingu.
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
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
Teraz wszystkie połączenia net1 i net2 przejdą przez VPN, a reset przejdzie bezpośrednio (przez wlo1 w tym przykładzie).
źródło