Muy buenas !
Continuamos en nuestra senda del networking path 😉 pero como siempre, orientado hacia GNU/Linux, es decir: ip fighters on the real linux world 😉  No se trata de nada que tenga que ver con el alto rendimiento o decisiones de escala a nivel de CPD, o análisis de tiempos estadÃsticos de convergencia de multi-links, o de la importancia de TCP Multipath en nuestro futuro cercano. Lo que queremos presentaros son pruebas de concepto de como «elevar» la perspectiva de un firewall GNU/Linux para que tome decisiones de forma dinámica en user-space, sin ser una configuración estática cerrada.
Generalmente, los frontends o pseudo-appliance full instalables tanto para Netfilter/Iptables, como para BSD/Packet Filter estilo nuestro gran amigo PFSense, guardan su config en un XM/SQLite, y cada vez que se hacen cambios re-invocan por debajo los comandos que procedan para flushear/añadir/modificar rules, para acabar teniendo en memoria todas las reglas ya configuradas. Es decir, realmente son reglas estáticas. Aunque claro está, la propia regla puede tener cierto «dinamismo» como el hecho de poder limitar las conexiones por segundo o similar, o tirar de connection tracking si lo tenemos habilitado. En este post de nuestro nuevo blog, nos referimos mas bien a algo que sea totalmente impredecible, es decir, como si fuera un proceso hijo que se lanza con system() o popen() y que él decida con su exit code lo que hacer. O incluso si nos ponemos en plan nostálgicos, algo estilo RealtimeBattle, muy usado antiguamente en las party’s y tal, pero hasta donde recuerdo/voy lurkeando – prácticamente olvidado en los FP’s y Universidades cercanos, esto la verdad es que se merecerÃa casi un post completo para revindicar su uso educativo 😉
Bueno, basta ya de preámbulos, a lo que nos referimos en concreto es a NFQUEUE de Netfiler, mecanismo compuesto del Target correspondiente en iptables para encolar paquetes y una librerÃa linkable desde user-space para poder acceder a esa cola de paquetes; Y, lo mas importante: No sólo poder acceder, que para eso nos bastarÃa un sniffer y nos dejamos de batallas, sino poder sentenciar con un un veredicto sobre el paquete e informar a Netfilter de si debe aceptar o no el paquete, lo cual es realmente magic!
Es decir, como todo en este universo de GNU/Linux y del open source en general: Las piezas están muchas ya hechas, bienvenidos todos al mundo de los puzzles – tuberÃas – relaciones – xxx para unirlo todo y hacer que funcione la maquinaria 😉 y, como no podÃa ser menos: firewalling tb ! Algo que generalmente suele estar menos integrado con la Web 5.0 o las cosas modernas jijiji.
Es decir, recapitulando: Podemos tomar la decisión de que hacer con nuestro paquete en un programa nuestro, que puede a su vez preguntárselo a /dev/urandom, recorrerse una lista de tropecientos teras para ver que hacer o tener un algoritmo hyperoptimizado para poder decidir muy rápido lo que hay que hacer. Es decir, para bien o para mal, depende la decisión de algo que podemos construir nosotros.
¿Cómo funciona exactamente?
Parte IPtables
Con iptables, podemos decidir enviar el paquete a la cola de decisión en base a los criterios de matching habituales, es decir: ip origen/destino, puertos, estado o incluso por paÃs(geoip) o layer7 si tenemos los patches correspondientes aplicados. A modo de ejemplo, para enviar cualquier ping que recibamos en entrada (es decir, no en routing):
iptables -I INPUT -p icmp -j NFQUEUE
Con este ejemplo, estamos diciendo a Netfilter que todos los paquetes que tengan como destino el propio sistema (localhost), entren por el interfaz/nic que sea, y tengan como protocolo ICMP, sea enviados al QUEUING de userspace. En este caso, no hemos especificado Queue, asà que por defecto lo envÃa a la cola 0. Si quisiéramos enviar a una cola diferente bastarÃa con usar: –queue-num X
Podemos comprobar que estamos matcheando bien los paquetes lanzando un listado de rules y sus counters de matching:
root@zgornfqueuepruebas:~# iptables -L -n -v -x Chain INPUT (policy ACCEPT 45 packets, 6669 bytes) pkts bytes target prot opt in out source destination 1 84 NFQUEUE icmp -- * * 0.0.0.0/0 0.0.0.0/0 NFQUEUE num 0
Como podemos ver en el ejemplo, «algo» está matcheando, dado que el contador de paquetes sube y tal.
Igualmente, tb podemos comprobar que está encolando, dado que: El comportamiento por defecto a nivel de veredicto es el de denegar. Esto supone que si encolamos paquetes y no tenemos una aplicación que los va recuperando y emitiendo un veredicto positivo: se  hará drop.
Libnfqueue
Esta librerÃa es la que nos permite vincular nuestra aplicación a las colas y actuar como juez 😉 Cabe destacar que para los Pythonistas – Saghul 😉 – y Perl power rangers existen bindings especÃficos.
Si estamos en Debian 8, por ejemplo, a nivel de soft a instalar tendrÃamos:
apt-get install libnfnetlink-dev libnetfilter-queue-dev gcc
Una vez tenemos instalado, nuestro hello world tendrÃa una estructura tal que:
- Obtener el handle a la librerÃa NFQ.
- Quitar posibles vinculaciones de aplicaciones o sistemas anteriores.
- Vincularse a la cola.
- Entrar en bucle de: recepción=>análisis=>veredicto.
- Al acabar: desvincularse, cerrar handle de librerÃa y tal.
En este caso, nuestro primer hello world emitiremos un resultado siempre positivo, quedando el código mÃnimo viable para un hello world algo asà como (perdonar por las tabulaciones, parece que se han perdido en cpy-psting)
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <linux/types.h> #include <linux/netfilter.h> #include <libnetfilter_queue/libnetfilter_queue.h> /* :::: Funcion callback ::: ================ Es invocada cada vez que hay un paquete en la cola */ static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { u_int32_t id; struct nfqnl_msg_packet_hdr *ph; // Un poco de debugging ;) //printf("."); //fflush(stdout); // Obtenemos las headers: ph = nfq_get_msg_packet_hdr(nfa); // Y el ID de paquete, que hace falta para el veredicto final: id = ntohl(ph->packet_id); // Decidimos aceptar el paquete siempre, sin ningun IF ni nada: return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } int main(int argc, char **argv) { struct nfq_handle *h; struct nfq_q_handle *qh; int fd; int rv; char buf[4096] __attribute__ ((aligned)); printf("Obteniendo el handle de la libreria: "); h = nfq_open(); if (!h) { fprintf(stderr, "Ha fallado\n"); exit(1); } else printf(" OK !\n"); printf("Haciendo unbind (por si existe alguno de AF_INET): "); if (nfq_unbind_pf(h, AF_INET) < 0) { fprintf(stderr, "error nfq_unbind_pf()\n"); exit(1); } else printf(" OK!\n"); printf("Vinculando nfnetlink_queue de tipo nf_queue handler para AF_INET:"); if (nfq_bind_pf(h, AF_INET) < 0) { fprintf(stderr, "error nfq_bind_pf()\n"); exit(1); } else printf(" OK!\n"); printf("Creando la vinculacion de la funcion callback con Queue 0, socket receptor: "); qh = nfq_create_queue(h, 0, &cb, NULL); if (!qh) { fprintf(stderr, "error during nfq_create_queue()\n"); exit(1); } else printf(" OK !\n"); printf("Definiendo que cantidad de paquete queremos recibir (no queremos todo para estas pruebas): "); if (nfq_set_mode(qh, NFQNL_COPY_META, 0xffff) < 0) { fprintf(stderr, "FALLO el COPY_META mode\n"); exit(1); } else printf("OK\n"); fd = nfq_fd(h); printf("Todo Listo !\n Entrando en bucle principal de recepcion ..\n"); while ((rv = recv(fd, buf, sizeof(buf), 0))) { // Si todo ha ido bien, gestionamos el paquete: if (rv>=0) nfq_handle_packet(h, buf, rv); // es posible que tengamos packet loss porque // nuestro codigo es lento y se llena la queue: else if ( errno == ENOBUFS) { fflush(stdout); printf("!"); } // O "simplemente", que algo haya ido mal: else { printf("ERROR \n"); fflush(stdout); break; } } // Teoricamente, nunca llegaremos aqui, pero si llegamos // Habra que liberar bien y tal: printf("unbinding de queue 0\n"); nfq_destroy_queue(qh); printf("cerrando library handle\n"); nfq_close(h); exit(0); }
Como podéis ver en este ejemplo, hemos comentado las lÃneas de debugging, ya que realmente se nota el rendimiento. De hecho en nuestras pruebas se ve claramente.
Para compilarlo:
gcc -o nftest codigo.c -lnfnetlink -lnetfilter_queue
Primeras pruebas
Para probar el rendimiento, nos basaremos en el conocido IPERF , asà que lo que haremos será hacer matching en el puerto tcp  5001, en entrada y en salida y arrancar nuestro programa compilado:
iptables -I INPUT -p tcp --dport 5001 -j NFQUEUE iptables -I OUTPUT -p tcp --sport 5001 -j NFQUEUE ./nftest
Siendo los resultados rápidos:
Sin pasar por NFQUEUE
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 35716 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 1.09 GBytes 939 Mbits/sec
Pasando por NFQUEUE con debugging on-screen
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 35713 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 916 MBytes 768 Mbits/sec
Pasando por NFQUEUE sin debugging
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 35714 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 1.09 GBytes 938 Mbits/sec
Conclusiones preliminares de las primeras pruebas
Bueno, a parte de lo básico: It Works! ,  con nuestro mini código, que no hace nada, se puede decir que no hay nada de merma de rendimiento en una red gigabit, únicamente tener en cuenta que el debugging on-screen con el printf y fflush y tal ralentiza todo mucho (habrá que estar al loro en posteriores pruebas).
Otros aspectos interesantes antes de bajar más al barro
Cabe destacar esta lista de opciones:
–queue-bypass
Esta opción/extensión (a partir de kernel 2.6.39 / iptables 1.4.11) permite cambiar el comportamiento por defecto, de tal forma que si no hay «nadie escuchando» (no hay programa arrancado vinculado a la cola), el comportamiento será el de continuar a la siguiente rule de iptables, no descartar el paquete.
En función de lo que queramos montar puede ser interesante o peligrosa la opción
--queue-balance
Nos permite distribuir en varias colas (de forma lineal) los matchs, de tal forma que luego podrÃamos tener varios procesos diferentes / hilos y gestionar mejor el multi processing o lo que queramos montar. Es importante comentar que el mismo flujo, acorde al source code, acaba en la misma cola.
–fail-open
Si la cola donde se mandan los paquetes está llena / va con delay porque el proceso en user space está ralentizándose o similar, el comportamiento por defecto
en vez de descartar todos los paquetes cuando la cola está llena, los acepta. Aquà lo mismo que queue-bypass, es posible que nos interese, pero tb puede suponer un riesgo. Es decir, si hemos optado por meternos en este «fregado» es porque queremos «decidir», no solo hacer análisis, que para eso tiramos de libpcap. Con lo que si hay que decidir, hay que estar presente (tener cola vinculada y ser capaces de procesar al ritmo que nos pase el kernel)
Interface en /proc
De cara a comprobaciones, scripting de monitorización y tal, Netfilter nos aporta una interface RO en el pseudo-fs PROC
cat /proc/net/netfilter/nfnetlink_queue
Manos a la obra: Probando el rendimiento
Hipótesis y planteamiento
Lo que sobre-entendemos (dado que nunca lo hemos tenido que hacer, «hasta ahora») es que querremos usar estos mecanismos de NFQUEUE para algo gestionable desde el panorama web. Asà que nos pondremos dos situaciones:
- MySQL. Esto serÃa el caso de base, que cualquiera que está empezando quisiera hacer «Todo gestionable 100% en BBDD, que lo pueda cambiar desde cualquier lado, gestionable 100%», sin pensar demasiado en rendimiento o en miles de paquetes por segundo.
- Memcached. Como evolución de lo anterior, vamos a hacer lo mismo pero en lugar de atacar una BBDD, atacaremos un servicio Memcache.
Estas pruebas son únicamente para ir viendo, no estamos haciendo una tesis doctoral y buscar el lÃmite máximo, ni ejecutando un proyecto (mas que nada porque vamos sin requisitos, haciendo directamente lo que nos va gustando jijiji 😉 ).
Prueba con memcached
Preparación de entorno
Lo primero, obviamente, instalar memcached, Debian style 😉 :
apt-get install memcached
Vamos a guardar una IP en la key: autorizada, del tal forma que luego nuestro programa consultará por la key ‘autorizada’ y sacará su valor para compararlo, siguiendo algunas tips vistas en algunas páginas, lo hacemos estilo oneliner, desde Bash jiji :
root@zgornfqueuepruebas:~# echo -e 'add autorizada 0 0 11\r\n10.10.0.201\r' | nc localhost 11211 STORED
y para confirmar que existe:
root@zgornfqueuepruebas:~# echo -e 'get autorizada\r' | nc localhost 11211 VALUE autorizada 0 11 10.10.0.201 END
Es decir, ya tenemos nuestro server memcached y una key metida 🙂
Código de prueba
Partiendo del código Hello World expuesto anteriormente, nuestra función de callback debe poder obtener la IP del paquete, para ello:
- Importante: Se debe actuar en modo NFQNL_COPY_PACKET, por las pruebas que hemos hecho, en modo COPY_META no llegan.
- En la función del callback hay que obtener el paquete completo (nf_packet).
- A partir del paquete completo, obtenemos ip header, a partir de ahà sacamos la IP
A modo de ejemplo la función de callback en modo debug nos quedarÃa:
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { u_int32_t id; struct nfqnl_msg_packet_hdr *ph; struct nfqnl_msg_packet_hw *hwph; unsigned char *nf_packet; char saddr[2048]; int ret; // Obtenemos las headers: ph = nfq_get_msg_packet_hdr(nfa); // Y el ID de paquete, que hace falta para el veredicto final: id = ntohl(ph->packet_id); // Obtenemos la IP origen del paquete: // - Primero hay que obtener el paquete en si: ret = nfq_get_payload(nfa, &nf_packet); if ((ret <= 0)) { printf("Error, no hay paquete que recibir - wtf \n"); return; } struct iphdr *iph = ((struct iphdr *) nf_packet); inet_ntop(AF_INET, &(iph->saddr), saddr, sizeof(saddr)); fprintf(stdout,"Recibido con origen: %s\n",saddr); // Decidimos aceptar el paquete siempre, sin ningun IF ni nada: return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); }
Y si lo ejecutamos (acordarse de seguir con el target Queue en iptables), veremos algo asà como en modo debug:
root@zgornfqueuepruebas:~# ./nfq_test Obteniendo el handle de la libreria: OK ! Haciendo unbind (por si existe alguno de AF_INET): OK! Vinculando nfnetlink_queue de tipo nf_queue handler para AF_INET: OK! Creando la vinculacion de la funcion callback con Queue 0, socket receptor: OK ! Definiendo que cantidad de paquete queremos recibir (no queremos todo para estas pruebas): OK Todo Listo ! Entrando en bucle principal de recepcion .. Recibido con origen: 10.10.0.201 Recibido con origen: 10.10.0.201 Recibido con origen: 10.10.0.201 Recibido con origen: 10.10.0.201 Recibido con origen: 10.10.0.201
En lo que respecta la comprobación contra Memcached, primero hay que instalarlo ( libmemcached-dev) y el código de prueba serÃa:
Variables globales, para usarlas desde la función de callback cómodamente:
/* Globales, para las pruebas de Memcache */ memcached_server_st *servers = NULL; memcached_st *memc; memcached_return rc; char *key= "keystring"; char *value= "keyvalue";
Conectarse antes del callback (para ahorrar tiempo en cada paquete a analizar):
En el main, algo asà como:
printf("Realizando conexión a memcache: "); memc= memcached_create(NULL); servers= memcached_server_list_append(servers, "localhost", 11211, &rc); rc= memcached_server_push(memc, servers); if (rc == MEMCACHED_SUCCESS) printf(" OK ! \n"); else { printf("error conectando a memcache: %s\n",memcached_strerror(memc, rc)); exit(0); }
Realizar la comprobación en cada llamada a callback
Tras obtener el paquete, IP origen y tal, una comprobación rápida:
valor_memcache = memcached_get(memc, key_memcache, strlen(key_memcache), &len_memcache, &flags, &rc); if (rc == MEMCACHED_SUCCESS) { if (strcmp(saddr,valor_memcache) == 0) { free(valor_memcache); return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } else { free(valor_memcache); return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); } } else { printf("Key %s no se ha podido encontrar en memcached\n",key_memcache); return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); }
(no os preocupéis que al final del post colgaremos el código completo en un link fácil de wgetear :D) )
(y sÃ, tendrÃamos que hacer este código más seguro 😉 )
Resultados con Memcache
Tras realizar la prueba, y repetirla varias veces, observamos que mas o menos siempre andamos por el mismo valor, unos 140Mbits:
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 41390 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 168 MBytes 141 Mbits/sec
Y, lo mas importante, nuestro juez de paquetes tb empieza a hacer overflow en su cola (se muestran los ! printeados en el fallo de recepción).
Pruebas con MySQL
Antes que nada, recordar que este es un post ilustrativo, MySQL está instalado del APT como quien instala el VIM 😉 Vamos, que no se ha puesto ni query_cache ni nada, asà que los MySQL Ninjas que no lancen todavÃa sus misiles 😉
Preparación de entorno
Pues eso, a lo fácil: apt-get install mysql-server libmysqlclient-dev
Y en lo que respecta la estructura y valores, estilo one line:
mysql -uroot -p -e "create database nfqueue;use nfqueue;create table autorizada(ip varchar(50));insert into autorizada values('10.10.0.201');"
Código de prueba
Conexión y arranque inicial en Main
SerÃa algo asà como (teniendo con de tipo MYSQL):
printf("Realizando INIT de MySQL: "); con = mysql_init(NULL); if (con == NULL) { printf(" FAIL\n"); exit(1); } else printf("OK\n"); printf("Realizando Conexion a MYQSL: "); if (mysql_real_connect(con, "localhost", "root", "password", "nfqueue", 0, NULL, 0) == NULL) { printf(" FAILED\n"); exit(1); } else printf(" OKI\n");
Validación en la función de callback
Un ejemplo de como se podrÃa hacer la validación, sin grandes optimizaciones ni nada:
char consulta[2048]; sprintf(consulta,"select * from autorizada where ip like '%s'",saddr); if (mysql_query(con, (const char*)consulta)) { printf("Fail query ...\n"); fprintf(stderr, "%s\n", mysql_error(con)); return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); } MYSQL_RES *result = mysql_store_result(con); int num_rows = mysql_num_rows(result); mysql_free_result(result); if (num_rows >= 1) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); else return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); }
Resultados con MySQL
Con MySQL los resultados son la instalación por defecto (query caché activada):
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 41994 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 224 MBytes 188 Mbits/sec
Si optamos por deshabilitar la query caché totalmente:
zgor@irontrueno:~$ iperf -c 10.10.0.134 ------------------------------------------------------------ Client connecting to 10.10.0.134, TCP port 5001 TCP window size: 85.0 KByte (default) ------------------------------------------------------------ [ 3] local 10.10.0.201 port 42110 connected with 10.10.0.134 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 123 MBytes 103 Mbits/sec
Comparativa final
No podÃa faltar un gráfico ! Si hablamos de rendimiento, algo tenemos que poner para comparar jiji Aunque está claro que no está nada optimizado, es todo a grosso-modo, máquinas virtuales, … Pero bueno, a modo representativo:
Notas importantes:
- Lo que no aparece en esta gráfica es el consumo de CPU, en todos los casos salvo el Direct Accept (la función que retorna siempre accept), se ha disparado todo bastante, tanto de Memcached como de Mysql y del propio proceso.
- Esto realmente es un poco «prensa sensacionalista» jiji, esas gráficas no reflejan la realidad de los máximos posibles, el proceso es single-threaded, se podrÃa haber hecho sacando hilos y seguramente el rendimiento obtenido en MySQL o Memcached hubiera sido muchÃsimo más alto.
- Desde luego, no es válido para comparar MySQL y Memcached, ya que era una única tabla con una única entrada, en el mundo ideal de BBDD esto no suele ser nunca asà (aunque bueno, al menos, la query se direccionarÃa con algún index y deberÃa ir rápido tb).
Conclusión genérica
Por lo que hemos visto, con un ejecutable compilado que prácticamente no haga nada conseguimos casi Gigabit/Sec, y si ya tenemos algo de I/O con Memcache, todo decrece, pero seguimos teniendo 100-200Mbits sin haber realizado ni siquiera un enfoque multi-hilo / proceso. Es decir, haciendo de la peor manera posible se consigue esas cifras (nada despreciables).
De hecho, para simplificarlo, podrÃamos hacer incluso que nuestra aplicación no sea ni siquiera SMP, podrÃamos tener varias aplicaciones, cada una recibiendo paquetes de una cola diferente, y asà tendrÃamos uso de los diferentes procesadores sin complicarnos con fork() o phtread_create ! Súper KISS total !
Y sobre todo, que aquà estamos matcheando todo el tráfico de la prueba con IPERF, pero en posibles planteamientos quizás serÃa sólo con match de state y state NEW por ejemplo (para saber si las nuevas sesiones las permitimos) y las ESTABLISHED/RELATED las dejamos pasar .. Todo esto, es especulación, ya que como os contábamos, hemos montado este escenario por amor al arte, no porque tenga que cumplir unos requisitos con SLA’s y derivados 😉
¿Y si quisiéramos hacer routing dinámico?
Hasta aquÃ, los ejemplos son únicamente para ACCEPT o DROP, pero que pasarÃa si lo que quisiéramos es hacer policy routing ? Es decir, encaminar por un lado o por otro ? De una forma pseudo-resumida, para el tema de policy routing en GNU/Linux, lo más cómodo es usar Netfilter marks para enviar a una tabla u otra de iproute2 (estilo VRF):
- Creamos las diferentes tablas en /etc/iproute2/rt_tables
- Con ip rule indicamos que tipo de marca tiene que tener un paquete para ir a una tabla concreta: ip rule add fwmark 1 table XXXX
- Con Netfitler tenemos que marcar los paquetes (vÃa NFQUEUE, que es la gracia de todo esto)
Para esa marca vÃa NFQueue, en lugar de retornar de la función de análisis de paquetes en callback con nfq_set_verdict, deberÃamos retornar con:
nfq_set_verdict2 - like nfq_set_verdict, but you can set the mark.
Es decir, en detalle por pasos si quisiéramos decidir por dónde enrutar en base a un software externo:
- Declaramos un par de tablas de routing: lento,rapido
- En dichas tablas metemos como ruta por defecto una diferente (una por Fiber y la otra por cslip or ejemplo jiji).
- Con iptables envÃamos los paquetes en FORWARD hacia NFQUEUE
- ConstruÃmos un software que reciba de la cola y tome decisión de ACCEPT pero aplicando marcas.
- Con ip rule mandamos el paquete a una tabla u otra de routing.
Y fin, ya nos hemos construido un super router IP que rutará en base a lo que nos dé la gana 😉
Como si queremos rutar en base a si el tamaño del paquete es par o impar;  O mas real life:  si coincide con que un algoritmo de una red social importantÃsima ha predicho que cuando sea de dÃa en USA hay un contenido que se está haciendo viral y se necesitará más ancho de banda en este path u en este otro y hay que re-enrutar 😉
Un micro-inciso: ¿No es alucinante el poder de GNU/Linux?
Posible uso especial
A parte de los usos localizables facilmente, como el que se le da en Suricata por ejemplo, se nos ocurre proponer:
Anti voice-spam (SPIT) o protector de palabras «mal sonantes»
jejeje, esto de llevar años ya orbitando por las galaxias y planetas del mundo VoIP, nos genera cierta inclinación a procastinar sobre estas área 😉 Comentando por aquà se nos ha ocurrido algo que parece bastante viable:
- Paquetes que matchean puertos RTP se mandan a NFQUEUE
- Aplicación contra libnfqueue los va recibiendo
- Dicha aplicación tiene el payload  completo, asà que tirando de librtp o similar parece vÃable obtener una estructura con un WAV
- Llegados al WAV, podemos tirar de VoxLibx de Verbio Technologies para realizar WordSpotting y detectar palabras mal sonantes o spam.
Es decir, si hubiese tiempo y alguien que lo quiera, no parece una meta inalcanzable 🙂 Para rizar el rizo, se podrÃa inyectar un «beep» por ejemplo, pero habrÃa que ir trackeando RTCP (secuencias, etc …) estilo MiTM, algo que ya requiere mas súper poderes 😉
Esto básicamente tb lo puede hacer un sniffer, pero luego para colgar o bloquear el audio tendrÃa que tirar contra Kamailio / Asterisk / X … Eso sÃ, quizás serÃa bastante mas sencillo :D)
Despedida y cierre 😉
Pues nada, esperemos que os haya gustado  esta aventurilla con la que hemos estado peleando en esta ocasión. Ha sido algo bastante especÃfico de Netfilter, pero la verdad es que nunca se sabe cuando puedes llegar a necesitar algo de nuestro amado Kernel y aplicaciones GNU o derivadas, lo mismo en un proyecto con hardware embebido programando  una función X de seguridad nos viene bien este mecanismo, o lo mismo nos piden construir algo tipo software que enrute paquetes IP en base a geolocalización de twitters 😉 o actuar como un auténtico BOFH: sonriendo en una pantalla si un paquete te gusta o no XDDD
Enlace al código de pruebas en: GITHUB/IRONTEC
Queremos tu opinión :)