Programando una pila TCP/IP 1: Ethernet y ARP (2016)
(saminiir.com)- Como primer paso de una serie para aprender capas de red construyendo directamente una pila TCP/IP en espacio de usuario en Linux, se implementa desde la gestión de tramas Ethernet de capa 2 hasta la respuesta ARP
- El tráfico de red de bajo nivel del kernel se recibe mediante un dispositivo TAP, y con el descriptor de archivo devuelto se puede hacer
read/writesobre el búfer Ethernet del dispositivo virtual - La cabecera Ethernet se maneja con
dmac,smac,ethertypeypayload, y si el valor deethertypees 1536 o mayor indica el tipo de payload, mientras que si es menor indica la longitud del payload - ARP mapea dinámicamente direcciones de protocolo como las direcciones IPv4 a direcciones MAC de 48 bits, y se implementa con un flujo que responde solicitudes mientras actualiza la tabla de traducción
- En la prueba con
arping, el kernel de Linux reconoce la respuesta ARP de la pila personalizada y agrega una entrada10.0.0.4a la caché ARP de la interfaztap0
Punto de partida de una pila TCP/IP en espacio de usuario
- El objetivo es implementar una pila TCP/IP mínima en espacio de usuario en Linux para entender con más profundidad las redes y la programación de sistemas
- TCP se ha vuelto complejo por la acumulación de especificaciones durante más de 30 años, pero sus elementos centrales de implementación pueden acotarse a parseo de cabeceras TCP, máquina de estados, control de congestión y cálculo de timeouts de retransmisión
- Ethernet e IP tienen menor complejidad que TCP, así que la serie empieza la implementación desde la capa 2
Recibir tráfico Ethernet con un dispositivo TAP
- Para interceptar tráfico de red de bajo nivel del kernel de Linux se usa un dispositivo TAP de Linux
- Los dispositivos TUN/TAP se usan con frecuencia cuando aplicaciones de red en espacio de usuario manipulan tráfico L3/L2 respectivamente
- El tunneling consiste en encapsular un paquete dentro del payload de otro paquete
- Programas como OpenVPN también usan dispositivos TUN/TAP
- Como la pila de red se construye desde la capa 2, se necesita un dispositivo TAP y no TUN
- El dispositivo TAP se crea abriendo
/dev/net/tapy usandoioctl(fd, TUNSETIFF, ...)IFF_TAPselecciona un dispositivo TAPIFF_NO_PIevita que se agregue información de paquete innecesaria delante de la trama Ethernet
- Después de crearlo, con el descriptor de archivo
fddevuelto se lee y escribe el búfer Ethernet del dispositivo virtual
Formato de trama Ethernet
- Ethernet es la tecnología base para conectar computadoras en una LAN, y el primer estándar Ethernet fue publicado en 1980 por Digital Equipment Corporation, Intel y Xerox
- La primera versión usaba una velocidad de alrededor de 10Mb/s y comunicación half-duplex, por lo que necesitaba un protocolo MAC para regular el flujo de datos
- En interfaces Ethernet half-duplex, CSMA/CD era necesario como método MAC
- 100BASE-T usa cableado twisted-pair para permitir comunicación full-duplex y mayor rendimiento
- Con la adopción generalizada de switches Ethernet, la necesidad de CSMA/CD en gran medida disminuyó
- El estándar Ethernet es administrado por el grupo de trabajo IEEE 802.3
Cabecera Ethernet usada en la implementación
- La implementación incluye
if_ether.hde Linux para usar el mapeo entre ethertype y valores hexadecimales - La cabecera Ethernet se representa en una estructura de C con los siguientes campos
dmac: dirección MAC de destinosmac: dirección MAC de origenethertype: longitud o tipo del payloadpayload: puntero al payload que contiene un paquete ARP o IPv4
ethertypees un campo de 2 octetos y su significado cambia según el valor- Si el valor es 1536 o mayor, indica un tipo de payload como IPv4 o ARP
- Si el valor es menor, indica la longitud del payload
- Las tramas Ethernet pueden llevar etiquetas que representan VLAN o QoS, pero esta implementación excluye las etiquetas de trama
- Si la longitud del payload es menor que el mínimo requerido de 48 bytes sin etiquetas, se agregan bytes de padding al final
- Al final del Ethernet Frame Format hay un campo Frame Check Sequence que verifica integridad con CRC, pero esta implementación no lo procesa
Método de parseo de tramas Ethernet
- El atributo
packeden la declaración de la estructura evita que el compilador GNU C optimice el layout de memoria de la estructura con bytes de padding para alineación de datos - El parseo en la implementación se hace mediante type casting del búfer a la estructura de protocolo correspondiente
- Ejemplo:
struct eth_hdr *hdr = (struct eth_hdr *) buf;
- Ejemplo:
- Un método más portable es serializar manualmente los datos del protocolo
- En ese caso, el compilador podría agregar bytes de padding para cumplir los requisitos de alineación de datos específicos del procesador
- El procesamiento de tramas Ethernet recibidas sigue un flujo simple
- Se lee un búfer desde el dispositivo TAP
- Se inicializa la cabecera Ethernet con
init_eth_hdr(buf) handle_frame(&netdev, hdr)decide la siguiente acción según el valor deethertype
Estructura y función de los paquetes ARP
- ARP (Address Resolution Protocol) mapea dinámicamente direcciones de protocolo como direcciones IPv4 a direcciones MAC, que son direcciones Ethernet de 48 bits
- ARP no está limitado solo a IPv4 y puede usarse junto con varios protocolos L3
- Como ejemplo, CHAOS define una dirección de protocolo de 16 bits
- En una comunicación LAN normal, aunque se conozca la dirección IP de un servicio, para la transmisión real se necesita una dirección MAC
- ARP envía una consulta broadcast a la red para que el propietario de esa dirección IP anuncie su dirección de hardware
Cabecera ARP y payload para IPv4
- La cabecera ARP está compuesta por los siguientes campos
hwtype: campo de 2 octetos que indica el tipo de capa de enlace; en Ethernet su valor es0x0001protype: campo de 2 octetos que indica el tipo de protocolo; en IPv4 su valor es0x0800hwsize: campo de 1 octeto que indica el tamaño de la dirección de hardware; una dirección MAC ocupa 6 bytesprosize: campo de 1 octeto que indica el tamaño de la dirección de protocolo; una dirección IPv4 ocupa 4 bytesopcode: campo de 2 octetos que indica el tipo de mensaje ARP
- Los valores de
opcodese dividen en cuatro- ARP request:
1 - ARP reply:
2 - RARP request:
3 - RARP reply:
4
- ARP request:
- Los datos ARP para IPv4 se manejan con la estructura
arp_ipv4smac: dirección MAC del remitentesip: dirección IP del remitentedmac: dirección MAC del receptordip: dirección IP del receptor
Algoritmo de resolución de direcciones y caché
- El algoritmo de resolución de direcciones de RFC 826 verifica el tipo de hardware y el tipo de protocolo, luego actualiza la tabla de traducción y, si la dirección objetivo es la propia, genera una respuesta
- La translation table almacena resultados ARP para que el host pueda consultar en caché entradas que ya posee
- Esta caché reduce que la red se llene innecesariamente con solicitudes ARP duplicadas
- El código de implementación está en
arp.c
Prueba de respuesta ARP y siguiente paso
- La prueba final de la implementación ARP consiste en verificar si responde correctamente a las solicitudes
- Al ejecutar
arping -I tap0 10.0.0.4, regresa una respuesta unicast desde10.0.0.4con la dirección MAC00:0C:29:6D:50:25 - Después, en la salida de
arp, aparece la entrada10.0.0.4 ether 00:0c:29:6d:50:25 tap0en la caché ARP del kernel de Linux - Incluso solo con el procesamiento mínimo de tramas Ethernet y la implementación ARP, se puede comprobar que un dispositivo Ethernet personalizado llena la caché ARP del host Linux
- El código fuente del proyecto está en GitHub, y el siguiente paso es implementar ping como ICMP echo/reply y el parseo de paquetes IPv4
1 comentarios
Opiniones en Hacker News
Hace unos años hice una pila de red en espacio de usuario en C, y la hice funcionar hasta cierto punto procesando paquetes crudos con una interfaz TUN.
Ahora incluye una shell sencilla para configurar direcciones IP, rutas, etc., y los paquetes de red se guardan en una estructura híbrida que parece una mezcla de mbuf y sk_buf.
Sin embargo, después de terminar la implementación de UDP no tuve tiempo ni ganas de implementar TCP, y el código está aquí: https://github.com/cakturk/unet
Por supuesto, era probablemente una de las cosas más lentas y frágiles de la historia, pero funcionaba de verdad y fue bastante divertido. Ojalá ese código todavía esté guardado en algún lado.
El “puerto POSIX” de lwip también obtiene bytes Ethernet crudos desde un dispositivo TUN/TAP de la misma manera.
https://github.com/lwip-tcpip/lwip/blob/master/contrib/ports...
Si se compila un kernel Linux mínimo sin pila TCP/IP, ocupa 400 KB; al incluir la pila TCP/IP, pasa a 800 KB.
En un proyecto donde solo había que enviar la temperatura, mandé los valores dentro de mensajes UDP hechos a mano desde un pequeño programa en C en espacio de usuario, y así pude reducir mucho el espacio y la complejidad.
Si se desactiva ARP, se puede configurar la misma IP en varios servidores de la misma red.
Si un servidor que actúa como frontend de enrutamiento puede entregar paquetes a la interfaz de red de los servidores backend según la dirección MAC, ese backend se reconocerá como destino y podrá responder directamente al cliente intercambiando las IP de origen/destino. En ese caso no vuelve a pasar por el frontend de enrutamiento.
O bien, sin desactivar ARP, se puede agregar la dirección IP común como alias en la interfaz loopback para lograr el mismo efecto, permitiendo que el backend se reconozca como destino y evitando conflictos de ARP. Era un truco usado por el balanceador de carga por software IBM WebSphere en los años 90 y 2000.
La ventaja frente al balanceo de carga L3 más común es que no hace falta reescribir el encabezado del paquete IP.
Por eso, si se usa este método, conviene crear una VLAN dedicada.
Hice algo parecido en Python: https://github.com/georgek/notebooks/blob/master/internet.ip...
Probablemente la calidad del código sea peor y, siendo sincero, el algoritmo de resolución de direcciones me lo inventé. Llegué a hacer ping a hosts de Internet con ICMP.
Me gusta que esté completamente contenido en un notebook corto. El artículo original omite en el texto muchos detalles que están en el código fuente más grande al que hace referencia.
Lo hice sin ver este artículo, solo consultando Wikipedia. Pero a partir de TCP la complejidad subió mucho y perdí algo de interés. Como la parte 3 trata ese tema, quizá algún día la lea y lo termine. Si te interesa el networking, creo que es una tarea viable y gratificante para programadores de cualquier nivel.
Hace unos años trabajé en instrumentación de plantas nucleares. El desarrollo del lado cliente se hacía en estaciones de trabajo Sun, y de hecho me contrataron gracias a mi experiencia con TCP/IP, que obtuve en el curso de “sistemas operativos” de CMU.
En cambio, las computadoras de la planta eran minicomputadoras sin pila TCP/IP, así que ese equipo tuvo que crear su propia pila.
Aproximadamente al minuto de empezar, el texto dice que “dmac y smac son campos bastante obvios”, pero un lector que no sepa qué son podría abandonar ahí mismo.
Puede pensar: “este artículo es para gente a la que esos campos le resultan obvios. No es para mí, así que mejor dejo de leer”.
Además, si es un artículo sobre crear una pila de red, es seguro asumir que el lector sabe algo de redes.
Publicaciones relacionadas:
Let’s code a TCP/IP stack (2016) - https://news.ycombinator.com/item?id=27654182 - junio de 2021, 49 comentarios
Let’s code a TCP/IP stack, 1: Ethernet & ARP (2016) - https://news.ycombinator.com/item?id=17316487 - junio de 2018, 47 comentarios
Let’s Code a TCP/IP Stack: TCP Retransmission - https://news.ycombinator.com/item?id=14701199 - julio de 2017, 30 comentarios
Let’s code a TCP/IP stack, 1: Ethernet and ARP - https://news.ycombinator.com/item?id=11234229 - marzo de 2016, 49 comentarios
No sé de dónde sacó el autor la dirección IP 10.0.0.4 que usa para la prueba de resolución ARP
¿Dirección de qué se supone que es? ¿Es un dispositivo ficticio accesible desde el dispositivo Ethernet ficticio creado aquí, o es un dispositivo que realmente existe en la red del autor?
Un dispositivo TAP es algo así como un enlace Ethernet emulado por software. Si le envías paquetes, se entregan directamente a un programa en espacio de usuario, y ese programa decide qué dirección IP tendrá y cómo responderá a ARP
Normalmente, el sistema operativo se encarga de este tipo de cosas, y para agregar una dirección IP a una interfaz se necesitan permisos de root. Lo mismo para abrir un dispositivo TAP. En general, las redes funcionan de manera cooperativa, y un actor malicioso con permisos de root en la red puede hacer cosas malas
Según recuerdo, ARP solo funciona en el segmento local. El router completa su propia dirección y reenvía el paquete
También existe rarp, que es una de las formas de preguntarle a la “red” cuál es tu propia dirección IP. No sé si rarp todavía funciona en entornos reales