INTRODUCCIÓN
Tengo la intención de comenzar una serie de artículos relacionados con la conectividad de sistemas embebidos a redes LAN y, dentro de este propósito, me ha parecido interesante comenzar escrbiendo sobre TwIP. TwIP es el stack IP que menos caracteres de código requiere, de hecho, su código fuente completo cabe en un twit. Evidentemente, como el lector podrá suponer, no se trata de una pila IP profesional lista para incluir y usar en nuestros diseños, sino que tiene una finalidad igualmente noble: divertirse programando.
Hace ya algunos años, cuando me pasé de programar en GW-BASIC a C, me entretenía tratando de hacer el código en el menor número de lineas posible y, dentro de estas lineas, tratando que fuesen del tamaño más pequeño posible. Con C las posibilidades en este campo son muy interesantes. TwIP es una de estas posibilidades que, además, nos servirá como apoyo para exponer el mecanismo de comunicación ICMP. De hecho, la única funcionalidad que ofrece TwIP es responder a un ping.
TwIP fue escrito por Adam Dunkels, quien se aficionó a este juego de escribir programas en C super-reducidos tras observar cómo Razvan Musaloiu escrbía un twit con código para reventar un Mac OSX 10.5.8 y de este modo nació este stack IP.
EL CÓDIGO
El código de TwIP es el siguiente:
short s[70];*l=s;t;main(){for(;;){read(0,s,140);if((s[4]&65280)==256&s[10]==8){s[10]=0;s[11]+=8;t=l[4];l[4]=l[3];l[3]=t;write(1,s,140);}}}
Si bien el código desglosado y comentado puede verse en [1], a continuación comento con mis propias palabras el mismo, con la finalidad también de introducir el protocolo ICMP:
Explicación del código: variables de programa
short s[70]; *l=s; t;
Como puede verse, se declara un buffer “s” que servirá para las tramas de recepción y de transmisión. Si se ha elegido hacerlo de 16 bits es porque el protocolo ICMP, como se verá más adelante, incluye un checksum de 16 bits y ocupará menos caracteres el código necesario para operar con él. Una de las viejas herencias de C es que cuando no se especifican tipos de datos, éstos se suponen por defecto como int, este es el motivo por el que, para ahorrar caracteres, el puntero al buffer y la variable “t” se han declarado de esta singular manera. Por supuesto, esto producirá un warning al compilar, pero nada impedirá que nuestro twit se ejecute correctamente.
Del mismo modo, se declara main() en lugar del típico void/int main (void) con mótivo de ahorrar caracteres para cumplir el peculiar requisito de que el código pueda caber en un solo twit.
Explicación del código: recepción de datos
Así pues el código continua con la declaración del main, dentro del cual se ejecuta un bucle sin fin:
main()
{ //Inicio del cuerpo del programa
for(;;) { //Inicio del bucle sin fin
Llegados a este punto, necesitaremos recibir la información y almacenarla en el buffer s, lo cual se hace mediante la función read. Es importante destacar en este punto, que se lee sobre el descriptor de fichero 0, para lo cual, deberemos haber redirigido la entrada del puerto IP al descriptor STDIN_FILENO. En caso contrario, aquí habría que indicar en lugar de 0 el descriptor correspondiente a dicha entrada. De este modo, el código continua como sigue:
read(0,s,140);
Explicación del código: comprobando la trama recibida
En este momento, como se verá cuando se resuma en detalle el protocolo ICMP, se debería de realizar una serie de comprobaciones:
- Se debería comprobar la longitud del paquete para verificar que al menos es del tamaño de una cabecera IP
- Se debería comprobar que la cabecera es válida de acuerdo al formato IPv4
- Se debería comprobar qué tipo de paquete es, y si es de tipo ICMP:
- Se debería comprobar qué tipo de ICMP es, por ejemplo, si es de tipo ECHO, se trata de un comando ping que requiere respuesta.
Sin embargo, debido al singular y minimalista requisito de poder introducir todo el código en un twit, no se dispone, al menos en esta versión, de espacio más que para realizar estas últimas comprobaciones, esto es, TwIP comprueba únicamente si es un paquete ICMP y, en caso de que así sea, comprueba si es un mensaje de tipo ECHO (ping), en cuyo caso envía la respuesta.
Veamos primero el código de comprobación:
if( ( s[4] & 65280 ) == 256 \ //Si el byte más significativo de s[4] vale 0xFF (protocolo ICMP) & s[10] == 8 ) { //Y además s[10] vale 8 (ECHO)
LA CABECERA IPv4
Dentro de la cabecera IPv4, el protocolo se indica en el byte 9 de dicha cabecera, por tanto, tratándose de un array de elementos de 16 bits, habrá que ir a realizar la comprobación en el elemento 4, y dentro de él, encontraremos el campo protocolo en el byte más significativo, salvo que trabajemos con un protocolo big-endian, en cuyo caso el protocolo se encontrará en el byte menos significativo y habría que realizar el and con 255 en lugar de con 65280. Aquí se ha supuesto que se trabaja en formato little-endian.
Esta comprobación puede verse claramente si se observa la cabecera IPv4:
Como puede verse en la imagen anterior, la información, en función del protocolo, comenzará a enviarse a partir del byte 20, en este caso, al tratarse del protocolo ICMP, en el byte 20 se tendrá una estructura de datos de acuerdo a dicho protocolo, y en él, el primer byte es es el que indica el tipo de operación, y ECHO es la operación con identificador 8. De nuevo, al tratarse de un array de elementos de 16 bits, se compara el elemento 10, y suponiendo una máquina little-endian, se espera que el dato esté en el byte menos significativo. Esto se ilustra gráficamente en la siguiente figura, donde se muestra la estructura de datos del protocolo ICMP.
Una vez explicadas las comprobaciones que realiza el código de TwIP, se continúa con la explicación del código.
Explicación del código: preparando el paquete que será enviado
La contestación al ping es una trama prácticamente idéntica, con tres consideraciones:
- IP de origen e IP de destino han de cambiar su posición en la cabecera IPv4
- El tipo de comando ICMP será ECHO_REPLY en lugar de ECHO
- Se deberá recalcular el Checksum de la trama ICMP
El código TwIP hace esto con las siguientes instrucciones:
s[10] = 0; //ECHO_REPLY está definido como 0 s[11] += 8; // Se actualiza el checksum (little-endian) /* Se intercambian los valores de IP origen y destino */ t = l[4]; l[4] = l[3]; l[3] = t;
TIPOS DE MENSAJES EN EL PROTOCOLO ICMP
Ya hemos aprovechado este pequeño código para ilustrar el funcionamiento de las cabeceras IPv4, así como para comentar el funcionamiento de uno de sus protocolos, el ICMP. Dentro de este protocolo hemos podido ver 2 mensajes ECHO y ECHO_REPLY que sirven para enviar un ping y para responderlo, respectivamente.
Llegados a este punto, y aunque nuestra pequeña pila IP no soporta estos comandos, parece interesante comentar el resto de mensajes ICMP que pueden enviarse, de modo que el lector interesado, con muy poco trabajo por su parte, podrá fácilmente desarrollar un stack IP que tenga todas las funcionalidades del protocolo ICMP.
Los comandosICMP, junto a su valor numérico, pueden verse en la siguiente tabla:
| Etiqueta y valor del comando | Descripción del comando |
| #define ECHO_REPLY 0 | Respuesta de eco |
| #define DESTINATION_UNREACHEABLE 3 | Destino inaccesible |
| #define SOURCE_QUENCH 4 | Disminución del tráfico desde el origen |
| #define REDIRECT 5 | Redireccionar – cambio de ruta |
| #define ECHO 8 | Solicitud de eco |
| #define TIME_EXCEEDED 11 | Tiempo excedido para un datagrama |
| #define PARAMETER_PROBLEM 12 | Problema de parámetros |
| #define TIMESTAMP 13 | Solicitud de marca de tiempo |
| #define TIMESTAMP_REPLY 14 | Respuesta de marca de tiempo |
| #define INFORMATION_REQUEST 15 | Solicitud de información – obsoleto- |
| #define INFORMATION_REPLY 16 | Respuesta de información – obsoleto- |
| #define ADDRESS_MASK 17 | Solicitud de máscara de dirección |
| #define ADDRESS_ASK_REPLY 18 | Respuesta de máscara de dirección |
Explicación del código: Realizando el envío
Si el lector ha llegado hasta aquí, no encontrará necesario que continúen explicándole el código, de modo que únicamente incluiré el final del twit:
[ ... ] write(1,s,140);}}}
¿TE GUSTÓ EL ARTÍCULO?
¿Te parece interesante que exista un blog de referencia en español sobre electrónica y sistemas embebidos? Ayuda a que Arte en 8 bits crezca, para ello sólo tienes que pulsar en los botones para compartirlo en redes sociales, Twitter o Google+. De este modo contribuirás a que Arte en 8 bits se posicione mejor, y por tanto podremos crecer e ir ofreciéndote cada vez un mejor contenido y de mayor calidad.
De antemano, muchas gracias.
BIBLIOGRAFÍA
[1] TwIP: A Tweet-sized IP-stack (well, not really)
[2] Protocolos de Comunicación. Networking and Emerging Optimization. Universidad de Málaga.


Hola,
me ha parecido muy interesante. He compilado el código, obteniendo los warnings de los que hablabas, pero ahora no sé como probarlo.
¿Me puedes decir cómo?
Hola,
Me alegro que te guste el artículo. Tienes que conseguir que te lleguen las tramas TCP/IP al descriptor de fichero 0. Eso lo puedes hacer editando read y haciendo que cuando el descriptor valga 0 vayas a la lectura del puerto TCP/IP. Otra opción es averiguar en qué descriptor de fichero te llegan esas tramas y cambiar el 0 tanto en read como en write por el descriptor correspondiente.
¿Lo estás compilando sobre linux?
Saludos
sí, uso linux. lo tengo compilado como
$ cc png.c -o png
png.c:1:13: warning: data definition has no type or storage class
png.c:1:16: warning: initialization from incompatible pointer type
png.c:1:18: warning: data definition has no type or storage class
me refería si hay algún modo de probarlo desde la línea de comando, algo así como lo que indican en http://linuxshellaccount.blogspot.com/2008/04/using-bash-to-access-network-via-file.html
¿en qué consiste la segunda opción que propones? he visto que si creo un interfaz virtual tun0 y obtengo su FD puedo hacerlo http://backreference.org/2010/03/26/tuntap-interface-tutorial/
pero ya se aleja del tamaño “tweet”. Lo dicho, ¿se puede probar en la línea de comandos?
Hola de nuevo,
Lo cierto es que está pensado para ser programado en un microcontrolador, la forma de probarlo sería registrar su interfaz de red con una IP, ejecutar el código y comprobar que cuando se hace ping a esa IP se obtiene respuesta, en caso contrario habría que cambiar el código de formato little endian por el de big endian.
En un PC con linux tendrías que buscar la forma de desactivar la respuesta al ping que ya dará linux por su cuenta, pero manteniendo la interfaz de red registrada y configurada para leerse en el descriptor 0, después bastaría con hacer ping al equipo.
Saludos