Being broadly bored, I started musing about new ways to hassle with networks. One of my dirty phantasies was writing network packets from console. Today, a c compiler came across and we had very much fun fulfilling this nasty fantasy.

What's this about?

If you want to work with network packets on userspace level, you can either read them with bpf and write them with raw sockets. The advantage is that you can bind on any existing interface. The bad thing is that you cannot really adress the program in terms of an IP address. Or you can use TUN (Network layer) or TAP (Datalink layer) devices and attach some software to it. When using a TUN device, the attached program can be adressed via an IP address assigned to the TUN device. Not really surprising -- the TUN abbreviation originates from "tunnel device", the most important use case.
So, what I want to do is have a program that virtualizes a host and writes received packets to its console and vice versa sends entered numbers as a packet. Use two of them and read the numbers on the telephone -- and you can tunnel IP over a telephone line in an very odd way.

The code

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/select.h>

#define atoih(nptr) (int)strtol(nptr, (char **)NULL, 16)

int
main(int argc, char** argv)
{
	int tfd = open("/dev/tun0", O_RDWR);

	int sfd = fileno(stdin);	
	unsigned char buf[6410];

	fd_set set;
	int max = (tfd > sfd) ? tfd+1 : sfd+1;

	while(1) {
		FD_ZERO(&set);
		FD_SET(sfd, &set);
		FD_SET(tfd, &set);

		select(max, &set, NULL, NULL, NULL);

		if (FD_ISSET(tfd, &set)) {
			size_t l = read(tfd, buf, 1600);
			size_t i;
			for (i = 0; i < l; i++)
				printf("%.2x ", buf[i]);
			putchar('\n');
		}
		if (FD_ISSET(sfd, &set)) {
			size_t l = read(sfd, buf, 6404); 

			buf[l] = 0;

			char* ptr1 = buf;
			char* ptr2 = buf+1;

			char* writepos = buf;
			size_t count = 0;
			int breakcond = 0;
			while(1) {
				if (breakcond)
					break;

				// trivial string parser
				while((*ptr2 != ' ') && (*ptr2 != 0)) {
					ptr2++;
				}
				if (*ptr2 == 0)
					breakcond = 1;
				*ptr2 = 0;
				*writepos = atoih(ptr1);
				writepos++;
				ptr2++;
				ptr1 = ptr2;
				count++;
			}
			write(tfd, buf, count);
		}
	}
}

The code is provided as it is, anyone may do anything to and with it -- I do not provide any guarantee for this piece of code. In short: It may work, but don't sue me if you embed it and it does not work as expected or described here.

Walking the code

The fist lines are includes and therefore quite boring. Then there is a atoi for hex as macro definition. The program starts with getting a file descriptor for the input stream and opening the tun device. Note how the device is not a parameter nor that the returned fd is checked for anything. One should not be allowed to write programs like this. Anyhow, after declaring a buffer large enough to contain either a data packet or its integer-string representation, the fd set and the helping maxfd-variable for the use of select() is prepared. We then enter an endless loop which starts with preparation and use of select() to watch both the console and the tun device for incoming data. If new data is present on the tun device, it is read to the buffer and subsequential written to the console in hex representation. Once the packet is written to console, a newline is added -- for convinience and also to avoid buffering effects. In case new data is present on the console, it is read into a buffer and then parsed (with a pointer-shifting construction you just should not be allowed to compile sucessfully) to their number representation and are written to the buffer. Finally the buffer is used in write() and the packet is sent to whoever cares.

Running the program

To run the program, I took the following steps:
First, create a tunnel device:

shell# ifconfig tun0 create
Then, start the program. It will attach and ifconfig will show:
shell# ifconfig tun0
tun0: flags=8010<POINTOPOINT,MULTICAST> metric 0 mtu 1500
        Opened by PID 2889
We then need to set the tunnel device's addresses and set the device to UP status.
shell# ifconfig tun0 inet 2.2.2.2 1.1.1.1 up
shell# ifconfig tun0
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1500
        inet 2.2.2.2 --> 1.1.1.1 netmask 0xff000000
        Opened by PID 2889
Finally, we can send some data using netcat!
shell# echo "blubb" | nc -w 1 -u 2.2.2.2 100
You then should see the packet turning up as a series of numbers. You may want to watch the whole process with tcpdump.

Cave

During debugging, you may want to deactivate avahi and all other dirt that pretends to be helpful by sending packets into literally every network connection that has a link without asking for permission. And yes, there may exist a config file which I never touched and which I will not touch. I just kill the daemon, period.

Stichworte:


Impressum