// written by yunta83@gmail.com
// license: GNU GPL v2

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>
//#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define BUFSIZE 8000


int mp = 2;
uint64_t delay = 100000;


char *localHost,*remoteHost;
uint16_t inPort,outPort;
int tapFd = -1;
int serverFd = -1;
int senderFd = -1;

volatile int stop = 0;
struct sockaddr_in remote_sck_addr;


uint64_t getTime() 
{
	struct timespec tp;
	clock_gettime(CLOCK_REALTIME,&tp);
      	return tp.tv_sec*1000000+tp.tv_nsec/1000;
	//	return tp.tv_nsec/1000;
	//	return tp.tv_sec;
	//	return  
}


struct packet {
	uint64_t time;
	int size;
	void* data;	
	struct packet* next;
};

struct fifo {
	pthread_mutex_t mutex;
	struct packet* head;
	struct packet* tail;
};

void fifoInit(struct fifo* f)
{
	pthread_mutex_init(&f->mutex,NULL);
	f->head = NULL;
	f->tail = NULL;
}

void fifoPut(struct fifo* f,struct packet* p)
{
	pthread_mutex_lock(&f->mutex);
	p->next = NULL;
	if (f->head) 
		f->head->next = p;
	else 
		f->tail = p;
	f->head = p;
	pthread_mutex_unlock(&f->mutex);
}

struct packet* fifoGet(struct fifo* f)
{
	struct packet* ret;
	pthread_mutex_lock(&f->mutex);
	ret = f->tail;
	if (f->tail) {
		f->tail = f->tail->next;
		if (! f->tail) f->head = NULL;
	};
	pthread_mutex_unlock(&f->mutex);
	return ret;
}

int fifoTime(struct fifo* f)
{
	int ret = -1;
	pthread_mutex_lock(&f->mutex);	
	if (f->tail) ret = f->tail->time;
	pthread_mutex_unlock(&f->mutex);
	return ret;
}

struct fifo** fifos;


pthread_t fromDevThread;
void* fromDev(void* in) 
{
	char buffer[BUFSIZE];
	int odp,i;
	struct packet* p;
	while (stop==0) {
		odp = read(tapFd,buffer,BUFSIZE-1);
		if (odp<0) continue;		
		buffer[odp] = 0;
		//		fprintf(stderr,"TAP_GOT (%i) |%s|\n",odp,"");
		fprintf(stderr,"+");
		for (i=0;i<mp;i++) {
			p = malloc(sizeof(struct packet));
			p->size = odp;
			p->data = malloc(odp);
			memcpy(p->data,buffer,odp);
			p->time = getTime()+delay*i;
			fifoPut(fifos[i],p);
		};
	};
}


pthread_t toUDPThread;
void* toUDP(void* in)
{
	struct packet* p;	
	int odp,i,count;
	uint64_t t,tp;
	while (stop==0) {
		count = 0;
		t = getTime();
		for (i=0;i<mp;i++) {
			tp = fifoTime(fifos[i]);
			if (tp<=t && tp>=0) {
				p = fifoGet(fifos[i]);
				//				fprintf(stderr,"Sending packet (%i)\n",p->size);
				fprintf(stderr,"-");
				sendto(senderFd,p->data,p->size,0,&remote_sck_addr,sizeof remote_sck_addr);
				free(p->data);
				free(p);
				count++;
		        };
		};
		if (count == 0) usleep(1000);
	};	
}


pthread_t fromUDPThread;
void* fromUDP(void* in)
{
	struct sockaddr_in sck_addr;
	char buffer[BUFSIZE];
	int odp;
	while (stop==0) {
		odp = recvfrom(serverFd,buffer,BUFSIZE-1,0,&sck_addr,&odp);
		if (odp<0) break;
		buffer[odp] = 0;
		//		fprintf(stderr,"UDP_GOT (%i) |%s|\n",odp,buffer);
		fprintf(stderr,"r");
		write(tapFd,buffer,odp);
	};	
}

#include <linux/if.h>
#include <linux/if_tun.h>

int tapOpen(char *dev)
{
	struct ifreq ifr;
	int fd, err;

	if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
		exit(2);

	memset(&ifr, 0, sizeof(ifr));
	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
	if( *dev )
		strncpy(ifr.ifr_name, dev, IFNAMSIZ);

	if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
		close(fd);
		exit(3);
	};
      
	strcpy(dev, ifr.ifr_name);
	return fd;
}



int main(int argc,char** argv)
{
	struct hostent	*host_ptr;
	struct sockaddr_in local_sck_addr;
	int i;

	localHost = argv[1];
	sscanf(argv[2],"%hi",&inPort);
	remoteHost = argv[3];
	sscanf(argv[4],"%hi",&outPort);
	sscanf(argv[5],"%i",&mp);
	sscanf(argv[6],"%llu",&delay);

	//fprintf(stderr,"%llu ",getTime());

	fifos = malloc(sizeof(struct fifo*)*mp);
	for (i=0;i<mp;i++) {
		fifos[i] = malloc(sizeof(struct fifo));
		fifoInit(fifos[i]);
	};

        char dev[] = "hatun%d\0......";
	tapFd = tapOpen(dev);
	//	fprintf(stderr,"Hatun initialized");
	
	if (!(host_ptr=gethostbyname(localHost))) {
		perror("Nieznany host lokalny");
		return 1;
	};

	memcpy(&local_sck_addr.sin_addr, host_ptr->h_addr, host_ptr->h_length);
	local_sck_addr.sin_family = host_ptr->h_addrtype; /* AF_INET */
	local_sck_addr.sin_port = htons(inPort);

	if ((serverFd=socket(PF_INET,SOCK_DGRAM,0))<0) {
		perror("Nie mozna utworzyc gniazdka"); 
		return 2; 
	};

	if (bind(serverFd,&local_sck_addr, sizeof local_sck_addr)!=0) { 
		perror("Niemoge bindowac");
		return 3; 
	};

	if (!(host_ptr=gethostbyname(remoteHost))) {
		perror("Nieznany host lokalny");
		return 1;
	};

	memcpy(&remote_sck_addr.sin_addr, host_ptr->h_addr, host_ptr->h_length);
	remote_sck_addr.sin_family = host_ptr->h_addrtype; /* AF_INET */
	remote_sck_addr.sin_port = htons(outPort);

	if ((senderFd=socket(PF_INET,SOCK_DGRAM,0))<0) {
		perror("Nie mozna utworzyc gniazdka"); 
		return 2; 
	};

	
	pthread_create(&fromUDPThread,NULL,fromUDP,NULL);
	pthread_create(&fromDevThread,NULL,fromDev,NULL);
	pthread_create(&toUDPThread,NULL,toUDP,NULL);

	//	fprintf(stderr,"%llu ",getTime());
	
	while(1) { usleep(1); };
	
	close(serverFd);
	for (i=0;i<mp;i++) free(fifos[i]);
	free(fifos);

	return 0;
}

