Estoy tratando de configurar una VPN (usando OpenVPN) de modo que todo el tráfico, y solo el tráfico, hacia / desde procesos específicos pase por la VPN; otros procesos deberían continuar usando el dispositivo físico directamente. Tengo entendido que la forma de hacer esto en Linux es con espacios de nombres de red.
Si uso OpenVPN normalmente (es decir, canalizando todo el tráfico del cliente a través de la VPN), funciona bien. Específicamente, comienzo OpenVPN así:
# openvpn --config destination.ovpn --auth-user-pass credentials.txt
(Una versión redactada de destination.ovpn se encuentra al final de esta pregunta).
Estoy atascado en el siguiente paso, escribiendo scripts que restringen el dispositivo del túnel a espacios de nombres. Yo he tratado:
Poner el dispositivo de túnel directamente en el espacio de nombres con
# ip netns add tns0 # ip link set dev tun0 netns tns0 # ip netns exec tns0 ( ... commands to bring up tun0 as usual ... )
Estos comandos se ejecutan con éxito, pero el tráfico generado dentro del espacio de nombres (por ejemplo, con
ip netns exec tns0 traceroute -n 8.8.8.8
) cae en un agujero negro.Suponiendo que " todavía puede [todavía] asignar interfaces virtuales Ethernet (veth) a un espacio de nombres de red " (lo que, de ser cierto, se lleva el premio de este año por la restricción API más ridículamente innecesaria), creando un par veth y un puente, y poniendo un extremo del par veth en el espacio de nombres. Esto ni siquiera llega a dejar caer el tráfico en el piso: ¡no me permitirá poner el túnel en el puente! [EDITAR: Esto parece deberse a que solo los dispositivos de tap se pueden poner en puentes. A diferencia de la incapacidad de colocar dispositivos arbitrarios en un espacio de nombres de red, eso realmente tiene sentido, ya que los puentes son un concepto de capa Ethernet; desafortunadamente, mi proveedor de VPN no admite OpenVPN en modo tap, por lo que necesito una solución 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
Los guiones al final de esta pregunta son para el enfoque veth. Los scripts para el enfoque directo se pueden encontrar en el historial de edición. El programa configura las variables en los scripts que parecen usarse sin establecerlas primero en el entorno openvpn
; sí, es descuidado y usa nombres en minúsculas.
Ofrezca consejos específicos sobre cómo hacer que esto funcione. Soy dolorosamente consciente de que estoy programando por el culto de carga aquí. ¿ Alguien ha escrito documentación exhaustiva para estas cosas? No puedo encontrar ninguno, por lo que también se agradece la revisión general del código de los scripts.
En caso de que 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
El kernel fue creado por mi proveedor de alojamiento virtual ( Linode ) y, aunque está compilado CONFIG_MODULES=y
, no tiene módulos reales: la única CONFIG_*
variable establecida de m
acuerdo con /proc/config.gz
fue CONFIG_XEN_TMEM
, y en realidad no tengo ese módulo (el kernel se almacena fuera de mi sistema de archivos; /lib/modules
está vacío e /proc/modules
indica que no se cargó mágicamente de alguna manera). Extractos /proc/config.gz
proporcionados a pedido, pero no quiero pegar todo aquí.
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
no enumera nada, pero no sé si eso es concluyente. Las instancias de Linode no tienen un núcleo instalado dentro de la partición del sistema operativo, por lo que no estoy seguro de poder cargar un módulo faltante de todos modos.lsmod
Produce alguna salida? ¿Hay un directorio/lib/modules
?lsmod: command not found
. Hay un/lib/modules
, pero no tiene ningún módulo , solo un montón de directorios por núcleo que contienenmodules.dep
archivos vacíos . Buscaré ayuda específica de Linode y averiguaré si así es como se supone que debe ser.Respuestas:
Puede iniciar el enlace OpenVPN dentro de un espacio de nombres y luego ejecutar cada comando que desee usar ese enlace OpenVPN dentro del espacio de nombres. Detalles sobre cómo hacerlo (no mi trabajo) aquí:
http://www.naju.se/articles/openvpn-netns.html
Lo probé y funciona; La idea es proporcionar un script personalizado para llevar a cabo las fases de subida y enrutamiento de la conexión OpenVPN dentro de un espacio de nombres específico en lugar del global. Cito del enlace anterior en caso de que se desconecte en el futuro:
El único inconveniente es que debes ser root para invocar
ip netns exec ...
y tal vez no quieras que tu aplicación se ejecute como root. La solución es simple:fuente
Resulta que se puede poner una interfaz de túnel en un espacio de nombres de red. Todo mi problema se debió a un error al abrir la interfaz:
El problema es el "enlace de alcance", que no entendí que solo afecta el enrutamiento. Hace que el núcleo establezca la dirección de origen de todos los paquetes enviados al túnel
0.0.0.0
; presumiblemente el servidor OpenVPN los descartaría como inválidos según RFC1122; incluso si no fuera así, el destino obviamente no podría responder.Todo funcionó correctamente en ausencia de espacios de nombres de red porque el script de configuración de red incorporado de openvpn no cometió este error. Y sin "enlace de alcance", mi script original también funciona.
(¿Cómo descubrí esto, me preguntas? Al ejecutar
strace
el proceso openvpn, configurar hexadecimal todo lo que lee del descriptor de túnel y luego decodificar manualmente los encabezados de los paquetes).fuente
El error al intentar crear los dispositivos veth es causado por un cambio en la forma de
ip
interpretar los argumentos de la línea de comandos.La invocación correcta de
ip
para crear un par de dispositivos veth es(
name
instad dedev
)Ahora, ¿cómo sacar el tráfico del espacio de nombres al túnel VPN? Como solo tiene dispositivos tun a su disposición, el "host" debe enrutar. Es decir, crear el par veth y poner uno en el espacio de nombres. Conecte el otro a través del enrutamiento al túnel. Por lo tanto, habilite el reenvío y luego agregue las rutas necesarias.
Por ejemplo, supongamos que
eth0
es su interfaz principal,tun0
es su interfaz de túnel VPN yveth0
/ oveth1
el par de interfaces de las cualesveth1
está en el espacio de nombres. Dentro del espacio de nombres agrega solo una ruta predeterminadaveth1
.En el host que necesita emplear el enrutamiento de políticas, consulte aquí, por ejemplo. Qué necesitas hacer:
Agregar / agregar una entrada como
a
/etc/iproute2/rt_tables
. Con esto, puede llamar a la tabla (aún por crear) por nombre.Luego use las siguientes declaraciones:
No puedo probar eso aquí con una configuración como la tuya, pero esto debería hacer exactamente lo que quieres. Puede aumentar eso mediante reglas de filtro de paquetes de modo que ni la red vpn ni la red "invitada" se vean perturbadas.
Nota:
tun0
en primer lugar, pasar al espacio de nombres parece ser lo correcto. Pero como tú, no conseguí que eso funcionara. El enrutamiento de políticas parece ser el siguiente paso correcto. La solución de Mahendra es aplicable si conoce las redes detrás de la VPN y todas las demás aplicaciones nunca accederán a esas redes. Pero su condición inicial ("todo el tráfico, y solo el tráfico, hacia / desde procesos específicos pasa por la VPN") suena como si este último no se puede garantizar.fuente
Si conoce las redes a las que accede a través de la VPN, puede editar su tabla de enrutamiento para lograr lo que desea.
Tenga en cuenta su ruta predeterminada actual.
# ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024
Ejecute VPN y esto introducirá una entrada de enrutamiento.
Elimine la ruta predeterminada actual (que agrega la VPN) donde, como la ruta predeterminada anterior, será la primera entrada predeterminada en la tabla.
# 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
Agregue rutas personalizadas a las redes que se encuentran en la VPN para enrutar a través de tun0.
# ip route add <net1>/16 dev tun0
# ip route add <net2>/24 dev tun0
Ahora todas las conexiones net1 y net2 pasarán por la VPN y el reinicio irá directamente (a través de wlo1 en este ejemplo).
fuente