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:
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.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 openvpn
festzulegen, 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 m
laut festgelegt /proc/config.gz
wurde CONFIG_XEN_TMEM
, ist, und ich habe dieses Modul tatsächlich nicht (der Kernel ist außerhalb meines Dateisystems gespeichert; /lib/modules
ist leer und /proc/modules
zeigt an, dass es nicht irgendwie magisch geladen wurde). Auszüge aus /proc/config.gz
auf 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>
grep veth /proc/modules
listet 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.
lsmod
produzieren überhaupt keine Ausgabe? Gibt es ein Verzeichnis /lib/modules
?
lsmod: command not found
. Es gibt ein /lib/modules
, aber es enthält keine Module , nur eine Reihe von Kernel-Verzeichnissen, die leere modules.dep
Dateien enthalten. Ich werde in Linode-spezifischer Hilfe stöbern und herausfinden, ob es so sein soll.