Variádico

Depurar redes

21 de febrero de 2017

Mucha de la programación moderna suele interactuar, de una forma u otra, con el internet o algun tipo de red. Como todo en la vida, tarde o temprano, algo va a fallar. Probablemente todos estemos más acostumbrados a depurar código fuente. Pero, ¿cómo solucionas un problema que flota por el aire o transcurre en un proceso invisible? Hablemos sobre depurar problemas de redes.

¿Está conectado el servidor?

Bueno, no lo digo en un sentido literal, pero, ¿estás seguro que tu servidor está corriendo? Quizá no lo iniciaste o quizá tronó un poco después de iniciarlo. ¿Cómo sabes?

$ ssh jaime@localhost
ssh: connect to host localhost port 22: Connection refused
$ curl http://localhost
curl: (7) Failed to connect to localhost port 80: Connection refused

Un mensaje de error común es Connection refused, o «conexión denegada». Sueles toparte con éste mensaje cuando no hay nada esperando clientes en un puerto. En el ejemplo anterior, no hay nada escuchando en los puertos 22 o 80. El cliente intenta conectar, pero después de un rato, el sistema operativo del servidor responde con conexión denegada porque no hay nadie para atender los clientes en esos puertos.

Verificar cortafuegos desde el lado del servidor

Otro caso de conexión denegada lo puede causar un cortafuegos. Si tu cortafuegos está configurado para bloquear el puerto 22, por ejemplo, vas a ver el mismo mensaje.

$ curl http://1.2.3.4
curl: (7) Failed to connect to 1.2.3.4 port 80: Connection refused

Para verificar el estado del cortafuegos en un servidor Linux, puedes usar los siguientes comandos.

ufw

Si tienes acceso al servidor, quizá puedas verificar las reglas con ufw. Es una de las formas más fáciles para configurar el cortafuegos.

# ufw status
Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere

Aquí se puede ver que sólo el puerto 22 está abierto. Es probable que conexiones en otros puertos fallen, por ejemplo, al puerto 80.

iptables

También se puede usar iptables para ver las reglas del cortafuegos. Aunque iptables es mucho más complicado, quizá sea útil si el cortafuegos fue configurado de una manera sencilla. iptables --list muestra todas las tablas de reglas. Con esta información, puedes buscar algunas cosas.

Obviamente, esto nada más es un cachito de iptables, pero para verificar algo sencillo puede ser útil.

# iptables --list
Chain INPUT (policy DROP)
target            prot  opt  source    destination
ufw-before-input  all   --   anywhere  anywhere

Chain ufw-before-input (1 references)
target          prot  opt  source    destination
ufw-user-input  all   --   anywhere  anywhere

Chain ufw-user-input (1 references)
target  prot  opt  source    destination
ACCEPT  tcp   --   anywhere  anywhere     tcp dpt:ssh
ACCEPT  udp   --   anywhere  anywhere     udp dpt:ssh

Empezamos desde arriba. A los paquetes entrantes, se les aplica la cadena INPUT. Luego, si coinciden con prot=all, source=anywhere, y destionation=anywhere, saltamos a la cadena ufw-before-input. Otra vez, si el protocolo, origin, y destino coinciden, brincamos a ufw-user-input. Finalmente, aquí el filtro es más especifico. iptables busca que el protocolo del paquete sea TCP o UDP, la IP del origin o destino puede ser a donde sea, y que el puerto de destino sea 22, o sea, el puerto de SSH. Si un paquete coincide con estas reglas, entonces se aplica la acción ACCEPT y se le permite entrada al servidor.

A todos los otros paquetes entrantes que no caigan dentro de estas reglas, se les aplica la política por defecto DROP, como indica Chain INPUT (policy DROP) en la primer tabla. Y aquí es dónde nos damos cuenta que el error anterior de «conexión denegada» al usar curl, quizá pueda ser porque hay un cortafuegos bloqueando conexiones al puerto 80.

Verificar cortafuegos desde el lado del cliente

Si por alguna razón no tienes acceso al servidor que estás depurando, quizá puedas usar nmap para ver si hay puertos abiertos desde el lado del cliente. Por ejemplo, podemos escanear el sitio scanme.nmap.org para investigar si hay puertos abiertos.

$ nmap scanme.nmap.org

Starting Nmap 7.40 ( https://nmap.org ) at 2017-02-21 17:30 PST
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.054s latency).
Other addresses for scanme.nmap.org (not scanned): 2600:3c01::f03c:91ff:fe18:bb2f
Not shown: 971 closed ports
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http
5269/tcp  filtered xmpp-server
6007/tcp  filtered X11:7

Nmap done: 1 IP address (1 host up) scanned in 24.58 seconds

Aquí el significado de algunos estados.

Aquí podemos ver que hay aplicaciones escuchando en los puertos 22 y 80. También puede ser que haya un cortafuegos bloqueando conexiones a los puertos 5269 y 6007.

Vale la pena notar que escanear un sitio se ve un poquito sospechoso porque genera mucho tráfico. Así que te recomiendo que solo escanees servidores que sean tuyos.

Súbele a los registros

Aveces pasa que creas un servidor en AWS o Digital Ocean y cambias la configuración del SSH para usar una llave, en lugar de una contraseña. O quizá quieras deshabilitar el uso de una contraseña. ¿Cómo sabes qué está pasando? Tienes que buscar una opción mostrar más información.

ssh

En el caso de ssh, usa la opción -vvv.

$ ssh -vvv jaime@ejemplo.com
debug2: resolving "ejemplo.com" port 22
debug1: Connecting to ejemplo.com [1.2.3.4] port 22.
debug1: Connection established.

# Leer llave pública
debug1: key_load_public: No such file or directory
debug1: identity file /home/jaime/.ssh/id_rsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/jaime/.ssh/id_ed25519 type -1

debug1: Authenticating to ejemplo.com:22 as 'jaime'

# Leer llave privada
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Trying private key: /home/jaime/.ssh/id_rsa
debug3: no such identity: /home/jaime/.ssh/id_rsa: No such file or directory
debug1: Trying private key: /home/jaime/.ssh/id_ed25519
debug3: no such identity: /home/jaime/.ssh/id_ed25519: No such file or directory
debug1: Next authentication method: password

jaime@ejemplo.com's password:

Ahora con estos registros, podemos tomar decisiones más informadas. Podemos ver que el comando SSH pudo resolver el dominio “ejemplo.com” y que intenta conectarse en el puerto 22. El mensaje Connection established, o «conexión establecida», nos indica que no hay problema de red. También podemos ver que SSH intenta leer llaves públicas y privadas, pero no las encuentra.

Casi todos los programas tienen opciones para mostrar más información o guardan un archivo de registros. Si puedes encontrar estas opciones, depurar un problema se hace mucho más fácil.

systemd

Si usas systemd, es posible que simplemente puedas usar journalctl para ver registros de diferentes programas.

# journalctl --unit ssh.service

Los registros más viejos están arriba y los registros más nuevos abajo.

Cuidado con el DNS

DNS es el sistema que convierte un nombre de dominio a una dirección IP. Por ejemplo, www.google.com se convierte en la IP 172.217.6.36. Cuando una computadora envía un paquete, lo hace con una dirección IP, no un nombre de dominio.

El resolvedor

El otro día estaba experimentando con rkt. Hice un contenedor con este comando y quería instalar algunas cosas, así que ejecuté apt-get.

# rkt run --insecure-options=image --interactive docker://ubuntu:14.04
root@rkt:/# apt-get update
Err http://archive.ubuntu.com trusty-updates InRelease
Err http://archive.ubuntu.com trusty-security InRelease
Err http://archive.ubuntu.com trusty-updates Release.gpg
  Could not resolve 'archive.ubuntu.com'
Err http://archive.ubuntu.com trusty-security Release.gpg
  Could not resolve 'archive.ubuntu.com'
Reading package lists... Done
W: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/trusty-updates/InRelease
W: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/trusty-security/InRelease

¿Qué onda? ¿Por qué falló apt-get update? Lo primero que hice es verificar que mi computadora estuviera conectada al internet. Después de confirmar eso, luego decidí verificar el DNS.

root@rkt:/# ping -c 1 172.217.6.36
PING 172.217.6.36 (172.217.6.36) 56(84) bytes of data.
64 bytes from 172.217.6.36: icmp_seq=1 ttl=49 time=26.2 ms
--- 172.217.6.36 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 26.297/26.297/26.297/0.000 ms

root@rkt:/# ping -c 1 www.google.com
ping: unknown host www.google.com

¡Ajá! Con un simple ping, pude verificar que sí tenía conexión a internet. El problema era el DNS no estaba funcionando correctamente. Los servidores que se usan para buscar la IP de un dominio están en /etc/resolv.conf. Luego me dí cuenta que estaba vacío ese archivo. Para rectificar el problema simplemente ejecuté rkt con la opción --dns.

# rkt run --insecure-options=image --dns 8.8.8.8 --interactive docker://ubuntu:14.04

drill

¿Cómo supe que la IP de www.google.com es 172.217.6.36? Con el comando drill.

$ # Uso: drill name [@server] [type] [class]
$ drill www.google.com
;; QUESTION SECTION:
;; www.google.com.	IN	A

;; ANSWER SECTION:
www.google.com.	297	IN	A	172.217.6.36

El comando anterior le pregunta a los servidores en /etc/resolv.conf, ¿qué es la IP de «www.google.com»? Luego, el servidor de nombres responde, «172.217.6.36». Si deseo usar un servidor de nombres en particular, puedo pasar la opción @8.8.8.8.

Inspección de paquetes

También existen maneras de ver información sobre los datos que se transmiten a travéz del aire o cables. Las siguientes herramientas funcionan en Linux, pero también macOS.

tcpdump

Una utilidad que te puede dar superpoderes, es tcpdump. Cuando primero intenté usar tcpdump, se me figuró muy complicado, sin embargo, ahora no le tengo miedo. El sintaxis es así.

tcpdump [opciones] 'filtro BPF'

Una forma muy común es usarlo así.

tcpdump -i lo 'filtro BPF'

El -i lo indica que queremos inspectar paquetes en la interfaz loopback, o sea en localhost. Quizá quieras usar la interfaz que usas para el internet inalámbrico. Puedes ver las interfazes disponibles así.

tcpdump --list-interfaces

Lo que hace tcpdump poderoso son los filtros. Aquí hay unos ejemplos básicos.

Así se ve el comando completo. Los primeros 3 paquetes son la famosa negociación en tres pasos. Aunque no parezca.

# tcpdump -i wlp58s0 'tcp port 80'
192.168.0.109.37370 > 107.170.18.175.http: Flags [S],  length 0
107.170.18.175.http > 192.168.0.109.37370: Flags [S.], length 0
192.168.0.109.37370 > 107.170.18.175.http: Flags [.],  length 0
192.168.0.109.37370 > 107.170.18.175.http: Flags [P.], length 80: HTTP: HEAD / HTTP/1.1
107.170.18.175.http > 192.168.0.109.37370: Flags [.],  length 0
107.170.18.175.http > 192.168.0.109.37370: Flags [P.], length 286: HTTP: HTTP/1.1200 OK
192.168.0.109.37370 > 107.170.18.175.http: Flags [.],  length 0
192.168.0.109.37370 > 107.170.18.175.http: Flags [F.], length 0
107.170.18.175.http > 192.168.0.109.37370: Flags [F.], length 0
192.168.0.109.37370 > 107.170.18.175.http: Flags [.],  length 0

Los símbolos entre corchetes, significan lo siguiente.

ngrep

Por otro lado, si quieres ver el contenido de los paquetes, entonces te recomiendo otra utilidad, ngrep. Desafortunadamente (o afortunadamente), no podrás ver el contenido de conexiones cifradas, por ejemplo, conexiones de HTTPS. Podrás ver el texto cifrado, pero no el contenido real. Aun así, esta herramienta es muy útil.

El sintaxis es así.

ngrep [opciones] 'patrón' 'filtro BPF'

Para espiar el contenido de peticiones HTTP, por ejemplo, podemos usar este comando. Recuerda que HTTP usa TCP.

# ngrep -q -d lo 'HTTP' 'tcp'
interface: lo (127.0.0.0/255.0.0.0)
filter: (ip or ip6) and ( tcp )
match: HTTP

T ::1:41540 -> ::1:8080 [AP]
  HEAD / HTTP/1.1..Host: localhost:8080..User-Agent: curl/7.52.1..Accept: */*....

T ::1:8080 -> ::1:41540 [AP]
  HTTP/1.1 200 OK..Cache-Control: max-age=31536000..Content-Security-Policy: default-src 'none'; style-src 's
  elf';..Content-Type: text/html; charset=UTF-8..Etag: 9672626753539807598..Date: Thu, 23 Feb 2017 00:07:05 G
  MT..Content-Length: 1669....

Las opciones significan lo siguiente.

Muy largo; no leí

Para encontrar la causa de un problema, asegúrate que…