Estou tentando configurar uma VPN (usando OpenVPN), de modo que todo o tráfego, e somente o tráfego, de / para processos específicos passe pela VPN; outros processos devem continuar usando o dispositivo físico diretamente. Entendo que a maneira de fazer isso no Linux é com namespaces de rede.
Se eu usar o OpenVPN normalmente (ou seja, canalizar todo o tráfego do cliente através da VPN), ele funcionará bem. Especificamente, 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 a namespaces. Eu tentei:
Colocando o dispositivo de encapsulamento diretamente no espaço para nome 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 espaço para nome (por exemplo, com
ip netns exec tns0 traceroute -n 8.8.8.8
) cai em um buraco negro.Supondo que " você ainda [pode] atribuir apenas interfaces Ethernet (veth) virtuais a um espaço para nome de rede " (que, se verdadeiro, recebe o prêmio deste ano pela restrição de API mais ridiculamente desnecessária), criando um par e uma ponte de veth e colocando uma extremidade do par veth no espaço para nome. Isso nem chega a diminuir o tráfego no chão: não me deixa colocar o túnel na ponte! [EDIT: Parece que apenas os dispositivos de toque podem ser colocados em pontes. Diferentemente da incapacidade de colocar dispositivos arbitrários em um espaço para nome de rede, isso realmente faz sentido, o que significa pontes como um conceito de camada Ethernet; infelizmente, meu provedor de VPN não oferece suporte ao OpenVPN no modo de toque, por isso 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ções. Variáveis nos scripts que parecem ser usadas sem defini-las primeiro são definidas no ambiente pelo openvpn
programa - sim, é desleixado e usa nomes em minúsculas.
Por favor, ofereça conselhos específicos sobre como fazer isso funcionar. Estou dolorosamente ciente de que estou programando por cult de carga aqui - alguém escreveu uma documentação abrangente para essas coisas? Não consigo encontrar nenhum - portanto, a revisão geral do código dos scripts também é apreciada.
Caso isso importe:
# 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 construído pelo meu provedor de hospedagem virtual ( Linode ) e, embora compilado CONFIG_MODULES=y
, não possui módulos reais - a única CONFIG_*
variável definida m
como /proc/config.gz
era CONFIG_XEN_TMEM
, e eu realmente não tenho esse módulo (o kernel é armazenado fora do meu sistema de arquivos; /lib/modules
está vazio e /proc/modules
indica que não foi carregado magicamente de alguma forma). Trechos /proc/config.gz
fornecidos a pedido, mas não quero colar a coisa toda 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
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
não lista nada, mas não sei se isso é conclusivo. As instâncias do Linode não têm um kernel instalado dentro da partição do SO, portanto, não tenho certeza se poderia carregar um módulo ausente de qualquer maneira.
lsmod
produz nenhuma saída em tudo? Existe um diretório /lib/modules
?
lsmod: command not found
. Existe /lib/modules
, mas ele não possui nenhum módulo , apenas um monte de diretórios por kernel contendo modules.dep
arquivos vazios . Vou procurar na ajuda específica do Linode e descobrir se é assim que deve ser.