domingo, 17 de mayo de 2015

UDP en GNU/Linux Parte I

Hace unos días me encontré con un caso interesante de pérdida de paquetes en transferencias utilizando UDP. Como se imaginaran poco me asombro que se perdieran paquetes UDP, ya que por definición se trata de un protocolo de transporte que no es fiable a la hora de entregar la información, una especie de cartero ebrio digamos. El control de flujo, el orden y la re transmisión no forman parte de las especificaciones de UDP [1], ya que fue ideado para ser simple. Meter los paquetes al cable y que la fuerza los acompañe.

Usar UDP como protocolo de transporte suele dar lugar a las siguientes situaciones:
  • Paquetes que nunca alcanzan el destino. Sin control de conexión y re transmisión, básicamente el emisor pone el paquete en el cable y se olvida por completo del mismo, no hay notificación de recepción ni nada que se le parezca.
  • Paquetes que llegan desordenados. Por esas cosas de las redes, los paquetes A y B que fueron enviados en ese orden podrían ser recibidos por el receptor en el orden inverso.
por lo tanto muchas aplicaciones que utilizan UDP deciden lidiar con estos problemas en la capa de aplicación.

En GNU/Linux, haciendo uso del programa netstat podemos ver las estadísticas UDP del sistema, por ejemplo:

root@ubuntu:/home/juan# netstat -su
Udp:
    23 packets received
    0 packets to unknown port received.
    0 packet receive errors
    23 packets sent
...
root@ubuntu:/home/juan#


En esta caso particular vemos que se enviaron 23 paquetes y se recibieron 23, muy probablemente se trate de consultas y respuestas DNS, ya que acabo de iniciar la máquina virtual. En total son 4 los campos disponibles de estadísticas UDP:
  • Packets received: número paquetes recibidos de manera satisfactoria por el sistema. Esto significa paquetes recibidos y pasados a la aplicación correspondiente.
  • Packets to unknown port received: número de paquetes que fueron recibidos pero no eran esperados por el sistema, es decir no había ninguna aplicación escuchando en ese puerto.
  • Packet receive errors: número de paquetes que no pasaron el checksum o que llegaron cuando el buffer de recepción se encontraba lleno y por lo tanto fueron descartados.
  • Packets sent: número de paquetes enviados por el sistema.

Probando Packets received:

Para probar este campo primero debemos identificar algún servicio escuchando en un puerto UDP, y para eso usamos netstat:

root@ubuntu:/home/juan# netstat -plun
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
udp        0      0 0.0.0.0:68              0.0.0.0:*                           558/dhclient   
udp        0      0 0.0.0.0:5113            0.0.0.0:*                           558/dhclient   
udp6       0      0 :::43512                :::*                                558/dhclient   
root@ubuntu:/home/juan#


Podemos ver que el puerto 68, 5113 y 43512 están en uso así que apuntaremos al puerto 68 y enviaremos 5 paquetes. Para generar tráfico UDP sin demasiadas complicaciones vamos a utilizar Hping3 [2] (aquí hay un post en donde lo utilicé para probar DNS Amplification attacks), para entender los parámetros man hping3.

root@moon:/home/juan# hping3 192.168.0.10 -2 -c 5 -p 68 -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 192.168.0.10 (wlan0 192.168.0.10): udp mode set, 28 headers + 0 data bytes

--- 192.168.0.10 hping statistic ---
5 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@moon:/home/juan# 


Ahora veamos cómo se manifiesta esto en las estadísticas UDP:

root@ubuntu:/home/juan# netstat -su
Udp:
    28 packets received
    0 packets to unknown port received.
    0 packet receive errors
    23 packets sent

...
root@ubuntu:/home/juan#


Vemos que los paquetes recibidos se incrementaron en 5, como era de esperarse, ya que los paquetes tenían como destino un puerto donde había una aplicación escuchando (mas allá de que la aplicación haya sido capaz de interpretar el contenido de los paquetes o no).

Probando Packets to unknown port received:

Los campos Packets received y Packets sent son bastante simples de comprobar así que vamos a apuntar a este otro que es un poco mas interesante.El tráfico será:
  • Destinado al puerto 5555.
  • Enviaremos 10 paquetes.
 Shoot!

root@moon:/home/juan# hping3 8.8.8.8 -2 -c 10 -p 5555 -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 8.8.8.8 (wlan0 8.8.8.8): udp mode set, 28 headers + 0 data bytes

--- 8.8.8.8 hping statistic ---
10 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@moon:/home/juan# hping3 192.168.0.10 -2 -c 10 -p 5555 -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 192.168.0.10 (wlan0 192.168.0.10): udp mode set, 28 headers + 0 data bytes
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1179 seq=0
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1180 seq=1
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1181 seq=2
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1182 seq=3
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1183 seq=4
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1184 seq=5
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1185 seq=6
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1186 seq=7
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1187 seq=8
ICMP Port Unreachable from ip=192.168.0.10 name=UNKNOWN  
status=0 port=1188 seq=9

--- 192.168.0.10 hping statistic ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 3.4/19.6/161.2 ms
root@moon:/home/juan#


Vemos que el destino nos respondió con un mensaje ICMP Port Unreachable por cada paquete que enviamos, lo cuál nos da dos pautas:
  • Los paquetes llegaron al sistema y el kernel reconoció que no tenía a quién entregarlos.
  • El host no tiene un firewall que descarte los paquetes a puertos que no sean necesarios.
Pero bien, ahora veamos qué pasó en los contadores del host destino!!

root@ubuntu:/home/juan# netstat -su
IcmpMsg:
    OutType3: 10
Udp:
    28 packets received
    10 packets to unknown port received.
    0 packet receive errors
    23 packets sent
...

root@ubuntu:/home/juan#

Exacto! Ahora tenemos 10 paquetes reconocidos como "packets to unknown port received" y también vemos los 10 mensajes ICMP enviados como respuesta.

Probando packet receive errors:

Ahora pongamos a prueba el reconocimiento de paquetes con problemas. Por suerte Hping3 nos permite, con el flag -b, enviar paquetes con el checksum corrupto así que usaremos eso para probar. En este punto haremos dos pruebas:

  • Prueba1
    • 5 paquetes con checksum inválido
    • puerto 68
  • Prueba 2
    • 5 paquetes con checksum inválido
    • puerto 5555
Prueba 1:

/root@moon:/home/juan# hping3 192.168.0.10 -2 -c 5 -p 68 -b -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 192.168.0.10 (wlan0 192.168.0.10): udp mode set, 28 headers + 0 data bytes

--- 192.168.0.10 hping statistic ---
5 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@moon:/home/juan#



root@ubuntu:/home/juan# netstat -su
IcmpMsg:
    OutType3: 10
Udp:
    28 packets received
    10 packets to unknown port received.
    5 packet receive errors
    23 packets sent
    InCsumErrors: 5
...
root@ubuntu:/home/juan#

Ahora podemos ver que el valor de "packet receive errors" pasó de 0 a 5, también tenemos un nuevo campo llamado InCsumErrors con valor 5.

Prueba 2:

root@moon:/home/juan# hping3 192.168.0.10 -2 -c 5 -p 5555 -b -V
using wlan0, addr: 192.168.0.2, MTU: 1500
HPING 192.168.0.10 (wlan0 192.168.0.10): udp mode set, 28 headers + 0 data bytes

--- 192.168.0.10 hping statistic ---
5 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@moon:/home/juan#



root@ubuntu:/home/juan# netstat -su
IcmpMsg:
    OutType3: 10
Udp:
    28 packets received
    10 packets to unknown port received.
    10 packet receive errors
    23 packets sent
    InCsumErrors: 10
...
root@ubuntu:/home/juan#


El número de packet receive errors (y InCsumErrors) volvió a incrementarse en 5. Vale notar que esta vez no recibimos los mensajes ICMP, básicamente porque los paquetes fueron descartados rápidamente por no tener el checksum inválido.

Un detalle llamativo de estas dos últimas pruebas es el siguiente. Viendo los logs escritos en /var/log/syslog, encontré:

May 17 15:08:25 ubuntu dhclient: 5 bad udp checksums in 5 packets
May 17 15:08:34 ubuntu kernel: [ 9005.777840] UDP: bad checksum. From 192.168.0.2:2868 to 192.168.0.10:5555 ulen 8
May 17 15:08:35 ubuntu kernel: [ 9006.776665] UDP: bad checksum. From 192.168.0.2:2869 to 192.168.0.10:5555 ulen 8
May 17 15:08:36 ubuntu kernel: [ 9007.781846] UDP: bad checksum. From 192.168.0.2:2870 to 192.168.0.10:5555 ulen 8
May 17 15:08:37 ubuntu kernel: [ 9008.776246] UDP: bad checksum. From 192.168.0.2:2871 to 192.168.0.10:5555 ulen 8
May 17 15:08:38 ubuntu kernel: [ 9009.779863] UDP: bad checksum. From 192.168.0.2:2872 to 192.168.0.10:5555 ulen 8


La primera línea indica que dhclient (el servicio escuchando en 68/UDP) recibió, o al menos se enteró de, los 5 paquetes con checksum erróneo, lo cual me deja un poco desconcertado. Aparentemente el kernel pasa estos paquetes de todas maneras a la aplicación, probablemente para que la aplicación decida qué medidas tomar. Las siguientes 5 líneas son del kernel indicando que se recibieron 5 paquetes UDP con bad checksum (dirigidos al 5555/UDP).

Otra forma un poco mas complicada de analizar packet receive erors es desde /proc/net/udp. La ventaja de este método es que la información está mas detallada, por ejemplo por puerto. netstat resume esta información.

root@ubuntu:/home/juan# cat /proc/net/udp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops            
   67: 00000000:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000     0        0 8110 2 caaa82c0 5                
  248: 00000000:13F9 00000000:0000 07 00000000:00000000 00:00000000 00000000     0        0 8044 2 caaa8000 0                 
root@ubuntu:/home/juan#


En este caso marcado en "negrita" vemos 0044 valor hexadecimal del puerto 68, y por último el número de paquetes descartados para ese puerto 5 (los paquetes enviados con bad checksum). Otros valores importantes que vemos son tx_queue y rx_queue que básicamente indican que las colas están vacías dado que no hay paquetes llegando o saliendo del host.

Cuando describí el campo packet receive errors al comienzo del post, dije que este contador no solo se incrementa por paquetes defectuosos sino también por paquetes que al llegar encuentran el buffer de recepción lleno y son descartados. Esta última condición se genera cuando la tasa de llegada de paquetes es mayor a la tasa de procesamiento de los mismos. Un clásico problema productor-consumidor, si se produce a mayor tasa de la que se consume, el productor debe ser capaz de reconocer la situación y frenar la producción (control de flujo que UDP no posee) o el consumidor debe ser capaz de aumentar su velocidad de consumo o ampliar su capacidad de almacenar productos (incrementar el tamaño de los buffers).

Probar esta última situación resulta un poco mas complicado así que queda pendiente para la próxima publicación :P

Links:

[1] UDP - https://www.ietf.org/rfc/rfc768.txt
[2] Hping3 - http://wiki.hping.org/

No hay comentarios:

Publicar un comentario