Alimenter tout le trafic via OpenVPN pour un espace de noms réseau spécifique uniquement

J'essaye de mettre en place un VPN (using OpenVPN) tel que tout le trafic, et seulement le trafic, de / vers des process spécifiques passe par le VPN; les autres process doivent continuer à utiliser le périphérique physique directement. Je crois comprendre que la façon de faire cela dans Linux est avec les namespaces de réseau.

Si j'utilise OpenVPN normalement (c.-à-d. Canaliser tout le trafic du client via le VPN), cela fonctionne très bien. Plus précisément, je démarre OpenVPN comme ceci:

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

(Une version expurgée de destination.ovpn est à la fin de cette question.)

Je suis bloqué à l'étape suivante, en écrivant des scripts qui limitent le périphérique tunnel aux namespaces. J'ai essayé:

  1. Mettre le périphérique tunnel directement dans l'espace de noms

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

    Ces commands s'exécutent correctement, mais le trafic généré à l'intérieur de l'espace de noms (par exemple avec ip netns exec tns0 traceroute -n 8.8.8.8 ) tombe dans un trou noir.

  2. En supposant que vous ne pouvez [encore] atsortingbuer des interfaces Ethernet virtuelles (veth) qu'à un espace de noms réseau (ce qui, si c'est vrai, prend la récompense de cette année pour la ressortingction API la plus ridiculement inutile) mettre une extrémité de la veth paire dans l'espace de noms. Cela ne va même pas jusqu'à laisser tomber le trafic sur le sol: il ne me laissera pas mettre le tunnel dans le pont! [EDIT: Cela semble être parce que seuls les dispositifs de sockets peuvent être placés dans des ponts. Contrairement à l'impossibilité de placer des périphériques arbitraires dans un espace de noms de réseau, cela a du sens, avec les ponts qui sont un concept de couche Ethernet; malheureusement, mon fournisseur VPN ne supporte pas OpenVPN en mode tap, j'ai donc besoin d'une solution de contournement.]

     # 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 

Les scripts à la fin de cette question sont pour l'approche veth. Les scripts pour l'approche directe peuvent être trouvés dans l'historique d'édition. Les variables dans les scripts qui semblent être utilisés sans les définir d'abord sont définies dans l'environnement par le programme openvpn – oui, c'est bâkey et utilise des noms en minuscules.

S'il vous plaît offrir des conseils spécifiques sur la façon de faire fonctionner cela. Je suis douloureusement conscient que je suis en train de programmer par cargo culte ici – est-ce que quelqu'un a écrit une documentation complète pour ce genre de choses? Je ne peux pas find – ainsi l'examen général du code des scripts est également apprécié.

Au cas où cela count:

 # 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 

Le kernel a été construit par mon hébergeur virtuel ( Linode ) et, bien que compilé avec CONFIG_MODULES=y , il n'y a pas de modules réels – la seule variable CONFIG_* réglée sur m selon /proc/config.gz était CONFIG_XEN_TMEM , et je ne sais pas j'ai effectivement ce module (le kernel est stocké en dehors de mon système de files, /lib/modules est vide et /proc/modules indique qu'il n'a pas été chargé magiquement). Extraits de /proc/config.gz fournis sur request, mais je ne veux pas coller la chose entière ici.

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 hm 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-mesortingc 1 proto tcp ping-exit 90 remote [REDACTED] <ca> [REDACTED] </ca> <cert> [REDACTED] </cert> <key> [REDACTED] </key> 

    Il s'avère que vous pouvez mettre une interface tunnel dans un espace de noms réseau. Tout mon problème était dû à une erreur de mise en place de l'interface:

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

    Le problème est le "scope link", que j'ai mal compris comme n'affectant que le routing. Cela amène le kernel à définir l'adresse source de tous les packages envoyés dans le tunnel à 0.0.0.0 ; vraisemblablement, le server OpenVPN les rejetterait alors comme invalides par RFC1122; même si ce n'était pas le cas, la destination serait évidemment incapable de répondre.

    Tout fonctionnait correctement en l'absence d'namespaces réseau car le script de configuration réseau embedded d'openvpn ne faisait pas cette erreur. Et sans "lien de scope", mon script d'origine fonctionne aussi.

    (Comment avez-vous découvert cela?) En exécutant strace sur le process openvpn, définissez sur hexdump tout ce qu'il lit dans le descripteur de tunnel, puis décode manuellement les en-têtes de packages.

    Vous pouvez démarrer le lien OpenVPN à l'intérieur d'un espace de noms, puis exécuter chaque command que vous souhaitez utiliser pour ce lien OpenVPN à l'intérieur de l'espace de noms. Détails sur la façon de le faire (pas mon travail) ici:

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

    Je l'ai essayé et ça marche; l'idée est de fournir un script personnalisé pour effectuer les phases de montée et de descente de la connection OpenVPN à l'intérieur d'un espace de noms spécifique au lieu du global. Je cite le lien ci-dessus au cas où il se déconnecte dans le futur:

    Créez d'abord un script –up pour OpenVPN. Ce script créera l'interface du tunnel VPN à l'intérieur d'un espace de nom de réseau appelé vpn, au lieu de l'espace de noms par défaut.

     $ 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 

    Ensuite, démarrez OpenVPN et dites-lui d'utiliser notre script –up au lieu d'exécuter ifconfig et route.

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

    Maintenant, vous pouvez démarrer des programmes pour être en tunnel comme ceci:

     ip netns exec vpn command 

    Le seul hic est que vous devez être root pour invoquer ip netns exec ... et peut-être que vous ne voulez pas que votre application s'exécute en tant que root. La solution est simple:

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

    L'erreur lors de la tentative de création des périphériques veth est provoquée par une modification de la façon dont ip interprète les arguments de la command line.

    L'invocation correcte d' ip pour créer une paire de veth devices est

     ip link add name veth0 type veth peer name veth1 

    ( name instad de dev )

    Maintenant, comment faire passer le trafic de l'espace de noms vers le tunnel VPN? Puisque vous n'avez que des appareils tun, l'hôte doit être routé. C'est à dire créer la veth paire et mettre un dans l'espace de noms. Connectez l'autre via le routing vers le tunnel. Ainsi, activez le transfert, puis ajoutez les routes nécessaires.

    Par exemple, supposons que eth0 soit votre interface principale, tun0 est votre interface de tunnel VPN, et veth0 / veth1 la paire d'interfaces dont veth1 est dans l'espace de noms. Dans l'espace de noms, vous ajoutez simplement une route par défaut pour veth1 .

    Sur l'hôte, vous devez utiliser le routing de stratégie, voir ici par exemple. Qu'as tu besoin de faire:

    Ajouter / append une input comme

     1 vpn 

    à /etc/iproute2/rt_tables . Vous pouvez ainsi appeler la table (à créer) par son nom.

    Utilisez ensuite les instructions suivantes:

     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 

    Je ne peux pas essayer cela avec une configuration comme la vôtre, mais cela devrait faire exactement ce que vous voulez. Vous pouvez augmenter cela par des règles de filtrage de packages de sorte que ni le VPN ni le réseau "invité" ne soient perturbés.

    NB Déplacer tun0 dans l'espace de noms à la première place ressemble à la bonne chose à faire. Mais comme toi je n'ai pas eu ça pour travailler. Le routing de la politique ressemble à la prochaine chose à faire. La solution de Mahendra est applicable si vous connaissez les réseaux derrière le VPN et que toutes les autres applications n'accèderont jamais à ces réseaux. Mais votre condition initiale ("tout le trafic, et seulement le trafic, de / vers des process spécifiques passe par le VPN") sonne comme si ce dernier ne peut pas être garanti.

    Si les réseaux auxquels vous accédez via le VPN sont connus, vous pouvez modifier votre table de routing pour get ce que vous voulez.

    1. Notez votre itinéraire par défaut actuel.

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

    2. Exécutez VPN et cela introduira une input de routing.

    3. Supprimez la route par défaut actuelle (qui est ajoutée par le VPN) où la route par défaut précédente est la première input par défaut dans la table.

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

      # ip route del default dev tun0 scope link

    4. Ajoutez des routes personnalisés aux réseaux qui se trouvent dans le VPN pour passer par tun0.

      # ip route add <net1>/16 dev tun0

      # ip route add <net2>/24 dev tun0

    5. Ajoutez les deux inputs du server de noms (dans resolv.conf) aussi bien pour le VPN que pour la connection directe.

    Maintenant toutes les connections net1 et net2 passeront par le VPN et la réinitialisation ira directement (à travers wlo1 dans cet exemple).