Bullet Proof (warrior mode) Multihomed VPN-HA

Buenas,

Muchos de los que trabajamos en este mundo «IT» vivimos la disponibilidad de las comunicaciones y el acceso remoto como si fuera la misma sangre que corre por nuestra galaxia digital. Al fin y al cabo, si tienes que gestionar, dar soporte o comprobar N^M clientes, no te puedes permitir no tener acceso 😉

Así que un buen día nos dió por diseñar lo que llamamos el acceso remoto tolerante a las contingencias de red y bloqueos no controlados habituales. Obviamente, no nos referimos a poner IODINE en producción (jiji, todavía no estamos tan asilvestrados). Se trata más bien de un poco de taller de networking alternativo, fuera de los usos normales, pero ilustrando igualmente ciertos conceptos aplicables que pueden resultar interesantes. Así que, básicamente: ¿Por qué? Por que nos gusta 🙂

De hecho, en una galaxia lejana, muy lejana, por el año 2005, curioseábamos ya con este tipo de mecanismos 😉

Como siempre: Show me a diagram, please, ASAP! así que aquí va lo que hemos montado en este mini laboratorio:

post_vpnbulletproof_esquemageneral

 

De base, lo que nos planteamos como objetivos son:

  1. Obviamente, poder acceder siempre al host.
  2. Que no se pierda la conexión si falla uno de los túneles y tiene que conmutar [!].
  3. Siempre que se pueda, por el túnel UDP en primer lugar, que es lo mas indicado y si este no está disponible por el túnel TCP443 y como última opción el túnel DNS.

Para el primer punto, está claro que lo que tenemos que hacer es publicar a nivel de routing una interfaz virtual / o de loopback, en los mundos de GNU/Linux: dummy network interface. Para el segundo y tercer punto, está claro que tenemos que optar por un protocolo de routing dinámico que anuncie por cada uno de los túneles la IP que estamos queriendo acceder, pero con diferentes pesos, métricas/distancias.

Por tanto, nuestros ingredientes para construir el bunker de comunicaciones:

Y en cuanto a tecnologías:

Y para montar todo esto, obviamente, nos ha hecho falta una máquina virtual tras NAT, y el respectivo terminador de túneles.

 Primer paso: Definiendo el direccionamiento

En nuestro proyecto, aunque no sea para nada productivo, tb hay que definir el direccionamiento 😉 así que este es el planteamiento, así se entenderán mejor luego las rutas y tal:

  • Interfaces de Loopback. El servidor será 10.131.131.1/32 en su dummy0, y el cliente-agente 10.132.132.2/32, lo mismo, en su dummy0.
  • Túnel UDP: Será de tipo tun, ptp tal que: servidor 10.141.141.1 y cliente 10.141.141.2
  • Túnel TCP443: Lo mismo, tipo tun, ptp y tal que: servidor 10.142.142.1 y cliente 10.142.142.2
  • Túnel DNS: Aquí IODINE no nos permite tun ptp, así que usamos el rango: 10.55.55.0/24

 

Túnel 1/3: Lanzando el túnel sencillo (UDP, PSK)

Este el túnel mas sencillo, por supuesto, tiraremos de OpenVPN, y en este caso con PSK, sin infraestructura PKI ni nada complejo.

Antes de nada, generamos clave PSK tal que:

# openvpn --genkey --secret test.key

La configuración en el lado servidor:

# cat udp.conf 
dev tunudp
ifconfig 10.141.141.1 10.141.141.2
secret test.key

y lo arrancamos y verificamos tal que:

# openvpn --daemon --config udp.conf
# ifconfig tunudp
tunudp Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 
inet addr:10.141.141.1 P-t-P:10.141.141.2 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100 
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

La configuración en el lado cliente es igualmente mínima:

# cat udp.conf 
remote 88.198.56.14
dev tunudp
ifconfig 10.141.141.2 10.141.141.1
secret test.key

Una vez lo levantamos, verificamos que pingamos bien extremo a extremo:

# openvpn --daemon --config udp.conf 
# ifconfig tunudp
tunudp Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 
inet addr:10.141.141.2 P-t-P:10.141.141.1 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100 
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

y el ping fluyendo:

# ping 10.141.141.1
PING 10.141.141.1 (10.141.141.1) 56(84) bytes of data.
64 bytes from 10.141.141.1: icmp_req=1 ttl=64 time=59.8 ms
^C
--- 10.141.141.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 59.808/59.808/59.808/0.000 ms

 

Túnel 2/3: Lanzando el túnel TCP vía HTTP Proxy

Una vez más, para este tipo de túnel optamos por OpenVPN, ya que soporta perfectamente TCP y la utilización de un proxy (vía método standard CONNECT, permitido normalmente cuando el puerto destino es HTTPS(443)).

Utilizaremos la misma clave PSK del túnel UDP (túnel1). Tendríamos por tanto en el lado servidor:

dev tuntcp443
proto tcp-server
lport 443
ifconfig 10.142.142.1 10.142.142.2
secret test.key

Y en el lado cliente, recordemos que habíamos comentado de hacerlo a través de un Proxy (con lo que hemos desplegado un micro squid para simularlo):

# cat tcp443.conf 
dev tuntcp443
proto tcp-client
remote 88.XX.XX.XX 443 
ifconfig 10.142.142.2 10.142.142.1
http-proxy 10.A.B.C.D 3128
secret test.key

Una vez levantado, el ping funciona correctamente:

# ping 10.142.142.1
PING 10.142.142.1 (10.142.142.1) 56(84) bytes of data.
64 bytes from 10.142.142.1: icmp_req=1 ttl=64 time=60.5 ms
^C
--- 10.142.142.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 60.530/60.530/60.530/0.000 ms

 

Túnel 3/3: Tunelizando por DNS con IODINE

De IODINE se ha hablado mucho siempre, sobre todo cuando se trata de portales de acceso tipo hot-spot en redes wireless y simialres, la documentación de la página oficial la verdad es que es muy clara, así que simplemente nos limitaremos a comentar por encima

Como primer paso, tenemos que tener un dominio o subdominio, nosotros hemos optado por: warriors.irontec.com, que hemos dado de alta ya y apuntado correctamente su registro de nameserver tal que:

warriors IN NS warriors.irontec.com.
warriors IN A 88.XX.XX.XX

Para el tema del payload y rendimiento, cuanto mas corto sea el subdominio mejor.

En el server 88.xx.xx.xx arrancamos el servicio IODINE, con password ‘test’ de prueba:

iodined -fP test 10.55.55.1 warriors.irontec.com
Opened dns0
Setting IP of dns0 to 10.55.55.1
Setting MTU of dns0 to 1130
Opened UDP socket
Listening to dns for domain warriors.irontec.com

y comprobamos:

# dig -t NS warriors.irontec.com

; <<>> DiG 9.7.3 <<>> -t NS warriors.irontec.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 767
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;warriors.irontec.com.		IN	NS

;; ANSWER SECTION:
warriors.irontec.com.	3600	IN	NS	ns.warriors.irontec.com.

;; ADDITIONAL SECTION:
ns.warriors.irontec.com. 3599	IN	A	8X.XX.XX.XX

;; Query time: 16 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Mar 23 14:30:30 2015
;; MSG SIZE  rcvd: 71

 

Ahora solo nos falta arrancar el cliente IODINE en el lado del agente vpn, tan sencillo como:

~# iodine -r -fP test 10.X.X.X warriors.irontec.com
Opened dns0
Opened UDP socket
Sending DNS queries for warriors.irontec.com to 10.X.X.X
Autodetecting DNS query type (use -T to override).
Using DNS type NULL queries
Version ok, both using protocol v 0x00000502. You are user #1
Setting IP of dns0 to 10.55.55.3
Setting MTU of dns0 to 1130
Server tunnel IP is 10.55.55.1
Skipping raw mode
Using EDNS0 extension
Switching upstream to codec Base64u
Server switched upstream to codec Base64u
No alternative downstream codec available, using default (Raw)
Switching to lazy mode for low-latency
Server switched to lazy mode
Autoprobing max downstream fragment size... (skip with -m fragsize)
768 ok.. 1152 ok.. ...1344 not ok.. ...1248 not ok.. ...1200 not ok.. 1176 ok.. ...1188 not ok.. will use 1176-2=1174
Setting downstream fragment size to max 1174...
Connection setup complete, transmitting data.

Nota: Hay que reemplazar 10.X.X.X por la IP del DNS local que tengamos.

Como veis, en este ejemplo hemos optado por usar la opción -r, porque sino IODINE es muy listo y envía directamente el tráfico, y no estaríamos en un entorno realmente capado 🙂

y por último, comprobar, en primer lugar que tenemos el interface dns0 levantado y la ruta configurado:

dns0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:10.55.55.3  P-t-P:10.55.55.3  Mask:255.255.255.224
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1130  Metric:1
          RX packets:41 errors:0 dropped:0 overruns:0 frame:0
          TX packets:49 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500 
          RX bytes:10119 (9.8 KiB)  TX bytes:6362 (6.2 KiB)

# ip route show dev dns0
10.55.55.0/27  proto kernel  scope link  src 10.55.55.3 

y lo mas evidente, que tenemos ping:

~# ping 10.55.55.1
PING 10.55.55.1 (10.55.55.1) 56(84) bytes of data.
64 bytes from 10.55.55.1: icmp_req=1 ttl=64 time=75.3 ms
64 bytes from 10.55.55.1: icmp_req=2 ttl=64 time=68.3 ms
^C
--- 10.55.55.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 68.302/71.840/75.379/3.548 ms

¡Bingo! Tenemos ya un agente desplegado que tiene los 3 métodos de comunicación levantados y operativos 🙂

Orquestando las rutas con Quagga y BGPD

Quagga es algo que usamos mucho por aquí y nos encanta, así que antes o después acabaremos hablando en detalle.

En este caso, comentaremos simplemente que hemos optado por eBGP, podríamos haber utilizado otro protocolo de routing dinámico, pero este el que mas nos convence, nos gusta mucho el concepto de session y los temas de local preference y weight, así que this is our choice 😉 Dicho esto:

  • Servidor: Será el AS 65001
  • Cliente/Agente: Será el AS 65002

Con, esto tras instalar quagga y habilitar el daemon bgp en daemons.conf, la configuración nos queda tal que:

Cliente:

router bgp 65002
bgp router-id 0.0.0.0
network 10.132.132.2/32
neighbor 10.55.55.1 remote-as 65001
neighbor 10.55.55.1 description server via dns
neighbor 10.141.141.1 remote-as 65001
neighbor 10.141.141.1 description server via udp
neighbor 10.141.141.1 weight 300
neighbor 10.142.142.1 remote-as 65001
neighbor 10.142.142.1 description server via tcp
neighbor 10.142.142.1 weight 200

De esto, lo que nos interesa comentar es el tema de usar weights diferentes, para poder permitir la elección del mejor camino.

Servidor, es muy parecido tb:

router bgp 65001
 bgp router-id 10.131.131.1
 network 10.131.131.1/32
 neighbor 10.55.55.3 remote-as 65002
 neighbor 10.55.55.3 description cliente via dns tunnel
 neighbor 10.141.141.2 remote-as 65002
 neighbor 10.141.141.2 description cliente via udp
 neighbor 10.141.141.2 weight 300
 neighbor 10.142.142.2 remote-as 65002
 neighbor 10.142.142.2 description cliente via tcp
 neighbor 10.142.142.2 weight 200

 

Y ahora, hay que verificarlo, con un clásico show ip bgp obtenemos en el cliente:

BGP table version is 0, local router ID is 10.132.132.2
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*  10.131.131.1/32  10.142.142.1             0           200 65001 i
*>                  10.141.141.1             0           300 65001 i
*                   10.55.55.1               0             0 65001 i
*> 10.132.132.2/32  0.0.0.0                  0         32768 i

Total number of prefixes 2

Se puede observar la marca > de la selección, en este caso via 10.141.141.1, que es el túnel UDP 🙂

Y dicho esto, si lo comprobamos de base:

# ping -I 10.132.132.2 10.131.131.1
PING 10.131.131.1 (10.131.131.1) from 10.132.132.2 : 56(84) bytes of data.
64 bytes from 10.131.131.1: icmp_req=1 ttl=64 time=68.3 ms
64 bytes from 10.131.131.1: icmp_req=2 ttl=64 time=76.1 ms
^C
--- 10.131.131.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 68.329/72.225/76.122/3.905 ms

Está funcionando, el cliente pinga desde su interfaz loopback al servidor, a su interfaz loopback tb 🙂

¿Pero que sucede si por ejemplo bloqueamos el tráfico saliente UDP 1194  de tal forma que la VPN UDP no puede establecerse?

Para probarlo, en el servidor por ejemplo:

iptables -I INPUT -p udp --dport 1194 -j REJECT

y al de poco tiempo (BGP Hold Time y tal), podemos ver que ahora la ruta ha cambiado sola mágicamente 😉

BGP table version is 0, local router ID is 10.132.132.2
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.131.131.1/32  10.142.142.1             0           200 65001 i
*                   10.55.55.1               0             0 65001 i
*> 10.132.132.2/32  0.0.0.0                  0         32768 i

Total number of prefixes 2

¿No es esto cool total? 🙂 Por aquí se ha comentado incluso de aplicar técnicas de MCTL: Missed Calls Transport Layer 😉

Bromas a parte, nos hemos permitido orientar este post desde el punto de vista algo alternativo y diferente, pero los conceptos de routing dinámico y tunneling como podéis sospechar son perfectamente válidos para otros escenarios, que esperemos en breve poder comentarlos desde perspectivas más globales.

 



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Gorka Gorrotxategi

CTO en Irontec, en el frente técnico desde un par de lustros ya, para todo lo que tenga que ver con Networking, VoIP y Sistemas, en ese orden :D) Desde @zetagor escribo algo, pero poco verbose la verdad

Queremos tu opinión :)