Projects/Software/TunTapIO/tuntap.c

From Qontrol.nl Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
/**********************************************************************
* 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;
		}
	}
}