twIP is a really, really tiny IP stack, written in 139 bytes of C code - small enough to fit in a Twitter message. Ok, so it is very far away from a real IP stack, but it can do the first task of an IP stack: respond to pings. The entire source code for version 1.1 can be found this tweet (139 characters long - version 1.0 in this tweet, 128 characters long).

FAQ:

  • Q: why? A: for fun.
  • Q: is twIP really an IP stack? Does twIP require an underlying socket layer? A: twIP operates on raw IP packet data and there are no sockets involved.

twIP (pronounced "twip" and short for tweet IP - the name was suggested by akiba) runs as a process under a *nix-like system, but could also be run on a standalone (OS-less) device, as long as there is a device driver for some networking hardware. The underlying system must provide a way to receive and send raw data packets. Operating systems such as FreeBSD (and others) provide a very nice mechanism called the tun/tap device, where a special file (/dev/tun0) is connected to a network interface (tun0) so that packets sent over the interface can be read from the file, and packets written to the file appear as packets on the network interface. By setting up the IP address of the network interface, routing IP packets to the user program is a breeze.

I have tested twIP under FreeBSD 6.1 (ancient, yes I know!). It should also work under Linux, but Linux requires some special ioctl() calls for the tun device to behave nicely, and the tweet-size program won't work out of the box.

To run twIP, do the following:

  • Download the source code, and place it in a file called twip.c
  • Compile the code: make twip (in the same directory as twip.c)
  • Start the program, as root (needed to make tun communication work) and with input and output redirected to the tun device: su then ./twip < /dev/tun0 > /dev/tun0
  • Open another terminal, and setup the IP addresses as root: su then ifconfig tun0 172.16.1.2 172.16.1.3
  • You should now be able to ping twIP! ping 172.16.1.3

That's it. Nothing more, nothing less. The twIP stack responds to the pings.

To make this code worthwhile, however, I'm going to walk through the code for others who might be interested in low-level network programming.

First, this is the code formatted to fit into the twitter message:

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);}}}

And here is the expanded version, with comments (download as a C file - previous version here):

/* The whole point of the twIP stack is to respond to pings. This is
   done by reading one IP packet at a time, hoping that it is an IP
   ping packet (no check is made!), changing the packet type to a ping
   reply packet, updating the ICMP checksum, swapping the IP source
   and destination addresses, and sending the packet back. That's
   it.
 */
 
/* This is the packet buffer. I chose the size of the array so that
   the maximum packet size that twIP would support would be the same
   as the largest twitter message: 140 bytes. This array could have
   been larger, and still make the code fit into a tweet: 576 bytes
   (the smallest maximum packet size a real IPv4 stack must support),
   or 1500 bytes (the maximum Ethernet size), for example.
   The array is declared as a 16-bit (short) array to allow shorter
   indexing of 16-bit data from the packet headers. This is needed
   because the ICMP checksum, which is a 16 bit quantity, needs to be
   updated. It is shorter to use a 16-bit pointer to reach into the
   packet to update the checksum.
*/
short s[70];
 
/* The 'l' variable is a pointer to an int. The 'int' keyword can be
   omitted because C implitictly treats the variable as an int then
   (this is an old legacy of C). An int is 32 bits on many platforms,
   and this variable is used when swapping the two 32-bit IPv4
   addresses in the packet header. The initialization of 'l' will
   produce a compiler warning, but the code works nevertheless.
*/
*l = s;
 
/* The 't' variable is a temporary int variable which is used when
   swapping IP addresses. The variable is implicitly defined as an int
   by the C compiler.
 */
t;
 
/* This is the main function. To save space, its type and arguments
   are omitted. This is valid C (but looks weird nowadays).
 */
main() {
 
  /* The program runs in an infinite loop. This is what the for(;;)
     statement does.
   */
  for(;;) {
 
    /* Here we read the IP packets. We read them from file descriptor
       0, which should correspond to STDIN_FILENO. This works only if
       we have redirected the input from the tun0 device file. We read
       the data into the packet buffer (variable 's') and at most 140
       bytes.
    */
    read(0, s, 140);
 
    /* At this point, we should do a lot of sanity checking of the
       incoming packet. We *should* check its length (to see that it
       is at least longer than a packet header), we should check that
       it is a valid IPv4 packet header, we should check that it is an
       ICMP packet, and we should check that the ICMP type is ECHO,
       which is the type used for ping packets.
 
       In this code, we do only a limited form of sanity checking. We
       check that the packet is indeed an ICMP packet by inspecting
       the correct field in the IP header (we must mask out the TTL
       field in the IP header to reach it) and by looking at the type
       code field in the ICMP header. The ICMP type must be ICMP ECHO.
 
       The IP protocol field is found at byte 9 in the IP header, and
       should be 1. We get to this field by indexing four 16-bit words
       into the 's' array, masking out the high 8 bits (65280 =
       0xff00) and seing if these high bits are 1 (256 = 1 << 8). The
       ICMP type field is found 20 bytes into the IP/ICMP header, and
       should be 8. Since we use 16-bit indexing, we find it at s[10].
    */
    if((s[4] & 65280) == 256 &
       s[10] == 8) {
      /* We now set the ICMP type field to be an ECHO_REPLY packet
	 type. This is what the ping command expects in return after
	 sending an ICMP ECHO packet.
      */
      s[10] = 0;
      
      /* Now that we've altered the packet, the ICMP checksum is no
	 longer valid and we must update it. Fortunately, we did a very
	 small change to the packet and we can simply update the
	 checksum accordingly. The code below works only on
	 little-endian machines.
	 
	 With the code below, there is still a small chance that the
	 ICMP checksum update fails, however. If the checksum is
	 0xfff7 or larger, we would really need to add 9 instead of
	 8. But the program wouldn't fit in a tweet if we'd check for
	 this condition. And with a risk of only 9 out of 65536, we
	 are willing to chance it this time.
	 The ICMP checksum is found 22 bytes into the packet, but
	 since we are using the 's' pointer, which is a 16-bit
	 pointer, we must use 22/2 = 11 as the array index.
      */
      s[11] += 8;
 
      /* Next, we swap the IP destination and source addresses before
	 sending the packet. Swapping the IP addresses will return the
	 packet back to the sender of the ping packet. Since we only
	 swap bytes in the IP header, we do not need to update the IP
	 header checksum.
 
	 We make use of the temporary variable 't' and the 32-bit
	 pointer 'l'. The IP addresses are found 12 and 16 bytes into
	 the packet, but since the 'l' pointer is a 32-bit pointer we
	 need to use the indicies 3 and 4.
      */
      t = l[4];
      l[4] = l[3];
      l[3] = t;
      
      /* We are now done with manipulating the packet. If the packet was
	 what we hoped it was - an incoming ping packet - we should now
	 have a suitable ping reply in the packet buffer, and we can
	 send the packet out again. We write the packet from the packet
	 buffer (the 's' variable) and to file descriptor 1. This file
	 descriptor is the STDOUT_FILENO, which we have redirected to
	 point to the /dev/tun0 file, and the corresponding tun
	 device. We write 140 bytes - the maximum packet size that we
	 support. The incoming packet may have been smaller than 140
	 bytes, but most IP stacks handles packets that are longer than
	 what their IP headers say. (This commonly is referred to as the
	 Jon Postel quote "Be liberal in what you accept, and
	 conservative in what you send.")
	 
	 Once the packet has been written to the file, the packet is
	 sent to the network interface, and our ping has been
	 successfully replied to.
      */
      write(1, s, 140);
    }
  }
}

So what's the point of this? This was done only for the fun of it. Fitting a sensible program into 140 characters of source code is quite a challenge. It started with Razvan Musaloiu-E. posting a tweet-size program that would crash MacOSX. Inspired by this, I began writing a few small programs (Hello world, a Fibbonachi function, a factorial function, and a small linked list library) and finally ended up writing the twIP stack. Just for fun.

See also