DEV Community

Cover image for Comprendiendo cómo funciona OpenVPN y configurar un Split Tunnel basado en usuarios (NixOS)
Federico Jensen
Federico Jensen

Posted on

Comprendiendo cómo funciona OpenVPN y configurar un Split Tunnel basado en usuarios (NixOS)

Ninguna IA ha estado involucrada en la creación de este artículo.

Introducción

En este artículo veremos, detalladamente, como OpenVPN manipula el networking de nuestro Linux para lograr la "magia" que conocemos como Full Tunnel (conexión VPN típica). Luego hablaremos sobre los peligros del DNS Leak y como solucionarlo. Finalmente revisaremos como podemos usar iproute2 para lograr tener un Split Tunnel basado en una división de trafico dado el usuario (UID) que genere el trafico.

Este artículo es extenso y detallado, no asume grandes conocimientos previos; y esta redactado en una forma secuencial, aclaratoria y reflexiva. O sea, si estas con prisa, aquí no encontraras un código para "copiar y pegar"; esto mas bien es una exploración profunda del funcionamiento de redes, Linux y los VPNs.

A considerar, en este artículo:

  • No se asumen conocimientos avanzados de redes, por tanto, pasamos bastante tiempo explicando de manera sencilla y prolongada determinados términos que ya podrías conocer perfectamente.
  • Uso la distribución de Linux conocida como NixOS. Si usan otra distribución este artículo puede ser igualmente útil, pero seguramente deberán adaptar los ejemplos.
  • No se usa el firewall (iptables/nftables) para lograr el Split Tunnel, solo iproute2.
  • Usare como proveedor de VPN (servidor), los servicios de pago de AirVPN (no he recibido compensación económica de ellos para este post). Es muy poco probable, pero si usan otro servidor VPN, podrían tener que adaptar algunos de mis ejemplos a su situación particular.

Vamos a ello!


Contexto

Generalmente al usar OpenVPN, como cliente de VPN, nuestra red se configurara de forma automática para que todo el trafico sea cifrado y enviado por un "túnel". Este túnel es un concepto abstracto y que, aunque es una buena analogía, entender realmente cómo funciona puede ser mas complejo.

La "entrada al túnel" sera nuestro computador y la "salida del túnel" algún lugar perteneciente al servidor VPN donde nos estemos conectado. Realmente la "entrada al túnel" es mas que una interfaz de red virtual configurada con una serie de reglas de ruteo, que canalizan la gran mayoría de nuestras conexiones salientes para que sean cifradas hasta una locación remota. Esa locación remota, "la salida del túnel", es un servidor VPN que toma dichos conexiones salientes y las direcciona a internet. Luego las respuestas de internet retornan al servidor VPN y este realiza el proceso inverso hasta nuestro equipo.

De esta manera, "ante el mundo", nosotros estamos donde esta el servidor VPN y no en en nuestra locación real. Adicionalmente, a ojos del ISP (empresa que nos provee de internet) el trafico esta totalmente ofuscado, siendo imposible que puedan rastrear realmente el contenido de los datos transferidos, como tampoco su destino (ojo, esto solo si tenemos todo bien configurado... vamos a ver algo que se llama DNS Leak, que puede "romper" parte del anonimato).

Este caso que he expuesto es el mas común, desear que todo el trafico del equipo use el túnel; esto se denomina Full Tunnel. Esta configuración es la que generalmente se aplica de forma automática por OpenVPN u cualquier otro cliente de VPN. Pero existe otro tipo de implementación un cliente de VPN, la de Split Tunnel.

En Split Tunnel dividimos el trafico siguiendo una o varias reglas. Si la regla se cumple, el trafico usará el túnel; y en caso que no, el trafico saldrá de alguna otra forma a internet. Esta "otra forma" puede ser directamente a internet (como normalmente), o también, podría ser algun otro túnel paralelo administrado por otro cliente de VPN.

Las "reglas" que definen como dividimos el trafico pueden ser variadas. En este artículo nos centraremos específicamente en una división basada en UID (IDs de usuarios de Linux). O sea, que solo el trafico generado por un usuario especifico sea enrutado por el túnel, el resto que salga a internet de forma directa.

Dependiendo de la complejidad de las "reglas", la forma de implementarlas puede recurrir pequeñas o grandes manipulaciones de nuestro networking. En esta guiá veremos que solo usando las tablas de ruteo y sus regla se puede lograr el Split Tunnel. No usaremos módulos de firewall como iptables o nftables; algo que podría ser necesario si las reglas que deseamos aplicar para la división de trafico fueran mas complejas que solo UIDs.

Durante las ultimas semanas he estado implementado una configuración de Split Tunnel para lograr tener funcionando, en paralelo, OpenVPN y Tailscale. En este proceso he aprendido bastante sobre que sucede en nuestro sistema, a nivel de redes, cuando estos clientes se auto-configuran. En este artículo espero aclarar dudas de lo que realmente sucede en nuestro networking.

En el próximo artículo podrán encontrar las siguientes secciones:

  1. Examinación profunda de como OpenVPN configura de forma automática nuestro networking para darnos un Full Tunnel.
  2. Sobre DNS, OpenVPN, el peligro del DNS Leak y como arreglarlo.
  3. Proceso para desactivar las configuraciones automáticas de OpenVPN, dándonos la oportunidad de manualmente definir el networking del túnel.
  4. Paso a paso de los comandos necesarias para lograr tener un Split Tunnel con OpenVPN basado en UID. Con una explicación clara de las configuraciones de networking creadas y de los métodos para probar su correcto funcionamiento.
  5. Uso de un Script para automatizar los procesos de generar un Split Tunnel en nuestro NixOS.

Si ustedes quieren ir directamente al tema del Split Tunnel, pueden ignorar la parte 1 y 2 del artículo.

Por ultimo recordar que estaré usando NixOS, mi distribución de Linux favorita para Home Servers. Pero en general, todo la información presentada en este artículo debería ser fácilmente "traducible" a otras distribuciones siempre que estas usen iproute2 para la gestión del networking y systemd para la gestión de servicios.


Explorando la configuración por defecto de OpenVPN y el Full Tunnel

El cliente de OpenVPN es bastante flexible en su funcionamiento. Esto quiere decir que realmente la manera en la cual establece su conexión con el servidor VPN y la forma el la cual manipule nuestro networking para crear el túnel es variada. Esta se ajustara a: 1) lo especificado en el .ovpn (archivo de configuración de la conexión OpenVPN) y 2) las configuraciones remotas que el servidor VPN "inyecte" en nuestro equipo (ya veremos mas de esto...).

Nosotros nos pondremos en una situación "convencional", donde el servidor VPN al cual nos estamos conectados pertenece a un proveedor de pago. En mi caso siempre he usado AirVPN, así que este artículo basará sus puntos en el uso de este servicio. De todos modos, independiente de lo anterior, en general no deberían encontrar gran diferencia si usan otro proveedor... siempre y cuando permita el uso del cliente OpenVPN, claro.

Instalando y configurando OpenVPN

OpenVPN solo necesita del archivo .ovpn para funcionar. Este archivo contiene toda la información de lo que debe hacer OpenVPN para: conectarse, autentificarse y establecer el túnel con el servidor VPN. En mi caso ya he generado el archivo .ovpn desde la interfaz web de AirVPN. Y lo he guardado en /etc/nixos/airvpn.ovpn. Su contenido se ve así:


client
dev tun
remote america3.vpn.airdns.org 443
resolv-retry infinite
nobind
persist-key
persist-tun
auth-nocache
verb 3
explicit-exit-notify 5
push-peer-info
setenv UV_IPV6 yes
remote-cert-tls server
comp-lzo no
data-ciphers AES-256-GCM:AES-256-CBC:AES-192-GCM:AES-192-CBC:AES-128-GCM:AES-128-CBC
data-ciphers-fallback AES-256-CBC
proto udp
auth SHA512
...
<...certificados...>
...
Enter fullscreen mode Exit fullscreen mode

Nota 1: He omitido mostrar los certificados contenidos en mi .ovpn... claramente.

Nota 2: Puede ser buena idea comparar su .ovpn con el mio. Si ven que su cliente OpenVPN no se comporta como yo explico a continuación, seguramente son diferencias en lo declarado en este archivo de configuración.

Por otro lado, he instalado OpenVPN en mi NixOS modificando el configuration.nix y he usado el nombre airVPN como nombre de la instancia de OpenVPN.

...
services.openvpn = {
  servers = {
    airVPN = {
      config = "config /etc/nixos/airvpn.ovpn"; 
    };
  };
};
...
Enter fullscreen mode Exit fullscreen mode

Pueden ver que simplemente estoy pasando la ruta del .ovpn al parámetro config. NixOS se encargará, automáticamente, de crear un servicio (administrado por systemd) para iniciar y conectar OpenVPN al servidor VPN indicado en el .ovpn durante el inicio del sistema. El servicio llevará por nombre uno asignado por NixOS; en mi caso el servicio ha quedado con el nombre: openvpn-airVPN.service.

Con todo eso listo, se puede usar el comando sudo nixos-rebuild switch para crear una nueva generación de NixOS. Posterior a un reinicio del sistema, al usar systemctl status openvpn-airVPN.service vamos a poder confirmar que OpenVPN esta andando correctamente.

Servicio de OpenVPN activo y funcionando

Si entramos a una web para ver nuestra IP, verán que, a los ojos del mundo, estamos en una locación remota y adicionalmente no existirá datos de nuestra ISP:

IP anonima segun my-ip.cc

Por ultimo, podemos hacer una prueba desde la terminal y deberían ver un resultado muy similar:

curl https://ipv4.ipleak.net/json/

OpenVPN esta funcionando perfectamente en modo Full Tunnel :)

Analizando lo que ha manipulado OpenVPN durante su inicio en un escenario de funcionamiento "normal"

¿Que ha "tocado" OpenVPN para darnos acceso a internet "mágicamente" al otro lado del mundo?. Una forma fácil de investigar esto es usar el comando journalctl -b | grep 'openvpn'. Con este vamos a ver los logs de OpenVPN emitidos desde el ultimo inicio del sistema.

Estos logs muestran cosas... muchas cosas... pero para entenderlo solo nos enfocaremos en lo principal. De manera secuencial iremos revisando los logs y haciendo coherencia de que esta pasando en nuestro equipo. Vamos a ello.

Nota: Solo muestro los segmentos de logs mas relevantes para nuestro análisis.

1) Inicia nuestro OpenVPN en la versión 2.6.14 usando OpenSSL 3.4.1.

openvpn[945]: OpenVPN 2.6.14...
openvpn[945]: library versions: OpenSSL 3.4.1...
Enter fullscreen mode Exit fullscreen mode

2) Primero OpenVPN debe establecer comunicación con el servidor VPN. Para eso usa la información indicada en la directriz remote del .ovpn. Generalmente aquí esta la IP del servidor VPN, en mi caso es un hostname (otra opción valida). Así que primero OpenVPN usará la red normal (y el DNS que tengamos configurado en nuestro equipo) para resolver el dominio. Luego de un par de intentos, mi OpenVPN logra resolver el hostname en la IP 184.75.223.237:443. Esta es la ip:port donde AirVPN me esta "ofreciendo" un servidor VPN y es el lugar con el cual el cliente OpenVPN va a intentar establecer comunicación.

openvpn[945]: RESOLVE: Cannot resolve host address: america3.vpn.airdns.org:443...
openvpn[945]: Restart pause, 1 second(s)
...
openvpn[945]: UDPv4 link remote: [AF_INET]184.75.223.237:443
Enter fullscreen mode Exit fullscreen mode

Nosotros denominaremos a 184.75.223.237:443 como: la dirección pública del peer/servidor VPN remoto.

3) OpenVPN realiza una prueba de nuestras interfaces de red y busca "como" salir a internet. Si notamos la linea net_route_v4_best_gw veremos que OpenVPN ha decidido usar mi interfaz enp0s3, con la gateway asociada 192.168.1.1, para alcanzar internet. Luego de esto, OpenVPN procede a autentificarse con el peer remoto usando los certificados encontrados en el .ovpn. Finalmente OpenVPN se ha conectado de forma exitosa y segura con el servidor VPN de AirVPN.

Importante de identificar en este segmento: mi interfaz ethernet, con la cual tengo internet en mi servidor, tiene por nombre enp0s3. Y esta pertenece, como es de esperar, a una red local privada (mi hogar); donde la gateway (router) tiene la IP de 192.168.1.1. Si los paquetes son enrutados a esta IP, mediante enp0s3, salen a internet.

openvpn[945]: TLS: Initial packet from [AF_INET]184.75.223.237:443...
openvpn[945]: net_route_v4_best_gw result: via 192.168.1.1 dev enp0s3
openvpn[945]: Validating certificate extended key usage
openvpn[945]: Peer Connection Initiated with [AF_INET]184.75.223.237:443
openvpn[945]: TLS: tls_multi_process: initial untrusted session promoted to trusted
Enter fullscreen mode Exit fullscreen mode

4) Es importante aclara, a pesar del punto 3), que OpenVPN aun no tiene un túnel creado. Solo se ha autentificado con el servidor VPN de AirVPN y tiene la capacidad e intercambiar información para proseguir el proceso de configuración.

Ahora viene lo interesante: El servidor VPN enviá, de forma remota, hacia nosotros una serie de configuraciones. Esto se denomina PUSH. Dicho de otra manera, el servidor remoto nos dará una serie de instrucciones de "como proceder" con la configuración local de nuestro equipo para habilitar un túnel que nos permita salir a internet a través del servidor remoto.

openvpn[945]: PUSH: Received control message:...
openvpn[945]: OPTIONS IMPORT...
openvpn[945]: OPTIONS IMPORT...
...
Enter fullscreen mode Exit fullscreen mode

Si analizamos el contenido del PUSH encontraremos la información clave. Aquí he destacado los tres parámetros mas importantes:

-ifconfig 10.10.238.249 255.255.255.0: Es la IP y mascara que serán asignadas a la interfaz virtual que será creada en nuestro equipo (entrada al túnel). Si usamos ambos valores IP y mascara podemos calcular la red que conforma el túnel: 10.10.238.0/24.
-route-gateway 10.10.238.1: Es la IP de la gateway donde la interfaz virtual debe enviar sus conexiones para salir a internet (salida del túnel). Como de es de esperar de una gateway, este es la primera IP del rango 10.10.238.0/24.

  • dhcp-option DNS 10.6.214.1: Es la IP(v4) del servidor DNS que nuestro proveedor de VPN ofrece. Veremos este tema, con mas detall, en un capitulo posterior. Generalmente tiene el mismo valor que la IP de la gateway.

Estos valores serán usados para manipular el networking de nuestro equipo y es importante no olvidarlos.

5) Ahora OpenVPN implementará las configuraciones que han llegado por el PUSH para setear nuestra red. En primer lugar volverá a confirmar la interfaz física por donde puede salir a internet. Como vimos antes, en la punto 2), en mi caso es la interfaz ethernet enp0s3, con gateway 192.168.1.1. En esta comprobación también veremos la mascara que usa dicha IP, en mi caso 255.255.255.0, por lo cual podemos calcular la red local: 192.168.1.0/24 (la típica de toda la vida).

Posteriormente OpenVPN creará una interfaz de red virtual. Esta, por defecto, lleva el nombre de tun0. Seguidamente le fijará a tun0 la IP indicada por las instrucciones PUSH (ifconfig): 10.10.238.249/24. Y finalmente la pasará a estado "up" (encenderá la interfaz).

openvpn[945]: ROUTE_GATEWAY 192.168.1.1/255.255.255.0 IFACE=enp0s3...
openvpn[945]: TUN/TAP device tun0 opened
openvpn[945]: net_addr_v4_add: 10.10.238.249/24 dev tun0
openvpn[945]: net_iface_up: set tun0 up
Enter fullscreen mode Exit fullscreen mode

Es importante destacar que en este ultimo paso de encender la interfaz tun0 (paso a "up"), el Kernel de Linux automáticamente añadirá, a las tablas de ruteo del sistema, la información para que dicha interfaz quede asociada a la red correcta. Esta configuración (entre otra) la podemos ver usando ip route, y se ve así:

10.10.238.0/24 dev tun0 proto kernel scope link src 10.10.238.249

Esto se lee como: "cualquier conexión que vaya a alguna dirección del rango 10.10.238.0/24, enviala por la interfaz tun0 y usa la IP de origen (src) 10.10.238.249". Con esto básicamente podemos hablar con cualquier dispositivo de la red 10.10.238.0/24 mediante tun0, incluyendo la gateway, que justamente esta en 10.10.238.1.

Nota: También se añade otro ruteo, un poco mas oculto de detectar: local 10.10.238.249 dev tun0 proto kernel scope host src 10.10.238.249. Este se puede observar en la tabla de rutas "local": ip route show table local. En esta tabla de rutas están centralizados los ruteos relacionados a paquetes localhost y bradcast. Esta regla se lee como: "cualquier trafico local dirigido a 10.10.238.249 envialo a tun0 con IP de origen 10.10.238.249". Simplemente permite que nos podemos auto-enviar trafico a notros mismos por tun0 si usamos nuestra propia IP.

6) Posteriormente a encender tun0 y tenerla conectada a la red 10.10.238.0/24, OpenVPN utilizará otras instrucción, indicadas en el PUSH, para configurar nuestras rutas. Estas configuraciones permitirán "focalizar" (casi*) todas las conexiones de nuestro equipo a tun0en vez de enp0s3 (la interfaz normal). En el proceso, se indicara específicamente la gateway e interfaz a usar. Por ultimo, también se añadirá una regla para que el propio túnel funcione correctamente.

openvpn[945]: net_route_v4_add: 184.75.223.237/32 via 192.168.1.1 dev [NULL] table 0 metric -1
openvpn[945]: net_route_v4_add: 0.0.0.0/1 via 10.10.238.1 dev [NULL] table 0 metric -1
openvpn[945]: net_route_v4_add: 128.0.0.0/1 via 10.10.238.1 dev [NULL] table 0 metric -1
Enter fullscreen mode Exit fullscreen mode

Si usamos ip route veremos estas reglas ya implementadas en nuestras tablas de ruteo (ignoremos otros ruteos que puedan aparecer en la tabla por el momento).

  • 0.0.0.0/1 via 10.10.238.1 dev tun0 y 128.0.0.0/1 via 10.10.238.1 dev tun0: Estas dos se usan en combinación y forman parte de un "truco" que usan muchos clientes de VPN.

Explico el "truco": Nosotros queremos enviar TODO el trafico generado por el sistema mediante tun0 a la gateway correspondiente, y normalmente para decir "todas las IPs" usaríamos un ruteo como 0.0.0.0/0 via 10.10.238.1 dev tun0. 0.0.0.0/0 es literalmente TODO absoluto. El ruteo se leería como: "enviá el todo absoluto a la gateway 10.10.238.1 mediante la interfaz tun0". Pero resulta que ese ruteo es "tan poderosa" que no puede convivir con otros ruteos mas granulares que podríamos (y necesitamos) ocupar. La solución es dividir el "ruteo absoluto" en dos. Usando de forma combinada el rango 0.0.0.0/1 y 128.0.0.0/1 virtualmente estamos cubriendo, a igual que con 0.0.0.0/0, todas las conexiones salientes. Pero ahora el sistema si analizará posibles otros ruteos (configurados en la misma tabla) que podrían definir el uso de rutas alternativas. Nuevamente, es solo un truco, porque si usamos 0.0.0.0/0 el sistema simplemente descartá cualquier otro ruteo definido en la misma tabla.

O sea, podemos leer estas declaraciones de ruteo como: "Todo trafico que salga dirigido a una IP perteneciente al rango 0.0.0.0/1 o 128.0.0.0/1 (todas las IPs existentes), envialo a la gateway 10.10.238.1 mediante la interfaz tun0 al menos que otro ruteo (en la misma tabla) diga lo contrario".

  • 184.75.223.237 via 192.168.1.1 dev enp0s3: Esta regla se lee como: "Si hay paquetes que se dirigen a 184.75.223.237, usa la gateway 192.168.1.1 mediante la interfaz enp0s3". Si recordamos 184.75.223.237 es la IP publica del servidor VPN remoto. Con esto logramos evitar que el trafico al servidor VPN sea canalizado por dentro del propio túnel (lo cual generaría un problema recursivo). Visto de otra manera: "Que todo trafico use tun0 para ir a internet, pero usa enp0s3 para mantener la conexión del túnel al servidor VPN".

7) Con las configuraciones listas, el sistema termina de activar el canal de datos y finalmente setea un timer para verificar constantemente que el túnel esta activo y funcionando.

openvpn[945]: Initialization Sequence Completed
openvpn[945]: Data Channel: cipher 'AES-256-GCM', peer-id: 2, compression: 'stub'
openvpn[945]: Timers: ping 10, ping-restart 60
openvpn[945]: Protocol options: explicit-exit-notify 5
Enter fullscreen mode Exit fullscreen mode

Revisando las tablas de ruteo

Veamos el panorama completo. Ahora que OpenVPN ya ha iniciado, revisemos la totalidad de nuestras reglas y tablas de ruteo.

Si hacemos ip rule para ver las reglas:

0:  from all lookup local
32766:  from all lookup main
32767:  from all lookup default
Enter fullscreen mode Exit fullscreen mode

Esta son las reglas que el sistema operativo revisa para determinar que tabla de ruteo usar. Cada paquete que el equipo debe enrutar es leído (sus propiedades) y se busca un match usando las tablas listadas. Se revisan en orden de prioridad, de menor a mayor número. La tabla local (que siempre es la primera con valor 0) tiene en su interior los ruteos de paquetes localhost y brodcast. La podemos ignorar, y aunque tiene una gran importancia en el funcionamiento de red, no viene a tema por ahora. La segunda tabla, main, es la tabla principal y, por defecto, es la que contiene los ruteos de todas las conexiones salientes del equipo. La ultima tabla, default, cumple la función de dar termino y bloquear cualquier paquete que no sea coherente con las reglas especificadas hasta el momento, por eso siempre lleva el numero mas grande (32767).

Ahora veremos el contenido de la tabla de ruteo main usando ip route (por defecto al usar ip route vemos la tabla main):

0.0.0.0/1 via 10.10.238.1 dev tun0 
default via 192.168.1.1 dev enp0s3 proto dhcp src 192.168.1.13 metric 100 
10.10.238.0/24 dev tun0 proto kernel scope link src 10.10.238.249 
128.0.0.0/1 via 10.10.238.1 dev tun0 
184.75.223.237 via 192.168.1.1 dev enp0s3 
192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.13 metric 100 
Enter fullscreen mode Exit fullscreen mode

Dentro de una tabla de ruteo se busca una coincidencia entre las rutas listadas y las propiedades del paquete de datos que sale a la red. Si hace match, se usa dicha ruta para el paquete. Las decisiones se toman de menor nivel de granularidad de IP a mayor. O sea, un ruteo que especifique por donde enviar trafico de UNA sola IP (Ej: 184.75.223.237) tiene mas poder de prioridad que un ruteo que engloba un rango de IPs (Ej: 10.10.238.0/24). Y una mascara pequeña (Ej: 128.0.0.0/1) tiene menos prioridad que una mascara grande (Ej: 192.168.1.0/24). Existen 2 excepciones. El uso de 0.0.0.0/0 domina a cualquier otro ruteo y es absoluto. Y la palabra especial default se interpreta como: "si ninguna otra ruta de esta tabla hace match con el paquete, usa esta ruta". Por ultimo, si una tabla de ruteo no tiene default y un paquete no encuentra una ruta a usar porque no existe match, el paquete "sale" de dicha tabla de ruteo y pasa a usar la siguiente listada en las reglas (ip rule).

Analicemos los ruteos. Existen dos rutas que ya estaban en la tabla main, incluso antes que OpenVPN iniciara, y que quiero mencionar:

  • default via 192.168.1.1 dev enp0s3 proto dhcp src 192.168.1.13 metric 100: Este ruteo es, por defecto, el que ha sido agregado mediante DHCP para que nuestra interfaz ethernet enp0s3 tenga acceso a internet. Se puede leer como: "Por defecto, si no hay otro match en esta tabla, enviá todo el paquete a la gateway 192.168.1.1 mediante la interfaz enp0s3 con una IP de origen de 192.168.1.13 (la IP local de nuestro equipo)". Sin este ruteo, nuestra interfaz física simplemente no funciona, no tendríamos internet y OpenVPN ni siquiera podría entablar la comunicación inicial con el servidor.

  • 192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.13 metric 100: Este segundo ruteo es añadido automáticamente, por el Kernel, cuando la interfaz enp0s3 pasa a estado "up" (durante el inicio del equipo). Y sirve para poder comunicarnos con los otros dispositivos de la red local, incluyendo la gateway correspondiente. Se puede leer como: "Todo trafico saliente dirigido a una de las IPs del rango 192.168.1.0/24 envialo por la interfaz enp0s3 usando la IP de origen 192.168.1.13 (la IP local de nuestro equipo)". Es importante notar que este ruteo es el que permite usar dispositivos en nuestra red local como impresoras, a pesar de estar conectados a la VPN. Ojo: esto es lo que provoca que el trafico local no sea considerado por los ruteos 0.0.0.0/1 y 128.0.0.0/1 del VPN, sino que se mantenga por fuera del túnel. Recordemos el orden de prioridad: /1 menos prioridad que /24. Nota: este ruteo, aunque es generado por el Kernel, es parte configurado gracias a DHCP.

¿Que es es DHCP?. En resumen es el método que usa nuestro equipo, cuando se inicia, para coordinar con nuestro router como integrarse a la red local y acceder a internet. Mas específicamente el protocolo DHCP ayuda a coordinar: la red local, su gateway y la IP que deberá tener cada dispositivo dentro de dicha red. Y, dependiendo de la configuración, también configura el servidor DNS que nuestro equipo usara para resolver hostnames (luego hablaremos mas de esto y como se relaciona a DNS Leak).

Los demás ruteos ya los mencionamos en su momento, pero ahora cobran mas sentido sabiendo las reglas de prioridad de la tabla y como funcionan:

  • 184.75.223.237 via 192.168.1.1 dev enp0s3: Un ruteo de alta prioridad porque especifica una sola IP (184.75.223.23), en este caso la del servidor VPN. Este trafico debe ser enviado por enp0s3 para evitar problemas de recursividad.

  • 10.10.238.0/24 dev tun0 proto kernel scope link src 10.10.238.249: Ruteo que explica que cualquier trafico a 10.10.238.0/24 (red del túnel), debe usar la interfaz tun0 con la IP origen 10.10.238.249.

  • 0.0.0.0/1 via 10.10.238.1 dev tun0 y 128.0.0.0/1 via 10.10.238.1 dev tun0: Dos reglas de ruteo, que en combinación, ayudan a hacer match con todos los paquetes (a excepción que otro ruteo de la misma tabla tenga match) para que sea enviado por tun0.

Antes de finalizar esta sección, quiero que reflexionemos en algo. Aunque esta modalidad de VPN se denomina "Full Tunnel", o sea, todo el trafico se va por la VPN... la verdad es que no y lo podemos ver. Si el trafico va a la red local (192.168.1.0/24), no sale por tun0. Si el trafico va específicamente a 184.75.223.237 (servidor VPN), tampoco usa tun0. Y, aunque no lo vimos en detalle, también están varias reglas de localhost y multicast que queda fuera del túnel (tabla local). Por tanto, realmente no existe nada como un "Full Tunnel", de alguna manera, siempre ha sido un "Split Tunnel" con reglas de división basadas en IPs.

Nosotros ahora, simplemente, tomaremos el control del networking y configuraremos un "Split Tunnel" para dividir el trafico dada nuestras necesidades.


Sobre el DNS y DNS Leak con OpenVPN

Ahora nos iremos por una tangente, nada que ver con el Split Tunnel, pero extremadamente importante si deseas que OpenVPN realice su trabajo correctamente.

Hablemos de DNS, Linux, OpenVPN y DNS Leak

¿Que es el DNS y su gran dilema en Linux?

Este tema es como un iceberg. Ni siquiera se como abordar la explicación... partamos desde lo básico. Nuestro sistema operativo cada vez que debe usar una dirección web, ejemplo: google.com, debe "resolver" dicho domino (o hostname) a una (o varias) IPs. Para lograr esto, el sistema operativo se comunica con un servidor de resolución de DNS. Existen miles en el mundo (y su propia estructura y funcionamiento es bien interesante, pero fuera del alcance de este artículo). Estos servidores DNS tienen tablas enormes donde están todos los dominios y su/s IPs correspondientes.

¿Pero cual servidor usa nuestro sistema operativo cuando debe resolver un hostname?... bueno... el servidor DNS esta en internet... por tanto también tiene una IP, así que nuestro sistema operativo debe saber esa IP y tenerla guardada en algún lugar... ¿no?

Nota: claramente el servidor DNS no puede tener un dominio, ejemplo "super-server-dns.org"; porque ese dominio debería también resolverse de alguna manera. Así que los servidores DNS solo se identifican con IP, no con hostname.

La cosa es que a través de los años de evolución de: Linux, su Kernel y las diversas distribuciones; se ha generado una confusión HORRIBLE!. Hoy en día es sumamente confuso saber donde esta configurado el DNS en Linux y que mecanismos se usan para definir y leer ese valor. Si no me creen, miren este maravilloso post de Tailscale donde un pobre hombre ha tratado de explicarlo. No pretendo darle sentido.

En resumen, existen muchas piezas en el sistema que se mueven y coordinan para configurar el DNS. Y también, luego, para rescatar dicha configuración cada vez que se necesite resolver un hostname. Esas piezas y mecanismos dependerán de su distribución de Linux, versión y paquetes instalados.

En NixOS, por defecto, el principal lugar donde debemos observar para saber a que servidor DNS estamos apuntando es: /etc/resolv.conf. Si hacemos cat /etc/resolv.conf veremos la IP del servidor DNS que nuestro sistema va a usar cada vez que trate de resolver un hostname. Existen 2 servicios que tienen un control sobre este archivo: networkmanager y resolvconf. Si desean editar y controlar su DNS, se deben usar directamente estos servicios para editar las configuraciones o recurrir a programa que se integre con esta arquitectura (como es el caso de OpenVPN que veremos a continuación).

DNS y OpenVPN

Nosotros esperaríamos que al usar un Full Tunnel nuestras actividades quedaran ocultadas ante ojos del ISP (proveedor de internet) ¿no?. Pues, podemos tener un túnel perfectamente funcionando con OpenVPN y aun así, el ISP ver perfectamente a donde nos estamos conectando. ¿Como? Sucede cuando el DNS de nuestro sistema operativo esta configurado para usar un servidor DNS que se encuentra fuera del dominio de conexiones que OpenVPN esta canalizando por el túnel. Esto se denomina DNS Leak y es mas común de lo que piensas.

Realicemos algunas pruebas.

Con su OpenVPN encendido (con configuraciones automáticas para lograr un Full Tunnel) podemos entrar a la web ipleak.net. Podrán ver que su IP publica corresponde al servidor remoto VPN donde estemos conectado. Pero un poco mas abajo, en "DNS Addresses", claramente información relacionada a ustedes. Este es un indicador que su sistema esta consultado la resolución de hostnames a la infraestructura de su ISP. DNS Leak detectado.

Si revisan que IP de servidor DNS esta usando su sistema con: cat /etc/resolv.conf seguramente verán la IP de su gateway de la red local. O sea, su router. Esto quiere decir que su equipo, cada vez que resuelve un dominio, consulta a su router por la IP de la pagina web. Luego, el router, consulta siguiendo una serie de procesos a diversos servidores del ISP.

La IP encontrada en /etc/resolv.conf es configurada automáticamente por su NetworkManager cuando el sistema de DHCP asigna la IP local a su interfaz física.

Nosotros NO queremos bajo ninguna motivo usar un DNS que pertenezca a la ISP. ¿que soluciones tenemos? Existen 2 alternativas relativamente simple de aplicar:

  1. Configurar, mediante su NetworkManager, un DNS publico con buena reputación como: 1.1.1.1, 8.8.8.8 o 9.9.9.9. Luego de hacer esto las resoluciones de hostname pasaran, por el interior del túnel, hasta dichos servidores DNS. El ISP ya no tendrá la posibilidad de ver a donde están accediendo. En NixOS es posible ajustar estos DNS mediante el configuration.nix.

  2. Configurar OpenVPN para que setee, en nuestro sistema, el servidor DNS que nuestro propio proveedor de VPN tiene. En mi caso, AirVPN ofrece a todos sus usuarios un servidor DNS interno mantenido por ellos.

Nosotros usaremos la opción 2, el DNS del proveedor VPN.

Activarlo es muy sencillo en NixOS, solo debemos añadir updateResolvConf = true a la configuración de OpenVPN en configuration.nix:

...
services.openvpn = {
  servers = {
    airVPN = {
      ...
      updateResolvConf = true;
      ...
    };
  };
};
...
Enter fullscreen mode Exit fullscreen mode

Nota: Al activar updateResolvConf, que por defecto esta desactivada, NixOS usara la técnica de update-resolv-conf para automáticamente ajustar el valor de /etc/resolv.conf al DNS indicado en las instrucciones PUSH que OpenVPN ha recibido desde el servidor VPN.

Ahora creamos una nueva generación de NixOS y reiniciamos el sistema.

Si volvemos a acceder a ipleak.net veremos que no solo nuestra IP publica corresponde a la del servidor VPN, sino que también el servidor DNS. De esta manera hemos logrado evitar el DNS Leak.

También podemos verificar usando cat /etc/resolv.conf que ahora esta la IP correspondiente a la gateway de tun0. Generalmente es la IP de la gateway del túnel donde reside el servidor DNS (Ojo, puede que en otros proveedores de VPN sea distinto)

Si tienen dudas de cual es la IP del servidor DNS que su proveedor VPN tiene, la forma mas segura de averiguarlo es entrar log de OpenVPN (journalctl -b | grep "openvpn") y buscar las instrucciones PUSH. Generalmente la IP del servidor VPN esta en un argumento llamado dhcp-option.

Nota: Quiero mencionar que, por defecto, todas las consultas DNS no están cifradas. Si las consultas no usan un túnel, el contenido puede ser fácilmente analizado por el ISP para saber a que dominios se están conectado. Esto incluso si usan 1.1.1.1 o 8.8.8.8. Existen tecnologías para evitar esto como DoT, DoH y DNSCrypt. Por tanto siempre es prioritario asegurar que todas sus resoluciones DNS usen el túnel, de esta manera estarán cifradas y serán ilegibles por el ISP.

Si no usas NixOS

Si no usan NixOS, puede que en su sistema se encuentren con el DNS correctamente configurado desde un principio. En caso contrario, dependerá de su distribución la forma de solucionar el DNS Leak. La mejor manera de afrontarlo es asegurar que OpenVPN este usando la implementación update-resolv-conf o update-systemd-resolved. Puede leer mas del tema aquí.


Configurando nuestro Split Tunnel

"Split Tunnel" es la capacidad de enviar trafico por dentro o fuera del túnel de VPN usando una serie de reglas/ruteos que lo dividan. Esto se puede lograr de diversas maneras. Entre ellas podemos encontrar dos métodos que no requieren el uso de paquetes adicionales a los ya incluidos en la mayoría de las distribuciones de Linux. El primero, y mas sencillo, es usando exclusivamente iproute2, el paquete de herramientas predeterminado para la gestión de redes. Y el segundo método es usando una combinación de iptables/nftables (el firewall) y iproute2. Aquí dejo una comparación de los dos métodos:

  • iproute2: Es comparativamente fácil de implementar. Solo nos permite usar reglas básicas para dividir el trafico, tales como reglas basadas en: IP de destino/origen, protocolo, puerto y usuario dueño del proceso que genera el trafico.
  • iptables/nftables + iproute2: Es mas complejo de configurar, ya que debemos coordinar las reglas y tablas de ruteo mas las reglas del propio firewall. Permite una división mucho mas exacta, basada no solo en lo anteriormente mencionado, sino también en: MACs, grupos de usuario, marcas de paquetes, procesos, tamaño de paquete, enrutamiento de forwarding...y otras cosas mas.

Nosotros nos enfocaremos solo en el método de iproute2, ya que sin dominar esto, es peligroso pensar en también querer usar iptables/nftables. De esta manera, nosotros editaremos nuestras tablas de enrutamiento y las reglas que las dominan y no tocaremos nada relacionado al firewall.

Nuestro objetivo sera dividir el trafico basado en usuarios. Si el trafico es generado por el usuario root o nixos (mi usuario normal), se enviara por fuera del túnel. Si el trafico es generado por el usuario vpn_user, se enviara por dentro del túnel.

Antes de comenzar dos puntos muy importantes:

1) Recordar que en todo este artículo estoy omitiendo el uso de IPv6 porque, sinceramente, aun no lo domino completamente. Así que las próximas configuraciones solo permitirán el uso del Split Tunnel con IPv4.

2) Ya que no tocaremos el firewall, vamos a asumir que las reglas actuales de su iptables/nftables permiten todo el trafico de salida (y respuestas a dicho trafico) sin restricciones de de interfaz, protocolos o IPs de origen/destino. Esto es importante ya que puede que ustedes digan: "no me funciona nada de lo que dice este artículo" y resulta que todo el tiempo ha sido su firewall ejecutando bloqueos. ¿Como saber si mi firewall afectará el Split Tunnel? Bueno, si puedes usar OpenVPN en modo "normal", o sea, con la configuración automática que revisamos el capitulo anterior; quiere decir que todo esta bien y no deberían tener problema tampoco al configurar el Split Tunnel.

Desactivando las manipulaciones automatices de OpenVPN

El primer paso sera cambiar la configuración de OpenVPN para que durante su proceso de inicio no manipule nuestras tablas/reglas de ruteo. Ni que tampoco encienda la interfaz virtual tun0. Ojo, que la cree, pero que no la configure ni encienda.

Para lograr esto debemos añadir 2 instrucciones a nuestro .ovpn:
-ifconfig-noexec: Provocará que el sistema no asigne IP ni encienda la interfaz virtual tun0. Solo la va a crear y la dejara apagada.
-route-noexec: Con esto evitaremos que OpenVPN manipule las tablas de ruteo y las reglas asociadas.

Normalmente deberíamos añadir estas dos instrucciones al .ovpn, pero en NixOS podemos añadirlas directamente en configuration.nix:

...
services.openvpn = {
  servers = {
    airVPN = {
      config = "config /etc/nixos/airvpn.ovpn \n ifconfig-noexec \n route-noexec";
      updateResolvConf = true;
    };
  };
};
...
Enter fullscreen mode Exit fullscreen mode

Luego de: guardar las configuraciones, crear una nueva generación de NixOS y reiniciar el OS; veremos que efectivamente OpenVPN inicia, pero sin activar tun0 y tampoco modificando nuestros ruteos. Podemos comprobarlo usando: ip addr, ip rule y ip route. De hecho, en la tabla de ruteo main, solo veremos los 2 ruteos por defecto que enp0s3 necesita para funcionar.

Lo otro que necesitaremos es crear el usuario: "vpn_user". Todo el trafico generado por este usuario usara el túnel de OpenVPN. Trafico generado por otros usuarios, como root o nixos no usaran el túnel. Yo he creado este usuario al "modo" NixOS, pero ustedes pueden hacerlo como corresponda en su distribución. Lo importante es tener claro el UID que tendrá este usuario. En mi caso usare el UID 1100. Así ha quedado mi configuration.nix:

...
users.users = {
  nixos = {
    isNormalUser = true;
    description = "nixos";
    extraGroups = [ "networkmanager" "wheel" ];
  };
  vpn_user = {
    isSystemUser = true;
    group = "vpn_user_g";
    uid = 1100;
  };
};
users.groups.vpn_user_g = {};
...
Enter fullscreen mode Exit fullscreen mode

Nota 1: En mi caso he creado a vpn_user como un usuario de sistema, aunque esto no es obligatorio (no tendrá shell ni carpeta personal). Por requisito de NixOS también he tenido que crear un grupo de usuario asociado a vpn_user, este grupo lo he llamado vpn_user_g.

Nota 2: También pueden ver en esta configuración la existencia del usuario nixos, el cual es el usuario normal. Ojo, el usuario root también existe, aunque no esta presente en ninguna parte del configuration.nix.

Para ver todos los usuarios y su correspondiente UID podemos usar: awk -F: '{ print $1, $3 }' /etc/passwd. Seguramente verán bastante mas usuarios de los esperados... la mayoría son del sistema de Linux y los diversos servicios que estén presente en el sistema. Los importantes a identificar en el listado son: el usuario normal que estén usando, en mi caso nixos con UID 1000, el usuario vpn_user, que acabamos de crear, con UID 1100 y el usuario root con UID 0.

Con esto listo podemos ponernos a configurar manualmente el networking.

Comandos de configuración

Es MUY importante destacar que el orden de uso de los comandos influye. Esto se debe a que ciertas cosas no puedan ser ejecutadas antes que otras. Y por otro lado, las reglas de ruteo tienen prioridades y ordenes de ejecución. Así que mucho cuidado con el orden.

Antes de empezar con los comandos necesitamos tener claro lo siguientes valores:

  • uid_range El rango de UID de usuarios que su trafico sera enviado por la VPN. En mi caso es "1100-1100" (si, es obligatorio que sea un rango; en caso de ser solo un usuario pueden iniciar y terminar el rango con el mismo UID).
  • openvpn_peer_ip La IP publica de servidor remoto de VPN. En mi caso es: 184.75.223.237.
  • tun0_host La IP que el servidor VPN a asignado a nuestra interfaz tun0. En mi caso es: 10.10.238.249.
  • tun0_gateway La IP de la gateway de la red del túnel. En mi caso: 10.10.238.1.
  • tun0_dns La IP del DNS del servidor VPN. Generalmente tiene exactamente el mismo valor que tun0_gateway. En mi caso: 10.10.238.1.
  • tun0_network El rango de la red que usa tun0 con la mascara incorporada en formato CIDR. En mi caso: 10.10.238.0/24.
  • enp0s3_network El rango de la red local que usa enp0s3 con la mascara incorporada en formato CIDR. En mi caso: 192.168.1.0/24.

Cada vez que OpenVPN inicia, estos valores podrían cambiar. ¿Como los obtengo?. Lo mas fácil es usar: journalctl -b | grep 'openvpn' y leer los logs en búsqueda de cada valor. El mejor lugar para buscar es la sección PUSH.

En caso del valor de enp0s3_network, una forma fácil de obtenerlo es con ip addr show enp0s3, luego tomar el valor inet (IP host del nuestro equipo) y cambiar el ultimo octeto por un .0. Ej: 192.168.1.13/24 -> 192.168.1.0/24. Nota: si su interfaz física no se llama enp0s3, recuerden cambiar al nombre correspondiente.

Claramente realizar los pasos anteriores de manera manual es lento y complicado. Luego de esta explicación del paso a paso, lo automatizaremos todo en un script.

Bien, con estas variables definidas ejecutaremos un total de 9 comandos de iproute2:

1) ip addr add <tun0_host> dev tun0: Le asignamos a la interfaz tun0 la IP host indicada por el PUSH de OpenVPN.

2) ip link set tun0 up: Pasamos la interfaz tun0 a estado "activa".

3) ip route add <tun0_network> dev tun0 scope link src <tun0_host> table 200: Esto enviara por la interfaz tun0, con IP de origen 10.10.238.249, todo trafico dirigido a 10.10.238.0/24. Pero OJO, esta regla no la estamos añadiendo a la tabla main (tabla de ruteo por defecto), sino a la tabla "200" (este numero lo he seleccionado de manera aleatoria y sirve como identificado, pueden usar otro si desean).

4) ip route add default via <tun0_gateway> dev tun0 src <tun0_host> table 200: Enviamos todo el trafico, si no hay otro ruteo en la misma tabla que realice match, a la la gateway 10.10.238.1 mediante la interfaz tun0 usando como IP de origen 10.10.238.249. Y OJO, nuevamente, esto también lo hemos añadido a la tabla "200".

5) ip rule add from all uidrange <uid_range> lookup 200: Creamos nuestra primera regla. Donde todo el trafico generado por el rango de usuarios 1100-1100 deberá dirigirse a ver la tabla de ruteo "200".

Ahora tomaremos una pausa de usar comandos y examinaremos como están nuestras tablas de ruteo y reglas. Usemos ip rule, ip route show table 200 y ip route. Veamos como va nuestro networking:

ip rule

0:  from all lookup local
32765:  from all uidrange 1100-1100 lookup 200
32766:  from all lookup main
32767:  from all lookup default
Enter fullscreen mode Exit fullscreen mode

Cuando el sistema necesite enviar un paquete por red lo primero que revisara son las reglas. Como pueden ver ahora (a diferencia de cuando OpenVPN se auto-configura como Full Tunnel) es que existe una nueva regla. Y esta posicionada antes de main. Cualquier paquete emitido por el rango de usuarios 1100-1100 deberá a ir a ver la tabla de ruteo "200".

ip route

default via 192.168.1.1 dev enp0s3 proto dhcp src 192.168.1.13 metric 100 
192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.13 metric 100 
Enter fullscreen mode Exit fullscreen mode

La tabla main no ha sufrido ningún cambio. Todo lo que llegue a esta tabla usara la interfaz enp0s3 ya sea para salir a internet por la gateway 192.168.1.1 o para ir a algún otro destino en la red local 192.168.1.0/24. Siempre usando como IP de origen 192.168.1.13.

ip route show table 200

default via 10.10.238.1 dev tun0 src 10.10.238.249 
10.10.238.0/24 dev tun0 scope link src 10.10.238.249
Enter fullscreen mode Exit fullscreen mode

Finalmente nuestra nueva tabla de ruteo "200", creada durante el comando 3 y 4. Como pueden ver es muy similar a la tabla main. Cualquier paquete que llegue a esta tabla usara la interfaz tun0, ya sea para salir a internet por la gateway 10.10.238.1 o para ir a algún otro destino de la red 10.10.238.0/24. Siempre usando como IP de origen 10.10.238.249.

Si ahora reflexionamos sobre estas reglas y tablas de ruteo podemos tratar de inferir que pasará con los paquetes de datos en diversas condiciones:

-¿Si un paquete va a localhost o broadcast? -> Se usa la tabla de ruteo "local".
-¿Si un paquete fue generado por UID 1100? -> La segunda regla hace match y se aplicar la tabla de ruteo "200", donde se especifica el uso de la interfaz tun0.
-¿Si un paquete no coincide con la primera y segunda regla? -> Se usa la tabla de ruteo main, y por ende, la interfaz enp0s3.

Genial!

Pero lamentablemente no hemos terminado. Dada la configuración anterior, existen 4 escenarios donde nuestro networking va a fallar. Veamos cada uno de estos casos "especiales" y como abordarlos:

6) ip rule add from <tun0_network> lookup 200: En Linux existe algo llamado rpfilter. Este es un mecanismo que se asegura que no recibamos ataques de sniffing.

Este tipo de ataques se basa alterar los paquetes entrantes al equipo para acceder a zonas de la red que no deberían y "mapear" posibles puntos de ataque. Con rpfilter el sistema se asegura que todas las respuestas a paquetes que entren por una interfaz X, usen la misma interfaz X para salir. De esta manera, al menos que una configuración de networking diga lo contrario, se mantienen aisladas las redes a las cuales esta conectado nuestro sistema (evitando el sniffing). El rpfilter esta implementado a nivel Kernel generalmente. En caso de NixOS el rpfilter esta presente como reglas del firewall iptables (específicamente en la tabla mangle chain nixos-fw-rpfilter).

Resulta que el rpfilter bloqueara los paquetes que entren a tun0, ya que al verificar la interfaz de salida (respuesta) encontrara a enp0s3. Esto provoca que rpfilter piense que se tratan paquetes que están tratando de hacer sniffing. ¿Pero porque sucede esto? Porque solo el usuario 1100 tiene las reglas para usar tun0 y el rpfilter es administrado por root (UID 0). Por tanto, es imposible que el rpfilter realice correctamente su trabajo... no puede ni acceder a tun0 para ver si los paquetes entrantes también salen por ahí.

Con esta regla que hemos añadido estamos ayudando rpfilter. Básicamente se lee como: "si algo entra por 10.10.238.0/24, usa la tabla de ruteo 200 para ver como responder", o sea, "si entra por tun0, sale por tun0, independiente del usuario que este manipulando el paquete de datos".

7) ip rule add to <openvpn_peer_ip> lookup main: ¿Que pasa si el usuario 1100 intenta comunicarse con la IP publica del servidor VPN?. Pues como la regla uidrange 1100-1100 lookup 200 esta antes que lookup main, el sistema va a insistir que el usuario 1100 use la tabla "200". Y esto no tiene sentido... porque no podemos alcanzar la IP publica del servidor VPN (184.75.223.237) desde el interior de túnel. Entonces, esta regla la anteponemos a uidrange 1100-1100 lookup 200 para asegurarnos que, independiente del usuario, todo paquete dirigido a la IP publica del servidor VPN (184.75.223.237) debe usar la tabla main, o sea, interfaz enp0s3. Comprendo que esta regla puede ser algo extraña, pero recuerden: desde el interior del túnel, por recursividad, no se puede llegar a la IP publica del servidor VPN.

8) ip rule add to <enp0s3_network> lookup main: Esta regla tiene un fin parecido a la anterior. ¿Que pasa si el usuario 1100 quiere comunicarse con un dispositivo de la red local... por ejemplo una impresora?... pues la regla uidrange 1100-1100 lookup 200 lo va a obligar a usar la interfaz tun0. Pero claramente desde el túnel es imposible llegar a nuestra impresora. Esta regla la anteponemos a uidrange 1100-1100 lookup 200, para obligar que cualquier trafico, independiente del usuario, que se apunte a algún dispositivo de la red 192.168.1.0/24, use la tabla main y por ende la interfaz enp0s3.

9) ip rule add to <tun0_dns> lookup 200: Esta ultima regla es para que el DNS funcione correctamente en modo Split Tunnel. Resulta que el sistema operativo no tiene una funcionalidad por defecto de tener algo como "Split DNS" (para eso debemos usar otras herramientas como Unbound). Por tanto, tenemos que definir un solo DNS para todo el sistema, independiente del usuario que este requiriendo resolver un hostname. Si no añadimos esta regla, solo el usuario vpn_user podrá acceder a usar el DNS que se ha definido en /etc/resolv.conf (automáticamente durante el inicio de de OpenVPN). Si otro usuario, como root o nixos intentan resolver un hostname, trataran de usar 10.10.238.1 pero, dadas las reglas, van a ser dirigidos a la tabla main; lugar por el cual es imposible llegar a 10.10.238.1 de tun0. Con esta novena regla nos aseguramos que, independiente del usuario, cualquier parte del sistema que requiera resolver un hostname, pueda usar la tabla "200" para llegar al servidor DNS que ofrece el proveedor VPN.

Nota: En caso que usen un DNS como 1.1.1.1 o 8.8.8.8 deberán configurar esta regla con dicho valor para hacer las consultas a esos servidores DNS usando el túnel. En caso contrario las resoluciones ocuparan la interfaz enp0s3 y el ISP podrá detectar a que dominós se esta conectado (para evitar esto ultimo pueden investigar sobre el uso de DoT o DoH aqui).

Si volvemos a usar ip rule, ip route y ip route show table 200 deberíamos ver:

ip rule

0:  from all lookup local
32761:  from all to 192.168.1.0/24 lookup main
32762:  from all to 184.75.223.237 lookup main
32763:  from 10.10.238.0/24 lookup 200
32764:  from all to 10.10.238.1 lookup 200
32765:  from all uidrange 1100-1100 lookup 200
32766:  from all lookup main
32767:  from all lookup default
Enter fullscreen mode Exit fullscreen mode

Ojo, miren la importancia del orden de cada regla

ip route

default via 192.168.1.1 dev enp0s3 proto dhcp src 192.168.1.13 metric 100 
192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.13 metric 100
Enter fullscreen mode Exit fullscreen mode

ip route show table 200

default via 10.10.238.1 dev tun0 src 10.10.238.249 
10.10.238.0/24 dev tun0 scope link src 10.10.238.249 
Enter fullscreen mode Exit fullscreen mode

Realizando pruebas

Una vez usemos las 9 instrucciones deberíamos tener nuestro Split Tunnel funcionando. Vamos a realizar algunas pruebas:

Si hacemos con nuestro usuario nixos (normal):

curl https://ipv4.ipleak.net/json/

Deberíamos ver como respuesta nuestra IP publica, muestra nuestro IPS y el país donde residimos... o sea, no esta usando la VPN.

Y lo mismo para el usuario root:

sudo curl https://ipv4.ipleak.net/json/

Deberíamos ver que el túnel no se usa.

PERO si usamos nuestro usuario vpn_user, con UID 1100:

sudo -u vpn_user curl https://ipv4.ipleak.net/json/

Magia! Dicha solicitud web esta saliendo por el túnel. Deberíamos ver una IP que no se puede asociar a nosotros a igual que una IPS y país distinto.

Pruebas de DNS

También podemos realizar pruebas para asegurar que no tenemos DNS Leak. Existe un método usando la terminal y pueden encontrar los detalles aqui. Lo primero que deberán hacer es generar una cadena de 40 caracteres aleatorios, esta sera la "session". Luego deberán hacer un curl a la siguiente URL usando dicha "session". Deben hacer varios curl consecutivos usando la misma "session", pero en cada una de ellas cambiar el valor de "random".

curl https://[session]-[random].ipleak.net/dnsdetection/

Las respuestas que acumulen de cada curl empezarán a formar un "mapeo" de los servidores DNS que se están usando. Luego tiene que tomar dichas IPs de respuesta e investigar mediante reverse IP lookup (link1, link2, link3) a que servidores corresponden.

Si su DNS esta correcto deberían ver que el servidor DNS usado no puede ser asociado a ustedes geográficamente.

Luego, generando una nueva "session", repitan el proceso para los otros usuarios: sudo curl..., sudo -u vpn_user curl...; el resultado debería ser siempre el mismo.

Si el DNS que detectan es geograficamente cercano a ustedes, algo no esta funcionando bien y tienen DNS Leak.

Split Tunnel basado en usuarios y sin DNS Leak funcionando :)


Automatizando el proceso

Claramente ejecutar 9 comandos y ademas tener que leer los logs de OpenVPN para configurar el Split Tunnel es... lento, fácil de cometer errores... no gracias. Bueno, podemos automatizar esto.

Aquí pueden encontrar un script que automatiza el proceso. Yo lo he guardado en /etc/nixos/vpn_custom_script.sh. Y debe ser integrado haciendo estos cambios en su configuration.nix:

...
services.openvpn = {
  servers = {
    airVPN = {
      config = "config /etc/nixos/airvpn.ovpn \n route-noexec \n ifconfig-noexec";
      updateResolvConf = true;
      up = ''
        UID_RANGE="1100-1100"
        CUSTOM_SCRIPT_MODE="up"
        ${builtins.readFile /etc/nixos/vpn_custom_script.sh}
      '';
      down = ''
        UID_RANGE="1100-1100"
        CUSTOM_SCRIPT_MODE="down"
        ${builtins.readFile /etc/nixos/vpn_custom_script.sh}
      '';
    };
  };
};
systemd.services.openvpn-airVPN.path = [ pkgs.gawk pkgs.ipcalc ];
... 
Enter fullscreen mode Exit fullscreen mode

La integración de OpenVPN en NixOS nos da el parámetro declarativo up y down. La cadena de texto pasada a estos parámetros será "incrustada" en los scripts del ciclo de vida del servicio de OpenVPN. Lo indicado en up se ejecutará luego que OpenVPN termine de iniciar. Lo indicado en down luego que el servicio se detenga. Estos son lugares perfectos para añadir funcionalidades que estén relacionadas en manipular la red.

Nuestro vpn_custom_script.sh, dependiendo del valor CUSTOM_SCRIPT_MODE, aplica o revierte las manipulaciones de red. El único valor que tenemos que definir manualmente es el rango de UID de usuarios en UID_RANGE. Estos son los usuarios cuyo trafico sera dirigido por dentro del túnel.

Nota 1: Usamos builtins.readFile para leer el contenido del .sh y pasarlo como cadena de caracteres a up y down. Esto ya que dichos parámetros no esperan un path a un script, sino, literalmente una cadena de texto que sera incrustada a los .sh de OpenVPN. Pueden ver aquí para mas información.

Nota 2: En systemd.services.openvpn-airVPN.path hemos añadido, al estilo NixOS, los paquetes que son usados al interior de vpn_custom_script.sh: gawk y ipcalc. Sin esta referencia, NixOS no podrá encontrar los path a los binarios necesarios.

Nota 3: Puede que te extrañe, que al interior de vpn_custom_script.sh, existen algunas variables como $ifconfig_netmask que parecieran ser usadas "de la nada" sin ser nunca declaradas. Realmente el valor de estas variables corresponde a variables de entorno que OpenVPN usa en su proceso. Por tanto, como el contenido de vpn_custom_script.sh esta siendo "incrustado" a los .sh propios de OpenVPN, podemos aprovechar de acceder a sus variables de entorno. Lugar donde, por fortuna, están los principales parámetros de red de la VPN. Puedes ver mas aquí.

Si no tienes NixOS

Como he dicho al inicio, este artículo esta pensado en NixOS. Así que si usas otra distribución de Linux te tocará idear como automatizar el proceso para tu ambiente. Espero que el script te pueda servir de inspiración para encontrar la mejor solución a tu caso.

El mayor desafió es encontrar el valor de las IPs y redes involucradas en la VPN. Para eso tienes 2 opciones. 1) De alguna manera acceder a las variables de entorno que maneja de OpenVPN (como lo hace mi script) o 2) usar journalctl -b | grep 'openvpn' para obtener y procesar el texto de los logs de OpenVPN. Generalmente OpenVPN permite añadir al .ovpn paths a scripts que deben ser ejecutados durante su proceso de inicio (up) y cierre (down). Es aquí el mejor lugar para que ustedes realicen la referencia a los scripts que vayan a usar en su proceso de automatización. Relacionado a esto, les recomiendo leer sobre script-security de OpenVPN. También, como ultimo consejo, no olviden tener, al igual que un script de seteo, un script para revertir todas las reglas y ruteos cuando OpenVPN se apaga o reinicia. Si no hacen esto, podrían llegar a corromper su networking.


Bonus: la regla inversa, que todos los usuarios, menos uno, usen el túnel

He decidido añadir este bonus donde explico, rápidamente, como lograr la "negación" de nuestro Split Tunnel. Que todos los usuarios, menos uno, usen OpenVPN.

Para esto deben reemplazar las reglas de ruteo para usar los rangos de esta manera. Supongamos que queremos mantener el trafico del usuario UID 1100 fuera del túnel.

ip rule add from all uidrange 0-1099 lookup 200
ip rule add from all uidrange 1101-65535 lookup 200

Nota: Verifiquen en su distribución cual es el máximo valor UID que puede existir. En NixOS es 65535.

Luego deberán hacer solo un cambio mas. Tiene que ver con el rpfilter. Debemos tener una regla que asegure que todo el trafico que llega por enp0s3 salga por enp0s3.

ip rule add from 192.168.1.0/24 lookup main

Esto se necesita ya que ahora root (usuario dueño de rpfilter) siempre estará usando el tun0 y provocará que cuando se encuentre con trafico del usuario 1100, y lo analice para ver si hay sniffing, no pueda usar la interfaz enp0s3 para ver la salida del mismo. Con esta regla lo solucionamos.


Conclusión

Espero que este artículo los ayudará a comprender mejor cómo funciona el networking que gestiona OpenVPN y como lograr tener un Split Tunnel. Los invito a leer más de mis artículos, donde hablo generalmente en profundidad de seguridad, NixOS y Home Servers.

Sin nada más que añadir, que tengan un lindo día experimentando :)

Top comments (0)