/* ttunnel.c test tunnel Laurent GUERBY 20160411 - GNU GPLv3 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <string.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/uio.h>
#include <time.h>

/*
7: tunt1: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1400 qdisc pfifo_fast state UP group default qlen 700
ip li set dev tunt1 txqueuelen 700
ip li set dev tunt1 mtu 1400
/usr/bin/nice -n -20 ./tunt
*/

int tun_alloc(char *dev_name)
{
    struct ifreq ifr;
    int fd, err;

    if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
      return -1;
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI; 
    if( *dev_name ) strncpy(ifr.ifr_name, dev_name, IFNAMSIZ);

    if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
       close(fd);
       return err;
    }
    strncpy(dev_name, ifr.ifr_name, IFNAMSIZ);
    if(ioctl(fd, TUNSETPERSIST, 1) < 0){
      perror("disabling TUNSETPERSIST");
      exit(1);
    }
    return fd;
}


/*
-b bind_address
-p port
-d dest_address dest_port
-t tunname

 */

#define IOV_N 128
#define MSG_LEN 2048
#define SERVER_MODE 0
#define CLIENT_MODE 1

int main(int argc, char **argv) {
  char devname[IFNAMSIZ] = "tunt1";
  int port = 30225;
  int tun_fd;
  int sock;
  int err;
  struct sockaddr_in server_addr;
  struct sockaddr_in src_addr;
  struct sockaddr_in dest_addr;
  fd_set rfds0;
  fd_set rfds;
  struct timespec ts,ts1,ts2,dt,dtr;
  int optval;
  int i;
  ssize_t count;
  ssize_t send_count;
  char msgbuf[MSG_LEN];
  socklen_t socklen;
  int mode = SERVER_MODE;
  int server_has_client = 0;
  int prev_send_count = 0;
  
  printf("argc %d\n", argc);
  if(argc>1) mode = CLIENT_MODE; 

  tun_fd = tun_alloc(devname);
  printf("tun_fd %d\n", tun_fd);

  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if(sock<0) return 1;

  optval = 1;
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
	     (const void *)&optval , sizeof(int));
  
  memset(&server_addr,0,sizeof(struct sockaddr_in));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  if (mode==SERVER_MODE) {
    server_addr.sin_port = htons(port);
  } else {
    server_addr.sin_port = 0;
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(port);
    err = inet_aton("91.224.148.214",&dest_addr.sin_addr);
  }
  err = bind(sock, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_in));
  if(err<0) return 2;
  
  printf("sock %d\n", sock);

  FD_ZERO(&rfds0);
  FD_SET(tun_fd, &rfds0);
  FD_SET(sock, &rfds0);  

  clock_gettime(CLOCK_MONOTONIC_RAW,&ts1);
  while (1) {
    rfds = rfds0;
    ts.tv_sec = 10;
    ts.tv_nsec = 0;
    /* clock_gettime(CLOCK_MONOTONIC_RAW,&ts1); */
    err = pselect(5, &rfds, NULL, NULL, &ts, NULL);
    /* clock_gettime(CLOCK_MONOTONIC_RAW,&ts2); */
    /* printf("clock %ld %ld\n",(long) ts2.tv_sec - (long)ts1.tv_sec,ts2.tv_nsec-ts1.tv_nsec); */
    /* printf("pselect %d\n",err); */

    if (err < 0) {
      return 4;
    } else if (err > 0) {
      if(FD_ISSET(sock, &rfds)) {
	socklen = sizeof(struct sockaddr_in);
	count = recvfrom(sock,&msgbuf,MSG_LEN,0,(struct sockaddr *)&src_addr,&socklen);
	if(mode == SERVER_MODE) {
	  server_has_client = 1;
	  dest_addr = src_addr;
	}
	if(count>0) {
          err = write(tun_fd,&msgbuf,count);
	}
      }
      if(FD_ISSET(tun_fd, &rfds)) {
	count = read(tun_fd,&msgbuf,MSG_LEN);
	if (prev_send_count>15000) {
	  clock_gettime(CLOCK_MONOTONIC_RAW,&ts2);
	  dt.tv_sec = ts1.tv_sec - ts2.tv_sec;
	  dt.tv_nsec = ts1.tv_nsec - ts2.tv_nsec + prev_send_count*13; /* 13 ~ 600 Mbit/s */
	  if(dt.tv_sec*1000000000+dt.tv_nsec>1000) {
	    /* nanosleep(&dt,&dtr); */
	    clock_nanosleep(CLOCK_MONOTONIC_RAW,0,&dt,&dtr);
	  }
	  ts1=ts2;
	  prev_send_count=0;
	}
	send_count = sendto(sock,&msgbuf,count,0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr_in));
	prev_send_count+=count;
      }
    } else {
      printf("no data\n");
    }
  }
  
  return 0;
}
