Alimenta tutto il traffico tramite OpenVPN solo per uno spazio dei nomi di rete specifico


16

Sto cercando di configurare una VPN (usando OpenVPN) in modo tale che tutto il traffico, e solo il traffico, verso / da processi specifici passi attraverso la VPN; altri processi dovrebbero continuare a utilizzare direttamente il dispositivo fisico. Comprendo che il modo per farlo in Linux è con gli spazi dei nomi di rete.

Se utilizzo OpenVPN normalmente (ovvero incanalando tutto il traffico dal client attraverso la VPN), funziona perfettamente. In particolare, avvio OpenVPN in questo modo:

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

(Una versione redatta di destination.ovpn è alla fine di questa domanda.)

Sono bloccato sul passaggio successivo, scrivendo script che limitano il dispositivo tunnel agli spazi dei nomi. Ho provato:

  1. Mettere il dispositivo tunnel direttamente nello spazio dei nomi con

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

    Questi comandi vengono eseguiti correttamente, ma il traffico generato all'interno dello spazio dei nomi (ad es. Con ip netns exec tns0 traceroute -n 8.8.8.8) cade in un buco nero.

  2. Partendo dal presupposto che " è possibile [ancora] assegnare solo interfacce Ethernet virtuali (veth) a uno spazio dei nomi di rete " (che, se vero, si aggiudica il premio di quest'anno per la restrizione API ridicolmente superflua), creando una coppia veth e un bridge e mettendo un'estremità della coppia veth nello spazio dei nomi. Questo non arriva nemmeno a far cadere il traffico sul pavimento: non mi permetterà di mettere il tunnel nel ponte! [EDIT: questo sembra essere perché solo i dispositivi di tocco possono essere messi in bridge. A differenza dell'incapacità di inserire dispositivi arbitrari in uno spazio dei nomi di rete, ciò ha effettivamente senso, ad esempio se i bridge sono un concetto di livello Ethernet; sfortunatamente, il mio provider VPN non supporta OpenVPN in modalità tap, quindi ho bisogno di una soluzione alternativa.]

    # 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
    

Gli script alla fine di questa domanda sono per l'approccio veth. Gli script per l'approccio diretto possono essere trovati nella cronologia delle modifiche. Le variabili negli script che sembrano essere utilizzate senza prima impostarle sono impostate nell'ambiente dal openvpnprogramma - sì, è sciatto e usa nomi minuscoli.

Si prega di offrire consigli specifici su come farlo funzionare. Sono dolorosamente consapevole del fatto che sto programmando da cult cult qui - qualcuno ha scritto una documentazione completa per queste cose? Non ne trovo nessuno, quindi è apprezzata anche la revisione generale del codice degli script.

Nel caso in cui sia importante:

# 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

Il kernel è stato creato dal mio provider di hosting virtuale ( Linode ) e, sebbene compilato CONFIG_MODULES=y, non ha moduli effettivi - l'unica CONFIG_*variabile impostata su msecondo /proc/config.gzera CONFIG_XEN_TMEM, e in realtà non ho quel modulo (il kernel è archiviato al di fuori del mio filesystem; /lib/modulesè vuoto e /proc/modulesindica che in qualche modo non è stato caricato magicamente). Estratti da /proc/config.gzforniti su richiesta, ma non voglio incollare l'intera cosa qui.

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>

Cominciamo con l'ovvio: i dispositivi veth sono supportati? i moduli del kernel (veth) sono caricati?
Contromodalità

@countermode grep veth /proc/modulesnon elenca nulla, ma non so se sia conclusivo. Le istanze di Linode non hanno un kernel installato all'interno della partizione del sistema operativo, quindi non sono sicuro di poter comunque caricare un modulo mancante.
zwol,

Non lsmodproduce alcun risultato a tutti? C'è una directory /lib/modules?
Contromodalità

lsmod: command not found. C'è un /lib/modules, ma non ha alcun modulo , solo un mucchio di directory per kernel contenenti modules.depfile vuoti . Dare un'occhiata in aiuto specifico di Linode e scoprire se è così che dovrebbe essere.
zwol,

hmm ... molto strano. Non ho familiarità con Linode ma a me sembra che i dispositivi veth non siano supportati.
Contromodalità

Risposte:


9

È possibile avviare il collegamento OpenVPN all'interno di uno spazio dei nomi e quindi eseguire tutti i comandi che si desidera utilizzare quel collegamento OpenVPN all'interno dello spazio dei nomi. Dettagli su come farlo (non il mio lavoro) qui:

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

L'ho provato e funziona; l'idea è di fornire uno script personalizzato per eseguire le fasi up e route-up della connessione OpenVPN all'interno di uno spazio dei nomi specifico anziché globale. Cito dal link sopra nel caso in cui non sia in linea in futuro:

Innanzitutto crea uno script --up per OpenVPN. Questo script creerà l'interfaccia del tunnel VPN all'interno di uno spazio dei nomi di rete chiamato vpn, invece dello spazio dei nomi predefinito.

$ 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

Quindi avvia OpenVPN e digli di usare il nostro script --up invece di eseguire ifconfig e route.

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

Ora puoi avviare i programmi per eseguire il tunneling in questo modo:

ip netns exec vpn command

L'unico problema è che devi essere root per invocare ip netns exec ...e forse non vuoi che l'applicazione venga eseguita come root. La soluzione è semplice:

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

1
Ciao e benvenuto nel sito! Incoraggiamo gli utenti a riassumere almeno (se possibile) il contenuto dei collegamenti che incollano nelle risposte. Ciò aiuta a mantenere la qualità della risposta nel caso in cui il collegamento diventi obsoleto (ad es. Il sito non è più accessibile). Migliora la tua risposta includendo le parti / istruzioni più importanti dell'articolo collegato.
Erathiel,

Questo è fantastico, ma è necessario inserire virgolette singole attorno al delimitatore heredoc di apertura per impedire alla shell di espandere tutte le variabili.
ewatt,

7

Si scopre che è possibile inserire un'interfaccia tunnel in uno spazio dei nomi di rete. Il mio intero problema era dovuto a un errore nel richiamare l'interfaccia:

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

Il problema è "scope scope", che ho frainteso come influente solo sul routing. Fa sì che il kernel imposti l'indirizzo sorgente di tutti i pacchetti inviati nel tunnel su 0.0.0.0; presumibilmente il server OpenVPN li eliminerebbe come non validi per RFC1122; anche se non lo fosse, la destinazione non sarebbe ovviamente in grado di rispondere.

Tutto ha funzionato correttamente in assenza di spazi dei nomi di rete perché lo script di configurazione della rete integrato di openvpn non ha commesso questo errore. E senza "link ambito", funziona anche il mio script originale.

(Come ho scoperto questo, mi chiedete? Eseguendo straceil processo openvpn, impostate su hexdump tutto ciò che legge dal descrittore del tunnel e quindi decodificate manualmente le intestazioni dei pacchetti.)


Hai qualche possibilità di scrivere una guida su questo? Sto cercando di impostare qualcosa di simile, ma è difficile dire da quali parti della tua domanda è utile iniziare e quali sono i percorsi che hanno portato al fallimento.
tremby,

@tremby Non ho probabilmente tempo di farlo nel prossimo futuro, ma potresti trovare utile github.com/zackw/tbbscraper/blob/master/scripts/openvpn-netns.c .
zwol,

Sì, non sono sicuro che un programma C da 1100 linee sarà d'aiuto. Che ne dici solo della configurazione finale, degli script e degli incantesimi che hanno fatto il lavoro fatto per te? ... O quel programma C è la tua implementazione finale di questo?
tremby,

@tremby Sì, quel programma C è la mia implementazione finale. (Vedi nel mio scenario di utilizzo, deve essere impostato, vedi.) Potresti essere in grado di inserire semplicemente la cosa - se il grande commento in alto non spiega come usarlo, fammi sapere.
zwol,

@tremby In alternativa, guarda "Script eseguiti dall'interno di openvpn", a partire da github.com/zackw/tbbscraper/blob/master/scripts/… , per vedere come lo spazio dei nomi di rete è impostato e abbattuto; e l'effettiva invocazione del client ovpn è su github.com/zackw/tbbscraper/blob/master/scripts/… . Il resto del codice può essere pensato come un'implementazione mini-shell per rendere quelle operazioni meno noiose da scrivere.
zwol,

4

L'errore nel tentativo di creare i dispositivi veth è causato da una modifica del modo in cui ipinterpreta gli argomenti della riga di comando.

La corretta invocazione di ipcreare una coppia di dispositivi veth è

ip link add name veth0 type veth peer name veth1

( nameinstad di dev)

Ora, come far uscire il traffico dallo spazio dei nomi al tunnel VPN? Dal momento che hai a disposizione solo dispositivi tun, l'host deve instradare. Vale a dire creare la coppia veth e inserirne una nello spazio dei nomi. Collegare l'altro tramite il routing al tunnel. Pertanto, abilitare l'inoltro e quindi aggiungere i percorsi necessari.

Ad esempio, supponiamo che eth0sia la tua interfaccia principale, tun0sia la tua interfaccia tunnel VPN e veth0/ veth1la cui coppia di interfacce veth1sia nello spazio dei nomi. All'interno dello spazio dei nomi aggiungi solo una route predefinita per veth1.

Sull'host è necessario utilizzare il routing delle politiche, vedere ad esempio qui . Cosa devi fare:

Aggiungi / aggiungi una voce come

1   vpn

a /etc/iproute2/rt_tables. In questo modo puoi chiamare la tabella (ancora da creare) per nome.

Quindi utilizzare le seguenti dichiarazioni:

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

Non posso provarlo qui con una configurazione come la tua, ma questo dovrebbe fare esattamente quello che vuoi. Puoi aumentare le regole del filtro pacchetti in modo tale che né la rete VPN né la rete "guest" siano disturbate.

NB tun0Innanzitutto, spostarsi nello spazio dei nomi sembra la cosa giusta da fare. Ma come te non l'ho fatto funzionare. Il routing delle politiche sembra la prossima cosa giusta da fare. La soluzione di Mahendra è applicabile se si conoscono le reti dietro la VPN e tutte le altre applicazioni non accederanno mai a tali reti. Ma la tua condizione iniziale ("tutto il traffico, e solo il traffico, verso / da processi specifici passa attraverso la VPN") sembra che quest'ultima non possa essere garantita.


Grazie, questo mi porta un po 'oltre, ma ora sono bloccato sulla parte "e quindi usi un bridge per connettere il dispositivo veth al tunnel" - vedi la domanda rivista.
zwol,

Per la risposta che ho appena pubblicato, l'intera cosa si riduce a un errore sciocco nella mia sceneggiatura originale - "scope scope" non significa ciò che pensavo significasse. Ma ti darò la grazia, perché hai lavorato molto per aiutarmi a provare varie possibilità, e probabilmente avrei rinunciato del tutto se non l'avessi fatto.
zwol,

Ehi Zack, grazie mille. Gli spazi dei nomi e l'instradamento delle politiche sono stati interessanti per la ricerca. Non avrei mai fatto così tanti sforzi in questo se non fosse stato eccitante da solo.
Contromodalità

0

Se le reti a cui accedi tramite VPN sono note, puoi modificare la tabella di routing per ottenere ciò che desideri.

  1. Nota il percorso predefinito corrente.

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

  2. Eseguire VPN e questo introdurrà una voce di routing.

  3. Elimina la route predefinita corrente (che viene aggiunta dalla VPN) dove come route predefinita precedente deve essere la prima voce predefinita nella tabella.

    # 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. Aggiungi percorsi personalizzati alle reti che si trovano nella VPN per instradare tun0.

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. Aggiungi entrambe le voci del nameserver (in resolv.conf) nonché per la VPN e la connessione diretta.

Ora tutte le connessioni net1 e net2 passeranno attraverso la VPN e il reset andrà direttamente (tramite wlo1 in questo esempio).


Purtroppo, le reti a cui si accede tramite la VPN non sono note in anticipo, quindi questo non funzionerà per me.
zwol
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.