Projects/Software/TunTapIO/tuntap.c

From Qontrol.nl Wiki
Jump to: navigation, search
/**********************************************************************
* ttio - tun/tap to stdin/out proxy                                   *
* Written by: Ivo Smits <Ivo@UFO-Net.nl>                              *
*                                                                     *
* Compile using: gcc tuntap.c -o ttio                                 *
*                                                                     *
* Based on PigeonWare (http://www.blug.linux.no/rfc1149/)             *
*                                                                     *
* Many thanks to:                                                     *
*  - http://www.blug.linux.no/rfc1149/ (must-see!)                    *
*  - http://linux.about.com/od/commands/l/blcmdl2_select.htm          *
*                                                                     *
**********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifndef HAVE_NETINET_IN_H
	#include <netinet/in.h>
#endif

#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/if_ether.h>

#include <errno.h>
#include <dirent.h> 

const char* TunTapDev = "/dev/net/tun";

// The main program, this is where all the magic happens
int main(int argc, char** argv) {
	int DumpStats = 1;
	int IncludePLen = 1;

	//Parse command line arguments
	if (argc > 1) {
		if (strcmp(argv[1], "-h") == 0) {
			fprintf(stderr, "ttio - tun/tap to stdio proxy\n");
			fprintf(stderr, "Usage: %s INTF [-tap|-tun] [CLEN] [-pi]\n", argv[0]);
			fprintf(stderr, "  INTF:  the name of the network interface\n");
			fprintf(stderr, "  -tap:  tap style ethernet tunnel (default)\n");
			fprintf(stderr, "  -tun:  point-to-point IP tunnel\n");
			fprintf(stderr, "  CLEN:  capture size (should be the same as the mtu, default: %d)\n", ETH_FRAME_LEN);
			fprintf(stderr, "  -pi:   include packet information\n");
			fprintf(stderr, "Note that the arguments should be in exactly THIS order.\n");
			fprintf(stderr, "Report bugs to <Ivo@UFO-Net.nl>\n");
			exit(0);
		}
	} else {
		//Application won't run without arguments
		fprintf(stderr, "Try: %s -h\n", argv[0]);
		exit(1);
	}

	struct ifreq ifr;
	//Clear the ifreq structure
	memset(&ifr, 0, sizeof(ifr));

	char dev[IFNAMSIZ] = "tap1";	//Interface name
	//Copy the interface name from the commandline
	strcpy(dev, argv[1]);
	strcpy(ifr.ifr_name, dev);

	//Initial capture length
	int CaptureLen = ETH_FRAME_LEN;

	{
		//Default to TAP interface, change only if --tun option is detected
		short ifrflags = IFF_TAP;
		if (argc > 2 && strcmp(argv[2], "-tun") == 0) {
			ifrflags = IFF_TUN;
			CaptureLen = ETH_DATA_LEN;
		}
		//Different capture length?
		if (argc > 3) CaptureLen = atoi(argv[3]);
		if (CaptureLen < 1) {
			fprintf(stderr, "Capture length %d invalid!\n", CaptureLen);
			exit(1);
		}
		fprintf(stderr, "Max packet length: %d\n", CaptureLen);
		//Include packet info in output?
		if (!(argc > 4 && strcmp(argv[4], "-pi") == 0)) ifrflags = ifrflags | IFF_NO_PI;
		//Set ifr flags
		ifr.ifr_flags = ifrflags;
	}

	#warning "Make sure that dbuf can hold all the captured data"
	register int Ret; //Return value
	int TunFD; //Tun/tap stream

	//Try to open the tun/tap device file, exit on failure
	fprintf(stderr, "Opening %s\n", TunTapDev);
	if ((TunFD = open(TunTapDev, O_RDWR)) < 0) {
		fprintf(stderr, "Failed to open %s: %d\n", TunTapDev, TunFD);
		exit(2);
	}

	//Request the interface and set its flags
	fprintf(stderr, "Requesting device: %s\n", dev);
	if (ioctl(TunFD, TUNSETIFF, (void *)&ifr) < 0 ) {
		fprintf(stderr, "ioctl for device name failed!\n");
		close(TunFD);
		exit(3);
	}

	//Just to make sure..?
	#warning "Do we really need to read the interface name back from the ifr structure?"
	strcpy(dev, ifr.ifr_name);
	fprintf(stderr, "Tunnel interface: %s\n", dev);

	#warning "Set the interfaces mtu to CaptureLength (-2 if Packet Info flag)"
	//Run the setup script
	{
		char syscmd[256];
		sprintf(syscmd, "./setup.sh %s", dev);
		fprintf(stderr, "Execute: %s\n", syscmd);
		system(syscmd);
	}

	fprintf(stderr, "Proxy ready for action!\n");

	fd_set fdsRead, fdsWrite; //FileDescriptor sets for select()
	unsigned char RBuf[CaptureLen], WBuf[CaptureLen]; //Data buffers
	int RBufLen = 0, WBufLen = 0; //Packet length
	int PLen;
	
	//Start an infinite loop
	while (1) {
		if (WBufLen < 0 || RBufLen < 0) {
			fprintf(stderr, "WBufLen < 0 or RBufLen < 0 :|\n");
			exit(2);
		}

		FD_ZERO(&fdsRead);			//Clear FD set
		if (RBufLen == 0) FD_SET(TunFD, &fdsRead);	//Add tunnel device file
		if (WBufLen == 0) FD_SET(0, &fdsRead);		//Add stdin

		FD_ZERO(&fdsWrite);			//Clear FD set
		if (WBufLen > 0) FD_SET(TunFD, &fdsWrite);	//Add tunnel device file
		if (RBufLen > 0) FD_SET(1, &fdsWrite);		//Add stdout

		if (select(TunFD + 1, &fdsRead, &fdsWrite, NULL, NULL) < 0) {
			if (errno == EAGAIN || errno == EINTR) {
				continue; //Retry select
			} else {
				fprintf(stderr, "An unhandled error occured in select: %d", errno);
				exit(5);
			}
		}

		//Read from tun/tap device file
		if (FD_ISSET(TunFD, &fdsRead) && RBufLen == 0) {
			RBufLen = read(TunFD, RBuf, CaptureLen);
			if (RBufLen == 0) {
				fprintf(stderr, "End of file on %s\n", TunTapDev);
				exit(0);
			} else if (RBufLen < 0) {
				fprintf(stderr, "Some error occured while reading from %s: %d\n", TunTapDev, RBufLen);
				exit(4);
			}
			if (DumpStats != 0) fprintf(stderr, "R: %d\n", RBufLen);
		}
		//Write to tun/tap device file
		if (FD_ISSET(TunFD, &fdsWrite) && WBufLen > 0) {
			write(TunFD, WBuf, WBufLen);
			if (DumpStats != 0) fprintf(stderr, "W: %d\n", WBufLen);
			WBufLen = 0;
		}

		//Read from stdin (FD 0)
		if (FD_ISSET(0, &fdsRead) && WBufLen == 0) {
			if (IncludePLen == 1) {
				WBufLen = read(0, &PLen, 4);
			}
			if (IncludePLen == 0 || PLen > CaptureLen) {
				PLen = CaptureLen;
			}
			if (IncludePLen == 0 || WBufLen > 0) {
				WBufLen = read(0, WBuf, PLen);
			}
			if (WBufLen == 0) {
				fprintf(stderr, "End of file on stdin\n");
				exit(0);
			} else if (WBufLen < 0) {
				fprintf(stderr, "Some error occured while reading stdin: %d\n", WBufLen);
				exit(4);
			}
		}
		//Write to stdout (FD 1)
		if (FD_ISSET(1, &fdsWrite) && RBufLen > 0) {
			if (IncludePLen == 1) write(1, &RBufLen, 4);
			write(1, RBuf, RBufLen);
			RBufLen = 0;
		}
	}
}