jueves, 25 de junio de 2015

Jugando con Linux Audit System Parte II

En la entrada anterior veíamos los cimientos de Linux Audit System, ahora vamos a ver un par de aplicaciones prácticas como para hacernos una idea mas clara de para qué se puede utilizar.

La instalación por defecto de Ubuntu no trae ninguna regla pre configurada en audit. Podemos ver las reglas que se encuentran cargadas utilizando auditctl -l de la siguiente manera:

root@ubuntu:/home/juan# auditctl -l
No rules
root@ubuntu:/home/juan#

Reglas de audit

Existen 3 tipos de reglas en audit
  • Reglas de control: permiten modificar el comportamiento de audit.
  • Reglas de archivos (File system): permiten registrar los accesos y modificaciones a archivos y directorios.
  • Reglas de syscalls:  permiten registrar la utilización de las llamadas al system por parte de los procesos.

Reglas de control

Estas reglas van a definir el comportamiento de audit en lineas generales. Por ejemplo la cantidad de buffers disponibles, el comportamiento ante errores críticos, etc.

En el siguiente ejemplo configuramos  audit para que ante un evento crítico genere un kernel panic

root@ubuntu:/home/juan# auditctl -f 2
AUDIT_STATUS: enabled=1 flag=2 pid=882 rate_limit=0 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan#


Ahora con la opción "-e 2" bloqueamos la configuración para que no pueda ser modificada salvo con un reinicio:

root@ubuntu:/home/juan# auditctl -e 2
AUDIT_STATUS: enabled=2 flag=2 pid=882 rate_limit=0 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan# auditctl -e 1
Error sending enable request (Operation not permitted)
root@ubuntu:/home/juan# auditctl -e 0
Error sending enable request (Operation not permitted)
root@ubuntu:/home/juan# auditctl -f 1
Error sending failure mode request (Operation not permitted) root@ubuntu:/home/juan#


ninguna operación que modifique el comportamiento de audit estará permitida, la única forma de solucionar esto es reiniciar el sistema. Una vez reiniciado el sistema, enabled vuelve a ser 1, es decir se vuelve a habilitar el cambio de configuración.

root@ubuntu:/home/juan# auditctl -s
AUDIT_STATUS: enabled=1 flag=1 pid=880 rate_limit=0 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan#

Reglas de archivos

Las reglas de archivos o directorios tienen 3 componentes, el path del archivos indicado por "-w", los permisos indicados por "-p" y una cadena indicada por "-k".

Ejemplo 1: para reportar cada vez que un usuario ejecuta el comando ping hacemos:

root@ubuntu:/home/juan# auditctl -w /bin/ping -p x -k "haciendo ping xD"
root@ubuntu:/home/juan# auditctl -l
LIST_RULES: exit,always watch=/bin/ping perm=x key=haciendo ping xD
root@ubuntu:/home/juan#  


Podemos ver que la regla fue agregada y si ahora hacemos ping

juan@ubuntu:~$ ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=58 time=62.0 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 62.040/62.040/62.040/0.000 ms
juan@ubuntu:~$


veremos las siguientes líneas en /var/log/audit/audit.log

type=EXECVE msg=audit(1435008355.184:39): argc=4 a0="ping" a1="-c" a2="1" a3="8.8.8.8"
type=CWD msg=audit(1435008355.184:39):  cwd="/home/juan"
type=PATH msg=audit(1435008355.184:39): item=0 name="/bin/ping" inode=130121 dev=08:01 mode=0104755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1435008355.184:39): item=1 name=(null) inode=130569 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL


Y qué demonios significa todo eso...??? Vamos a desmenuzar un poco las líneas para entender qué nos está diciendo audit con ese log:

  • Los logs comienzan con un campo tipo type que indica el tipo de registro en cuestión.
  • A continuación viene el campo msg que se encuentra formado por el audit unique ID (los logs que formen parte de un mismo evento se pueden reconocer porque llevarán el mismo audit unique ID) compuesto por el timestamp (1435008355.184) y un identificador (39). Luego de esta campo veremos una lista variable de valores tipo name=value que dependerán del tipo de registro y harán referencia a los distintos campos informativos provistos por el kernel o las aplicaciones en espacio de usuario
Entonces si con esto analizamos las 3 lineas que registró audit daemon vemos:
  • El primer registro es de tipo EXECVE, este registra los parámetros de la syscall execve con la que se ejecutó ping.
  • El segundo registro es de tipo CWD y obtuvo el current working directory del evento /home/juan.
  • El tercer registro (item=0 de la syscall) es de tipo PATH y obtuvo información acerca del archivo /bin/ping, por ejemplo 
    • El inodo del archivo (130569)
    • El dispositivo donde se encuentra (08:01)
    • Los permisos del archivo 4755
    • OUID y OGID ID del usuario dueño y grupo del usuario dueño 0 y 0 en este caso.
  • El cuerto registro (item=1 de la syscall) hace referencia a la llamada al dynamic linker ld que es utilizado para cargar las librerias compartidas que precisa ping para ejecutarse.
    • Vemos el inodo del archivos utilizado (130569)
    • Los permisos (0755)
Ahora podemos comprobar todo esto que vimos en los logs de audit consultando directamente a los archivos en cuestión:

root@ubuntu:/home/juan# ls -lai /bin/ping
130121 -rwsr-xr-x 1 root root 38932 May  7  2014 /bin/ping
root@ubuntu:/home/juan# ls -lai /lib/i386-linux-gnu/ld-2.19.so
130569 -rwxr-xr-x 1 root root 134380 Feb 25 16:58 /lib/i386-linux-gnu/ld-2.19.so
root@ubuntu:/home/juan#  


Por suerte la información es consistente.

Ejemplo 2: ahora algo un poco más sencillo, registremos las alteraciones a un determinado archivo, por ejemplo /etc/resolv.conf

root@ubuntu:/home/juan# auditctl -w /etc/resolv.conf -p wa -k "resolv.conf"
root@ubuntu:/home/juan# auditctl -l
LIST_RULES: exit,always watch=/bin/ping perm=x key=haciendo ping xD
LIST_RULES: exit,always watch=/etc/resolv.conf perm=wa key=resolv.conf
root@ubuntu:/home/juan#


Ahora simplemente agregamos una linea al final con un comentario de la siguiente manera:

root@ubuntu:/home/juan# echo "#comentario" >> /etc/resolv.conf
root@ubuntu:/home/juan#


y vemos qué hay en los logs:

type=SYSCALL msg=audit(1435095642.812:48): arch=40000003 syscall=5 success=yes exit=3 a0=9fba768 a1=8441 a2=1b6 a3=9fba768 items=4 ppid=1033 pid=1034 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="bash" exe="/bin/bash" key="resolv.conf"
type=CWD msg=audit(1435095642.812:48):  cwd="/home/juan"
type=PATH msg=audit(1435095642.812:48): item=0 name="/etc/" inode=6957 dev=00:10 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT
type=PATH msg=audit(1435095642.812:48): item=1 name=(null) inode=260560 dev=08:01 mode=0120777 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1435095642.812:48): item=2 name=(null) inode=8112 dev=00:10 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1435095642.812:48): item=3 name=(null) inode=8112 dev=00:10 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL


A decir verdad esperaba un log un poco mas humano y simple para este escenario, pero viene genial para aprender a usar otra de las herramientas de audit. La herramienta es ausearch, y nos permitirá buscar dentro de los logs determinados eventos y hasta es capaz de interpretar buena parte de los valores y reemplazarlos por palabras mas humanas, por ejemplo si buscamos eventos con la key resolv.conf veremos lo siguiente:

root@ubuntu:/home/juan# ausearch -i -k "resolv.conf"
----
type=CONFIG_CHANGE msg=audit(06/23/15 22:37:40.856:45) : auid=juan ses=1 op="add rule" key=resolv.conf list=exit res=yes
----
type=CONFIG_CHANGE msg=audit(06/23/15 22:38:05.520:46) : auid=juan ses=1 op="add rule" key=resolv.conf list=exit res=yes
----
type=PATH msg=audit(06/23/15 22:40:42.812:48) : item=3 name=(null) inode=8112 dev=00:10 mode=file,644 ouid=root ogid=root rdev=00:00 nametype=NORMAL
type=PATH msg=audit(06/23/15 22:40:42.812:48) : item=2 name=(null) inode=8112 dev=00:10 mode=file,644 ouid=root ogid=root rdev=00:00 nametype=NORMAL
type=PATH msg=audit(06/23/15 22:40:42.812:48) : item=1 name=(null) inode=260560 dev=08:01 mode=link,777 ouid=root ogid=root rdev=00:00 nametype=NORMAL
type=PATH msg=audit(06/23/15 22:40:42.812:48) : item=0 name=/etc/ inode=6957 dev=00:10 mode=dir,755 ouid=root ogid=root rdev=00:00 nametype=PARENT
type=CWD msg=audit(06/23/15 22:40:42.812:48) :  cwd=/home/juan
type=SYSCALL msg=audit(06/23/15 22:40:42.812:48) : arch=i386 syscall=open success=yes exit=3 a0=0x9fba768 a1=O_WRONLY|O_CREAT|O_APPEND a2=0666 a3=0x9fba768 items=4 ppid=1033 pid=1034 auid=juan uid=root gid=root euid=root suid=root fsuid=root egid=root sgid=root fsgid=root tty=pts0 ses=1 comm=bash exe=/bin/bash key=resolv.conf
root@ubuntu:/home/juan#


ahora se puede ver como muchos campos fueron reemplazados por valores un poco mas conocidos (se reemplazo syscall 5 por open, los ouid y ogid por root, etc).

Reglas de syscalls

Estas reglas ya implican un mayor detalle a la hora de definirlas. Son necesarios los siguientes parámetros:
  • -a action,filter: la acción puede ser always o never. Cuando se elige never, no se generan registros para este evento.
  • -S syscall: indica la syscall he se registrará.
  • -F name=value: permite afinar aún mas el filtro para desencadenar el regirstro de la syscall, por ejemplo se podría filtrar una determinada syscall para el proceso con pid=1000.
  • -k key: una clave para facilitar las búsqueda dentro de los logs.
Por ejemplo, vamos a crear una regla para registrar todos los eventos donde el usuario Juan elimine un archivo

root@ubuntu:/home/juan# grep juan /etc/passwd
juan:x:1000:1000:juan,,,:/home/juan:/bin/bash
root@ubuntu:/home/juan# auditctl -a always,exit -S unlink -F uid=1000 -k juan_delete
root@ubuntu:/home/juan# auditctl -l
LIST_RULES: exit,always uid=1000 (0x3e8) key=juan_delete syscall=unlink
root@ubuntu:/home/juan# 


una vez agregada la regla probamos eliminando un archivo:

juan@ubuntu:~$ touch file
juan@ubuntu:~$ ll -i file
158216 -rw-rw-r-- 1 juan juan 0 Jun 25 22:25 file
juan@ubuntu:~$ rm file
juan@ubuntu:~$


y controlamos los logs con ausearch interpretado (-i)

root@ubuntu:/home/juan# ausearch -i -k juan_delete
----
type=CONFIG_CHANGE msg=audit(06/25/15 22:19:59.309:35) : auid=juan ses=1 op="add rule" key=juan_delete list=exit res=yes 

----
type=PATH msg=audit(06/25/15 22:31:40.089:62) : item=1 name=file inode=158216 dev=08:01 mode=file,664 ouid=juan ogid=juan rdev=00:00 nametype=DELETE
type=PATH msg=audit(06/25/15 22:31:40.089:62) : item=0 name=/home/juan inode=149893 dev=08:01 mode=dir,755 ouid=juan ogid=juan rdev=00:00 nametype=PARENT
type=CWD msg=audit(06/25/15 22:31:40.089:62) :  cwd=/home/juan
type=SYSCALL msg=audit(06/25/15 22:31:40.089:62) : arch=i386 syscall=unlinkat success=yes exit=0 a0=0xffffff9c a1=0x86bdab8 a2=0x0 a3=0x0 items=2 ppid=1209 pid=1241 auid=juan uid=juan gid=juan euid=juan suid=juan fsuid=juan egid=juan sgid=juan fsgid=juan tty=pts1 ses=2 comm=rm exe=/bin/rm key=juan_delete
root@ubuntu:/home/juan#


a partir de los logs del evento, podemos averiguar bastante información de lo que sucedió como: el nombre del archivo eliminado, sus permisos, el comando utilizado la syscall, etc. Queda claro que no pudimos evitar la eliminación, sin embargo quedó bien registrada y documentada.

Rompamos

Bien a modo de última prueba se me ocurrió comprobar el funcionamiento de la opción "-f 2" que ante un evento desafortunado debería generar un kernel panic. Para forzar el evento desafortunado vamos a setear un rate limit the mensajes bajo, 2 por seguno:

root@ubuntu:/home/juan# auditctl -s
AUDIT_STATUS: enabled=1 flag=1 pid=885 rate_limit=0 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan# auditctl -r 2
AUDIT_STATUS: enabled=1 flag=1 pid=885 rate_limit=2 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan# auditctl -f 2
AUDIT_STATUS: enabled=1 flag=2 pid=885 rate_limit=2 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan#

ahora con el nuevo rate limit y la opción de flag 2 seteada forzamos unos cuantos mensajes y boala:


la VM quedó completamente inutilizable.

Resumen

Linux System Audit es sin lugar a dudas una muy buena opción para registrar eventos en el sistema. Sin embargo hay algunas cosas para tener en cuenta:
  • Audit no proveé protección extra, solo reporta eventos. No sustituye ni a SELinux ni Apparmor, los complementa en todo caso.
  • Como toda medida de seguridad, puede afectar la performance del sistema si se aplica desmedidamente. El mismo man de auditcl lo confirma con "Syscall  rules get evaluated for each syscall for every program. If you have 10 syscall rules, every program on your system will delay during a syscall  while  the  audit system evaluates each rule. Too many syscall rules will hurt performance."
  • Las reglas se pueden poner de manera permanente en un archivo dentro de /etc/audit/rules.d/

viernes, 19 de junio de 2015

Jugando con Linux Audit System Parte I

Linux Audit System es un componente extra que viene en el kernel de Linux hace ya algunos años y provee la capacidad de obtener información sobre el sistema que podría ser relevante desde el punto de vista de la seguridad. Basicamente, a partir de un conjunto de reglas, es capaz de generar logs ante determinados eventos del sistema. Pero OJO!, Linux Audit System no es un mecanismo de control como SELINUX, sino un mecanismo de reporte.

Algunas de los detalles que podemos obtener con Linux Audit System son:
  • Fecha, hora, resultado y tipo de un evento.
  • Asociación del evento con el usuario que lo inició.
  • Registro de uso de los mecanismo de autenticación.
  • Modificaciones de los propios archivos de configuración de audit.
  • etc
Algunos de los casos de uso mas comunes donde se puede aplicar Linux Audit System son:
  • Vigilar el acceso a determinados archivos
  • Vigilar el uso de determinadas syscalls
  • Registrar los comandos ejecutados por un usuario:
  • Registrar eventos de seguridad del sistema: eventos como los ingresos de contraseña incorrecta pueden ser registrados por audit.
  • Monitoreo de actividades de red: iptables puede comunicarse con audit y generar eventos.

¿Cómo está formado?

Linux System Audit se divide en dos partes, las utilidades de espacio de usuario y el procesamiento de las syscalls en el espacio kernel.

Los componentes en espacio de kernel reciben las syscalls del espacio de usuario y las pasan por unos filtros (user, task y exit). Una vez que la syscall fue filtrada es procesada por otro filtro, llamado Exclude, donde a partir de las reglas de audit se envía o no al Audito Daemon para continuar su procesamiento.

Los componentes en espacio de usuario como el Audit Daemon recolectan la información del kernel y generan los registros correspondientes. Además de Audit Daemon, hay otros utilitarios que forman parte de las herramientas en espacio de usuario como:
  • auditcl: permite comunicarse con los componentes en el kernel para su configuración.
  • ausearch: herramienta para la búsqueda de registros en los logs generados por audit
  • audisp: interactúa con el audit daemon y puede enviar eventos a otras aplicaciones.
  • aureport: herramienta para generar reportes a partir de los logs obtenidos.
  • etc.

¿Cómo saber si mi kernel soporta Linux System Audit?

Lo mas probable es que si xD, pero por si las moscas podes corroborarlo a partir de la configuración del kernel que estás corriendo, por ejemplo de la siguiente manera:

juan@moon:~$ uname -r
3.13.0-49-generic

juan@moon:~$ grep CONFIG_AUDIT /boot/config-`uname -r`
CONFIG_AUDIT_ARCH=y
CONFIG_AUDIT=y
CONFIG_AUDITSYSCALL=y
CONFIG_AUDIT_WATCH=y
CONFIG_AUDIT_TREE=y
juan@moon:~$


Si todas estas opciones se encuentran en Y significa que el kernel tiene compilado Linux Audit System.

¿Cómo instalar las herramientas de espacio de usuario?

Exacto, incluso si el kernel soporta Linux Audit aún faltan las herramientas de espacio de usuario. La disponibilidad y el nombre de los paquetes va a depender bastante de cada distribución, pero los clásicos serían:

yum install audit
apt-get install auditd

De acuerdo al sistema operativo será necesario o no activar auditd como servicio.

root@ubuntu:/home/juan# service auditd status
 * auditd is running.
root@ubuntu:/home/juan#


Comandos básicos

Antes de pasar a las reglas que van a gobernar el comportamiento de auditd vamos a ver el comando esencial para controlarlo, auditctl. Algunos de los flags mas interesantes de auditctl son
  • -b NUM: número de buffers disponibles para audit system. Si los buffers se llenan se evaluará la flag de error para ver qué hacer.
  • -f [0-2]: esta opción permite definir el comportamiento del kernel ante un evento adverso como backlog limit alcanzado, problema de comunicación entre con audit daemon, etc. La opción 2 generará un kernel panic.
  • -e [0-2]: este flag permite 0-deshabilitar temporalmente system audit, 1-habilita system audit nuevamente, 2-bloquea la configuración de system audit, cualquier intento por cambiar la configuración será denegado y registrado. Solo reiniciando se puede salir de esa situación.
  • -s: reporta el estado del kernel audit system, mostrará como fueron configuradas las otras flags.
root@ubuntu:/home/juan# auditctl -s
AUDIT_STATUS: enabled=1 flag=1 pid=1396 rate_limit=0 backlog_limit=320 lost=0 backlog=0
root@ubuntu:/home/juan#  


A partir de la salida anterior, vemos que por defecto audit se encuentra habilitado pero no bloqueado, que ante una condición crítica se imprimirá la condición con printk. El pid que se puede ver es el del audit daemon que se encuentra corriendo. El resto de los parámetros se pueden ver desde el man xD, man auditctl.

En la próxima entrada, agregaremos reglas!

lunes, 8 de junio de 2015

UDP en GNU/Linux Parte II

Esta entrada es la continuación de UDP en GNU/Linux Parte I y la idea es continuar con las pruebas para entender un poco mas cómo funciona el stack UDP en GNU/Linux.

En la publicación anterior quedó pendiente probar packet receive errors  en una condición de buffer insuficiente. Dijimos que esta condición se puede dar en un escenario donde los paquetes arriban al host a una tasa mayor que con la que son retirados del buffer del socket. Para comprobar esto vamos a precisar algunos números y un receptor UDP lento.

Receptor UDP lento

La verdad es que no se me ocurrió ninguna idea maravillosa para hacer un receptor lento de paquetes UDP, así que hurgando un poco en internet armé un pequeño cliente.c (si ¬¬ .c, ¿qué tiene de malo?). El cliente básicamente realiza las siguientes tareas:
  • Abre un socket UDP y escucha en el puerto 5555.
  • Imprime en pantalla el tamaño del buffer del socket.
  • Luego en un loop toma del buffer del socket 1 paquete por segundo e imprime en pantalla un contador de paquetes leidos.
Acá pueden ver una ejecución abortada de ejemplo:
juan@ubuntu:~$ ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840
^C
juan@ubuntu:~$ 


Como ven el tamaño del buffer por defecto es de 163840 bytes (160Kbytes). Este valor no es aleatorio ni nada que se le parezca, el tamaño está definido por la variable de kernel net.core.rmem_default

root@ubuntu:/home/juan# sysctl -a|grep rmem_default
net.core.rmem_default = 163840
root@ubuntu:/home/juan#


por ejemplo, se podría ampliar de la siguiente manera

root@ubuntu:/home/juan# sysctl -w net.core.rmem_default=1638400
net.core.rmem_default = 1638400
root@ubuntu:/home/juan# sysctl -a|grep rmem_default
net.core.rmem_default = 1638400
root@ubuntu:/home/juan# 


y se vería reflejado en la próxima ejecución de nuestro cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 1638400
^C
root@ubuntu:/home/juan#  


Entonces, resumiendo los números:
  • Tamaño del buffer de recepción 163840 bytes.
  • Enviaremos paquetes de 1024 bytes forjados con Hping3. Por lo tanto, 160 paquetes son suficientes para llenar el buffer.
  • Se enviarán 2000 paquetes a una tasa de 10 paquetes por segundo.
  • La aplicación consume 1 paquete cada 1 segundo.
Está claro que si esta situación se prolonga lo suficiente en el tiempo, en algún momento el buffer se llenará y el sistema deberá descartar paquetes. Si bien es una condición un poco exagerada a fines prácticos resulta útil.

Antes de lanzar nuestro cliente lento tomamos los valores de las estadísticas del sistema:

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


lanzamos el cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840


y apuntamos el cañón hping3 al host:

root@moon:/home/juan/pruebas# hping3 192.168.0.10 -2 -c 2000 --fast -E 1024 -d 1024 -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 + 1024 data bytes
[main] memlockall(): Success
Warning: can't disable memory paging!

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


esta vez hay nuevos parámetros en hping3:
  • --fast: le indica que debe mandar 10 paquetes por segundo.
  • --E 1024: indica que debe meter como payload de los paquetes el contenido del archivo llamado 1024 (un archivo con 1024 bytes en 0).
  • -d 1024: este parámetro es el que realmente indica cuántos bytes del archivo indicado por -E se van a mandar como payload. En esta caso se arman datagramas de 1024 bytes.
  • -c 2000: indica que se enviarán 2000 paquetes.
en definitiva se enviaron 2000 paquetes con 1024 bytes como payload cada uno de ellos (en total se enviaron unos 2048000 bytes, claramente superamos el tamaño del buffer).

Vemos la salida de la aplicación (dado que la función de lectura es bloqueante, la aplicación no termina por sus propios medios después de leer el último paquete, luego de unos segundos sin nuevos paquetes la aborté con Ctrl+C)

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 163840
Paquete numero 0
Paquete numero 1
Paquete numero 2
Paquete numero 3
Paquete numero 4
Paquete numero 5
Paquete numero 6
Paquete numero 7
...

Paquete numero 279
Paquete numero 280
Paquete numero 281
Paquete numero 282

^C
root@ubuntu:/home/juan#


Entonces se enviaron 2000 paquetes pero nuestra aplicación lenta solo pudo leer 283 de ellos!!! A donde fueron los restantes mmmm 1717 paquetes?

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


Claramente fueron al cielo de los paquetes. Fueron completamente descartados por el kernel debido a que no había espacio donde almacenarlos.

Dado que leemos 1 paquete por segundo y agregamos 10 por segundo, podríamos decir que llegan un neto de 9 paquetes por segundo al buffer. A 9 paquetes por segundo el buffer se llena en unos 17 segundos aproximadamente. Es decir que, si la matemática no me falla, a partir de los 17 segundos estaríamos perdiendo al menos 9 paquetes por segundo.

Hay dos maneras de enfrentar esta situación, dado que UDP no tiene control de flujo e implementarlo en la capa de aplicación sería un delirio, podemos:
  • Incrementar la eficiencia del consumidor, leyendo mas paquetes por segundo.
  • Aumentando el tamaño del buffer de recepción para poder soportar el asedio de paquetes sin perderlos.
Entonces supongamos que mejoramos el código y multiplicamos por 7 la eficiencia de la aplicación y ahora es capaz de leer 7 paquetes por segundo. Matemáticamente estamos en la misma (en el horno), estarían ingresando un neto de 3 paquetes por segundo, ahora el buffer se llenaría en 53 segundos y volvemos a tener pérdida de paquetes. El paso siguiente es aumentar el tamaño del buffer. Sabemos que vamos a recibir una ráfaga de 2000 paquetes de 1024 bytes, osea 2Mbytes es decir que podríamos configurar un buffer de 2MBytes y quedarnos re tranquilos, pero sería algo extremo.

Con el nuevo código llenamos el buffer en 53 segundos, osea que aún nos quedan 147 segundos con pérdida de paquetes. Pero ahora vamos a ampliar el buffer multiplicándolo por 4 (NOTA: vale aclarar que este nuevo cambio se aplicará a todos los nuevos sockets creados).

root@ubuntu:/home/juan# echo "163840*4"|bc
655360
root@ubuntu:/home/juan# sysctl -w net.core.rmem_default=655360
net.core.rmem_default = 655360

root@ubuntu:/home/juan# sysctl -w net.core.rmem_max=655360
net.core.rmem_max = 655360

root@ubuntu:/home/juan# sysctl -a | grep net.core.rmem_default
net.core.rmem_default = 655360
root@ubuntu:/home/juan# 


En teoría, con este nuevo buffer de solo 4 veces mayor tamaño no deberíamos ver pérdida de paquetes.

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


Ejecución cliente

root@ubuntu:/home/juan# ./cliente
--Socket creado!
-Configuracion del socket tamanio del buffer de recepcion: 655360
Paquete numero 7
Paquete numero 14
Paquete numero 21
Paquete numero 28
Paquete numero 35
Paquete numero 42
Paquete numero 49
Paquete numero 56

...
Paquete numero 1960
Paquete numero 1967
Paquete numero 1974
Paquete numero 1981
Paquete numero 1988
Paquete numero 1995
2000 paquetes leidos!!!

root@ubuntu:/home/juan#

Y corroboramos con las estadísticas:

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

root@ubuntu:/home/juan#

El aumento del tamaño del buffer y la mejora en la aplicación permitieron que esta vez no haya pérdida de paquetes.