Routing avanzado con el núcleo Linux Eric Van Buggenhaut Andago eric@andago.com El núcleo Linux, a partir de la version 2.4 nos ofrece una interfaz que permite implementar herramientas profesionales de alto nivel en cuanto a la gestión del tráfico de paquetes IP. Nos permite hacer cosas como tuneles IP, tablas de routing multiples, reserva de ancho de banda, multicasting, proxy ARP y mucho más. Esas funcionalidades estaban hasta ahora solo disponibles en routers proprietarios de gama alta y de precio casi prohibitivo. El núcleo linux nos permite implementarlas de modo más seguro, mas económico y con más rendimiento, además de desarrollar nuestras propias herramientas específicas. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- Introducción A partir de la version 2.4 del núcleo Linux, está disponible un socket llamado NETLINK que permite implementar en espacio usuario (user space) código de gestión de paquetes y tráfico IP. Téoricamente, este socket, por su naturaleza, nos permite implementar cualquier código. Sin embargo, nos vamos a centrar en el código escrito por Alexey Kuznetsov y disponible bajo el nombre 'iproute' bajo licencia GPL. Esta accesible por ftp:/ /ftp.inr.ac.ru/ip-routing El código está idsponible como paquete Debian iproute *deb Vamos a ver a lo largo de esta charla lo que nos permite hacer este código. * unificar los comandos relacionados con la gestión del tráfico IP, sea de redes, de interfaces, ... * uso de tablas de routing multiples * creación de tuneles IP * reserva de ancho de banda * gestión de direcciones multicast * gestión de tablas ARP * monitorización de los periféricos, direcciones y rutas. ------------------------------------------------------------------------------- Configurar el sistema Para que funcione el iproute, necesitamos configurar el núcleo para que provee el socket NETLINK que nos interesa. Vienen en /usr/src/linux/.config bastante opciones que nos permiten adaptar el kernel a nuestras necesidades : CONFIG_NETLINK=y CONFIG_RTNETLINK=y # CONFIG_NETLINK_DEV is not set CONFIG_NETFILTER=y CONFIG_NETFILTER_DEBUG=y # CONFIG_FILTER is not set CONFIG_UNIX=y CONFIG_INET=y # CONFIG_IP_MULTICAST is not set CONFIG_IP_ADVANCED_ROUTER=y CONFIG_RTNETLINK=y CONFIG_NETLINK=y CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_ROUTE_FWMARK=y CONFIG_IP_ROUTE_NAT=y CONFIG_IP_ROUTE_MULTIPATH=y CONFIG_IP_ROUTE_TOS=y CONFIG_IP_ROUTE_VERBOSE=y CONFIG_IP_ROUTE_LARGE_TABLES=y # CONFIG_IP_PNP is not set CONFIG_NET_IPIP=m CONFIG_NET_IPGRE=m # CONFIG_ARPD is not set # CONFIG_INET_ECN is not set ------------------------------------------------------------------------------- El comando ip Actualmente usamos varios comandos para gestionar el tráfico IP y todo lo que le rodea : interfaces, rutas, tuneles, ... Se decidio unificar todo y proveer un solo comando con una sintaxis coherente y global. El comando se llama 'ip' y tiene la siguiente sintaxis : ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]] ------------------------------------------------------------------------------- OPTIONS OPTIONS son varios opciones que influyen el comportamiento general de la herramienta. Todas las opciones empiezan por el carácter '-' y se pueden abreviar. Unas opciones son : * -s, -stats, -statistics obtener más información * -f, -family especifica que familia de protocolo usar : inet, inet6 o link. * -r, -resolve imprimir nombres DNS en lugar de direcciones de host ------------------------------------------------------------------------------- OBJECT OBJECT es el objecto que queremos manejar o del cual buscamos informaciones. Aqui vienen unos y también pueden ser abreviados : * link,l -- periférico de red * address,a -- direccion (IPv4 o IPv6) de periférico * route,r -- entrada en la tabla de routing * rule,ru -- regla en la base de datos de política (policy database) * maddress,maddr -- direccion multicast * tunnel,t -- tunnel sobre IP ------------------------------------------------------------------------------- COMMAND COMMAND es el comando que se aplica al objeto. Se puede abreviar también : * add,a -- añadir un objeto * del,d -- borrar un objeto * set,s -- ajustar un objeto * show,list,l -- ver un objeto ------------------------------------------------------------------------------- Mensajes de error Possibles errores son : * Wrong syntax of command line -- problema de sintaxis * el núcleo devuelve un error a una petición NETLINK -- En este caso, ip imprime el mensaje de error prefijado por "RTNETLINK answers:" * Cannot open netlink socket: Invalid value -- Netlink no está configurado en el núcleo * Cannot talk to rtnetlink: Connection refused -- RTNETLINK no está configurado en el núcleo * Cannot send dumb request: Connection refused -- RTNETLINK no está configurado en el núcleo * RTNETLINK error: Invalid argument -- CONFIG_IP_MULTIPLES_TABLES no está configurado en el núcleo ------------------------------------------------------------------------------- ip link -- manejar las interfaces ------------------------------------------------------------------------------- ip link set -- cambiar los atributos de la interfaz * dev NAME : especifica de que interfaz se trata * up/down : cambiar el estado de la interfaz * name NAME : cambiar nombre de la interfaz * mtu NUMBER : cambiar MTU de la interfaz * ejemplo ip link set dummy up ------------------------------------------------------------------------------- ip link show -- ver los atributos * dev NAME : mostrar la interfaz espedificada * up : mostrar solo las interfaces 'up' * ejemplos [eric@mrmime:~]$ ip l l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff [eric@mrmime:~]$ La primera linea de cada entrada da un número único a la interfaz, su nombre (que puede ser cambiado), así que varias informaciones sobre el estado de la interfaz. La segunda línea da informaciones sobre el tipo de interfaz de que se trata, la dirección de la interfaz a nivel de la capa 'layer' (en el caso de ethernet, la dirección MAC). La opción -s nos permite ver estadísticas de la interfaz : [eric@mrmime:~]$ ip -s l l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 RX: bytes packets errors dropped overrun mcast 1368991 5872 0 0 0 0 TX: bytes packets errors dropped carrier collsns 1368991 5872 0 0 0 0 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff RX: bytes packets errors dropped overrun mcast 1789685066 1975826 99656 0 0 0 TX: bytes packets errors dropped carrier collsns 1619835989 2304565 69 16 4 1762589 [eric@mrmime:~]$ Los parámetros son similares al antiguo comando 'ifconfig'. ------------------------------------------------------------------------------- ip address -- gestión de las direcciones de interfaz ip addr permite ver las direcciones de interfaz, añadir nuevas direcciones o borrarlas. Es importante notar que a partir de iproute, las interfaces físicas y las direcciones son totalmente disociadas, eso significa que una interfaz puede tener varias direcciones sin necesitad de crear un alias como era el caso antes. * ip addr show : ver direcciones de protócolo mrmime:~# ip a l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff inet 192.168.2.71/24 brd 192.168.2.255 scope global eth0 mrmime:~# * ip addr add : añadir nueva direccion mrmime:~# ip a l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff inet 192.168.2.71/24 brd 192.168.2.255 scope global eth0 mrmime:~# ip a a 10.0.0.1 dev eth0 mrmime:~# ip a l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff inet 192.168.2.71/24 brd 192.168.2.255 scope global eth0 inet 10.0.0.1/32 scope global eth0 mrmime:~# * ip addr del : borrar una direccion mrmime:~# ip a d 10.0.0.1 dev eth0 mrmime:~# ip a l 1: lo: LOOPBACK,UP mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:05:1c:01:b1:33 brd ff:ff:ff:ff:ff:ff inet 192.168.2.71/24 brd 192.168.2.255 scope global eth0 mrmime:~# ------------------------------------------------------------------------------- Tablas de ruting multiples ip nos permite de trabajar con tablas de ruting multiples, una gran novedad en la gestión de tráfico IP. De que se trata ? Hasta ahora habiamos tenido, por cada sistema, una tabla de ruting unica, que define a que interfaz esta conectada una red, donde se encuentra el gateway, etc ... Gracias a iproute, podemos trabajar con varias tablas de ruting a la vez y elegir que tabla usar según las características del paquete IP. Imaginamos por ejemplo que tenemos un ruter con dos interfaces de coneccion a Internet. Una interfaz RDSI mas lenta pero barata y una interfaz ADSL rapida, pero mas cara. La maravilla es que se puede decidir, gracias a iproute, que interfaz sera usada, según los paquetes que hay que mandar. Se puede decidir por ejemplo, que los paquetes SMTP saliran por la interfaz RDSI lenta (no hay prisa). A revés, si tenemos aplicaciones de videoconferencia, queremos que vayan por la interfaz ADSL rápida. En este caso, la política de routing se basa en el puerto de destino de los paquetes IP. Podemos imaginar basar la política de routing sobre la direccion IP de orígen de los paquetes. Sería el caso si queremos dar prioridad a unos servicios de una empresa. Por ejemplo, la dirección de la empresa usara la conexion ADSL rápida, mientras el servicio de mecanografía vera su tráfico dirigido por la interfaz lenta. En realidad, podemos basarnos en muchos parámetros para establecer nuestra política de routing : IP de origen, IP de destino, puerto de origen, puerto de destino, protocolo usado, TOS e interfaz de llegada. Hay que destacar que para hacer comprobación sobre protocolos IP y puertos de transporte, hay que usar el sistema conjuntamente con ipchains que nos provee fwmark, un sistema para marcar paquetes. Lo veremos más adelante. Por defecto, hay 3 tablas de ruting en la base de datos 'routing policy database' : [eric@mrmime:~]$ ip ru l 0: from all lookup local 32766: from all lookup main 32767: from all lookup default [eric@mrmime:~]$ El comando antiguo 'route' nos enseña la tabla 'main'. Las dos otras son nuevas. La tabla 'local' es especial, no puede ser borrada y tiene la prioridad más alta (0). Se usa para direcciones locales y de broadcast. La tabla 'main' es la tabla clásica que nos devuelve el antiguo comando 'route'. Se puede borrar o cambiar. La tabla 'default' esta vacía y se reserva para procesos de post-routing (si las reglas anteriores no coinciden). También se puede borrar. [eric@mrmime:~]$ ip r l table main 195.96.98.253 dev ppp2 proto kernel scope link src 212.64.78.148 212.64.94.1 dev ppp0 proto kernel scope link src 212.64.94.251 192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.71 127.0.0.0/8 dev lo scope link default via 212.64.94.1 dev ppp0 [eric@mrmime:~]$ ------------------------------------------------------------------------------- Ejemplo sencillo de ruting según origen Imaginamos que las 2 interfaces de red que hemos mencionado antes son: 212.64.94.1 para el tráfico de alta velocidad 195.96.98.253 para el tráfico lento Pedro no se ha portado bien esta semana así que le vamos a redireccionar a través de la conexion lenta. Generamos una regla que se llama 'Pedro' : # echo 200 John >> /etc/iproute2/rt_tables # ip rule add from 192.168.2.35 table John # ip rule ls 0: from all lookup local 32765: from 192.168.2.35 lookup John 32766: from all lookup main 32767: from all lookup default Esta regla especifica que todo el tráfico que viene con IP de origen 192.168.2.35 usa la tabla de routing llamada Pedro. Necesitamos entoncés crear la misma tabla Pedro : # ip route add default via 195.96.98.253 dev ppp2 table John # ip route flush cache Así de simple. A continuación vemos los identificadores que se pueden usar en la base de datos : from : determina la origen del paquete to : determina el destino del paquete iis : determina la interfaz de llegada. tos : determina el valor de TOS fwmark : determina el valor de 'marca' del paquete (puesto por iptables por ej., ver el ejemplo siguiente) Es importante no confundir 'tablas de routing' y 'reglas'. Las reglas apuntan a tablas de routing, varias reglas pueden apuntar a la misma tabla de routing. De la misma manera, una tabla de routing puede no tener ninguna regla apuntando a ella sin dejar de existir. ------------------------------------------------------------------------------- Ejemplo más complejo con marcar de paquetes En el ejemplo previo, hemos dirigido nuestros paquetes segun su direccion IP de origen. Si queremos, por otra parte, basar nuestra política sobre el tipo de tráfico (correo, web, video), usaremos la herramienta de marca de paquetes proveeda por netfilter (iptables). Siguiendo con los datos previos, vemos ahora como desviar nuestro tráfico de web saliente hacia la interfaz rapida (212.64.94.1, ver arriba). Marcamos los paquetes que tienen como destino el puerto 25 (SMTP) con un número '1' : # iptables -A PREROUTING -i eth0 -t mangle -p tcp --dport 25 \ -j MARK --set-mark 1 Ahora dirigimos los paquetes marcados '1' a una tabla de ruting especifica, en este caso, la llamamos web.out : # echo 201 mail.out >> /etc/iproute2/rt_tables # ip rule add fwmark 1 table web.out # ip rule ls 0: from all lookup local 32764: from all fwmark 1 lookup web.out 32765: from 192.168.2.35 lookup John 32766: from all lookup main 32767: from all lookup default Solo nos hace falta ahora crear la tabla de routing : #ip route add default via 195.96.98.253 dev ppp0 table web.out Ya lo tenemos todo. La regla de marca que usamos es muy simple, solo se basa en el puerto de destino del paquete. Obviamente, con la flejibilidad que nos ofrece iptables, podemos complejificarlo mucho, añadiendo excepciones, etc. ------------------------------------------------------------------------------- ip tunnel -- Tuneles Sin necesitades de usar herramientas externas y complicadas como PPTP, iptunnel, etc. Podemos crear tuneles cifrados o no, con el mismo comando ip. 3 tipos de tuneles son disponibles : IPIP (IP sobre IP), sit y GRE (el protócolo desarollado por Cisco). Se trata realmente de encapsular paquetes IP en otros paquetes IPv4 y mandarlos a través de una infrastructura IP. ------------------------------------------------------------------------------- ip tun [add|change|delete] -- 'abrir/cambiar/cerrar' un tunel Los argumentos posibles son: * name NOMBRE -- selecciona el nombre del tunel * mode MODO -- hay 3 modos disponibles: ipip, sit y gre El modo ipip corresponde a un simple tunel IP sobre IP. Se encapsulan los paquetes sin más. El modo sit se usa para tuneles IPv6. El modo gre corresponde a los tuneles GRE especificados por la compañia Cisco, son tuneles IP sobre IP cifrados. * remote DIRECCION -- direccion de 'salida' del tunel * local DIRECCION -- direccion local de 'entrada' del tunel * dev PERIFERICO-- nombre del periférico a través del que se envian los paquetes Por ejemplo, para crear un tunel IPv6 sobre IPv4, ip tunl add MiTunel mode sit remote 192.31.7.104\ local 192.203.80.142 ------------------------------------------------------------------------------- ip tun show -- ver los tuneles mrmime:~# ip tu ls tunl0: ip/ip remote any local any ttl inherit nopmtudisc mitun: ip/ip remote 192.168.2.5 local 192.168.2.71 ttl inherit mrmime:~# o con estadisticas mrmime:~# ip -s tu ls mitun mitun: ip/ip remote 192.168.2.5 local 192.168.2.71 ttl inherit RX: Packets Bytes Errors CsumErrs OutOfSeq Mcasts 0 0 0 0 0 0 TX: Packets Bytes Errors DeadLoop NoRoute NoBufs 0 0 0 0 0 0 mrmime:~# ------------------------------------------------------------------------------- Reserva de ancho de banda Eso es realmente lo mejor de Linux. Va mucho más alla de lo que existe en materia de sistema de gestión de ancho de banda de tipo proprietario que cuestan cientos de miles de pesetas. Para el controlo de tráfico, se usan dos conceptos : filtros y colas. Los filtros ponen los paquetes en las colas y las colas deciden que paquetes mandar antes de otros (a tirarlos). Luego veremos más en detalles lo que tiene que ver colas con ancho de banda. Hay muchos tipos de filtros distinctos, los más comunes son 'fwmark' y 'u32'. El primero nos deja usar el código de netfilter (iptables) para seleccionar tráfico, el segundo nos permite seleccionar el tráfico basandose en _cualquiera_ cabezera de paquete. Vamos a explorar todo esto con un ejemplo fictivo de ISP que necesita hacer reparto de ancho de banda entre sus clientes. ------------------------------------------------------------------------------- Que son colas ? Las colas determinen el orden en que se manda los paquetes. Que tiene que ver con nuestro ancho de banda ? Imaginamos una caja de supermercado donde la gente hace cola para pagar sus compras. La ultima persona llegada se pone al final de la cola y espera su turno, es una cola FIFO (First In, First Out). Ahora si dejamos ciertas personas siempre ponerse en medio de la cola, seran atendidas más rápidamente y podran comprar más rapidamente. Internet esta basado principalmente en TCP/IP y TCP/IP no sabe nada de ancho de banda. Lo que hace una máquina es mandar datos cada vez más rápido hasta que se detecte que paquetes se estan perdiendo, luego ralentiza. Es el equivalente de no leer mitad de sus emails, esperando que gente deje de mandarte correo. La diferencia es que con Internet sí funciona ------------------------------------------------------------------------------- Nuestro ISP Nuestro router Linux tiene 2 interfaces eth0 y eth1. eth0 esta conectado a nuestra red de clientes, eth1 es nuestra conexion al backbone Internet. Solo podemos limitar lo que mandamos así que necesitamos 2 tablas de reglas. Modificaremos la cola de eth0 para decidir la velocidad a la que se mandan los datos a nuestros clientes, su ancho de banda o su 'download speed'. Luego modificamos eth1 para especificar la velocidad de mando de datos a Internet, la 'upload speed' de nuestros clientes. ------------------------------------------------------------------------------- Usando CBQ Con CBQ podemos usar clases de usuarios (así como subclases). Vamos a crear 2 : ISP para nuestros clientes y Office para la red corporativa. Disponemos de 10 Mbit de ancho de banda, vamos a dar 8 a ISP y 2 a Office. La herramienta que usamos es 'tc' # tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 10 Definimos la regla de cola (queueing discipline) para eth0. Con 'root' indicamos que es la disciplina raíz. El 'handle' es el identificador de la regla. Luego indicamos al núcleo que tiene 10Mbit disponibles y que el tamaño medio de los paquetes es mas o menos 1000 octets. # tc class add dev eth0 parent 10:0 classid 10:1 cbq bandwidth 10Mbit \ 10Mbit allot 1514 weight 1Mbit prio 8 maxburst 20 avpkt 1000 Ahora generamos nuestra clase raíz de la que las demas cuelgan. 'parent' indica la clase 'madre' de esta a que damos el el identificador '10:1'. Se especifica también el MTU (1514). # tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mb \ 8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \ bounded Se genera nuestra clase ISP : le dedicamos 8 Mbit y indicamos que no se puede superar, con 'bounded', si no, iria chupando ancho de banda de otra clases. # tc class add dev eth0 parent 10:1 classid 10:200 cbq bandwidth 10Mb \ 2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \ bounded Aqui está la clase Office Muy bien. Hasta ahora, hemos indocado al núcleo qué son nuestras clases pero no como gestionar las colas. Aqui viene : # tc qdisc add dev eth0 parent 10:100 sfq quantum 1514b perturb 15 # tc qdisc add dev eth0 parent 10:200 sfq quantum 1514b perturb 15 En este caso, usamos la regla 'Stochastic Fairness Queueing' (sfq), que no es totalmente imparcial, pero funciona bien para ancho de banda bastante elevado, sin cargar demasiado el núcleo. Hay otras reglas como 'Token Bucket Filter' pero consumen mas ciclos de CPU. Nos queda hacer una cosa, indicar al núcleo que paquetes pertenecen a que clase : # tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip \ 150.151.23.24 flowid 10:200 # tc filter add dev eth0 parent 10:0 protocol ip prio 25 u32 match ip \ 150.151.0.0/16 flowid 10:10 Aqui consideramos que Office solo tiene 1 direccion IP para todo el tráfico y que las demas son de ISP esta parte servía de dividir el tráfico 'downstream'. Hay que hacer lo mismo con el tráfico saliente : # tc qdisc add dev eth1 root handle 20: cbq bandwidth 10Mbit avpkt 10 # tc class add dev eth1 parent 20:0 classid 20:1 cbq bandwidth 10Mbit \ 10Mbit allot 1514 weight 1Mbit prio 8 maxburst 20 avpkt 1000 # tc class add dev eth1 parent 20:1 classid 20:100 cbq bandwidth 10Mb \ 8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \ bounded # tc class add dev eth1 parent 20:1 classid 20:200 cbq bandwidth 10Mb \ 2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \ bounded # tc qdisc add dev eth1 parent 20:100 sfq quantum 1514b perturb 15 # tc qdisc add dev eth1 parent 20:200 sfq quantum 1514b perturb 15 # tc filter add dev eth1 parent 20:0 protocol ip prio 100 u32 match ip \ 150.151.23.24 flowid 20:200 # tc filter add dev eth1 parent 20:0 protocol ip prio 25 u32 match ip \ 150.151.0.0/16 flowid 20:100 Pues muy bien, tenemos nuestro sistema de ancho de banda funcionando ! ------------------------------------------------------------------------------- Bibliografía S. Alexey N. Kuznetsov, IP Command Reference: http://snafu.freedom.org/linux2.2 /docs/ip-cref/ip-cref.html , Descripcion de la herramienta ip del paquete iproute2 . Bert Hubert et al., Linux 2.4 Advanced Routing HOWTO: http://www.linuxdoc.org/ HOWTO/Adv-Routing-HOWTO.html , Guía COMO de routing avanzado .