Alimente todo o tráfego através do OpenVPN apenas para um namespace de rede específico

13

Estou tentando configurar uma VPN (usando o OpenVPN) para que todo o tráfego e somente o tráfego, de / para processos específicos passem pela VPN; outros processos devem continuar a usar o dispositivo físico diretamente. No meu entender, a maneira de fazer isso no Linux é com namespaces de rede.

Se eu uso o OpenVPN normalmente (ou seja, afunilando todo tráfego do cliente através da VPN), ele funciona bem. Especificamente, eu inicio o OpenVPN assim:

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

(Uma versão editada de destination.ovpn está no final desta pergunta.)

Estou preso na próxima etapa, escrevendo scripts que restringem o dispositivo de túnel aos namespaces. Eu tentei:

  1. Colocando o dispositivo de encapsulamento diretamente no namespace com

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

    Esses comandos são executados com sucesso, mas o tráfego gerado dentro do namespace (por exemplo, com ip netns exec tns0 traceroute -n 8.8.8.8 ) cai em um buraco negro.

  2. No pressuposto de que " você pode [ainda] atribuir apenas a Ethernet virtual (veth ) interfaces para um namespace de rede "(que, se for verdade, leva o prêmio deste ano para a restrição de API mais ridiculamente desnecessária), criando um par veth e uma ponte e colocando uma extremidade do par veth no namespace. Isso nem chega a deixar o tráfego no chão: não me deixa colocar o túnel na ponte! [EDIT: Isto parece ser porque apenas os dispositivos tap podem ser colocados em pontes. Ao contrário da incapacidade de colocar dispositivos arbitrários em um namespace de rede, isso realmente faz sentido, com as pontes sendo um conceito de camada Ethernet; infelizmente, meu provedor de VPN não suporta o OpenVPN no modo tap, então eu preciso de uma solução 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
    

Os scripts no final desta pergunta são para a abordagem veth. Os scripts para a abordagem direta podem ser encontrados no histórico de edição. Variáveis nos scripts que parecem ser usadas sem configurá-las primeiro são definidas no ambiente pelo programa openvpn - sim, é malfeito e usa nomes em minúsculas.

Por favor, forneça conselhos específicos sobre como fazer isso funcionar. Estou dolorosamente ciente de que estou programando por cult de carga aqui - tem alguém escrito documentação abrangente para essas coisas? Eu não consigo encontrar nenhum - revisão de código geral dos scripts também é apreciada.

Caso seja 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

O kernel foi criado pelo meu provedor de hospedagem virtual ( Linode ) e, embora compilado com CONFIG_MODULES=y , não possui módulos reais - - a única variável CONFIG_* definida como m de acordo com /proc/config.gz foi CONFIG_XEN_TMEM , e na verdade não tenho esse módulo (o kernel é armazenado fora do meu sistema de arquivos; /lib/modules é vazio, e /proc/modules indica que não foi magicamente carregado de alguma forma). Trechos de /proc/config.gz fornecidos a pedido, mas eu não quero colar tudo aqui.

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

netnsdown.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>
    
por zwol 08.08.2014 / 21:26

4 respostas

6

Acontece que você pode colocar uma interface de túnel em um namespace de rede. Todo o meu problema foi um erro ao trazer a interface:

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

O problema é "link de escopo", que eu entendi como afetando apenas o roteamento. Faz com que o kernel configure o endereço de origem de todos os pacotes enviados para o túnel para 0.0.0.0 ; presumivelmente, o servidor OpenVPN então os descartaria como inválidos por RFC1122; mesmo que isso não acontecesse, o destino obviamente seria incapaz de responder.

Tudo funcionou corretamente na ausência de namespaces de rede porque o script de configuração de rede interno do openvpn não cometeu esse erro. E sem "link de escopo", meu script original funciona também.

(Como eu descobri isso, você pergunta? Ao executar strace no processo openvpn, defina para hexdump tudo o que ler do descritor de túnel e, em seguida, decodificar manualmente os cabeçalhos de pacote.)

    
por 14.08.2014 / 18:47
7

Você pode iniciar o link OpenVPN dentro de um namespace e, em seguida, executar todos os comandos que você deseja usar nesse link do OpenVPN dentro do namespace. Detalhes sobre como fazer isso (não meu trabalho) aqui:

link

Eu tentei e funciona; A idéia é fornecer um script personalizado para realizar as fases de up e route-up da conexão OpenVPN dentro de um namespace específico, em vez do global. Cito o link acima apenas para o caso de ficar off-line no futuro:

First create an --up script for OpenVPN. This script will create the VPN tunnel interface inside a network namespace called vpn, instead of the default namespace.

$ 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

Then start OpenVPN and tell it to use our --up script instead of executing ifconfig and route.

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

Now you can start programs to be tunneled like this:

ip netns exec vpn command

O único problema é que você precisa ser root para invocar ip netns exec ... e talvez não queira que seu aplicativo seja executado como root. A solução é simples:

sudo ip netns exec vpn sudo -u $(whoami) command
    
por 14.04.2015 / 12:05
2

O erro na tentativa de criar os dispositivos veth é causado por uma alteração de como ip interpreta os argumentos da linha de comando.

A invocação correta de ip para criar um par de dispositivos veth é

ip link add name veth0 type veth peer name veth1

( name instad de dev )

Agora, como obter tráfego do namespace para o túnel VPN? Como você tem apenas dispositivos de tun à sua disposição, o "host" deve ser encaminhado. Ou seja crie o par de veth e coloque um no namespace. Conecte o outro via roteamento para o túnel. Assim, habilite o encaminhamento e adicione as rotas necessárias.

Por exemplo, suponha que eth0 seja sua interface principal, tun0 é sua interface de túnel VPN e veth0 / veth1 o par de interfaces do qual veth1 está no namespace. Dentro do namespace, você adiciona apenas uma rota padrão para veth1 .

No host, você precisa empregar o roteamento de políticas. Consulte aqui , por exemplo. O que você precisa fazer:

Adicione / acrescente uma entrada como

1   vpn

para /etc/iproute2/rt_tables . Por isso, você pode chamar a tabela (ainda a ser criada) pelo nome.

Em seguida, use as seguintes declarações:

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

Eu não posso experimentar isso aqui com uma configuração como a sua, mas isso deve fazer exatamente o que você quer. Você pode aumentar as regras de filtragem de pacotes de tal forma que nem a VPN nem a rede "guest" sejam perturbadas.

N.B. Mover tun0 para o espaço de nomes, em primeiro lugar, parece a coisa certa a fazer. Mas, como você, eu não consegui fazer isso funcionar. O roteamento de políticas parece a próxima coisa certa a fazer. A solução da Mahendra é aplicável se você souber que as redes por trás da VPN e todas as outras aplicações nunca acessarão essas redes. Mas sua condição inicial ("todo o tráfego, e somente o tráfego, de / para processos específicos passa pela VPN") soa como se o último não pudesse ser garantido.

    
por 12.08.2014 / 16:12
0

Se as redes que você acessar através da VPN forem conhecidas, você poderá editar sua tabela de roteamento para obter o que deseja.

  1. Observe sua rota padrão atual.

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

  2. Execute VPN e isso introduzirá uma entrada de roteamento.

  3. Exclua a rota padrão atual (que é adicionada pela VPN), em que a rota padrão anterior é a primeira entrada padrão na tabela.

    # 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. Adicione rotas personalizadas às redes que estão na VPN para rotear através do tun0.

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. Adicione as duas entradas do servidor de nomes (no resolv.conf) também para a VPN e a conexão direta.

Agora, todas as conexões net1 e net2 passarão pela VPN e a reinicialização será direta (através de wlo1 neste exemplo).

    
por 13.08.2014 / 03:34