#ifdef __WIN32__
   #include <winsock2.h>
   #include <ws2tcpip.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
/*
This is V2, as it now compiles under Cygwin.

Compile with:
gcc -O2 tmnrelay.c -o tmnrelay

Cygwin compile:
gcc tmnrelay.c -o tmnrelay -lws2_32 -mno-cygwin

Usage:
see http://www.csc.liv.ac.uk/~greg/tmnrelay.html

Run on the client sides of the TrackManiaNations game, with the IP(s) of the server(s)

*/
#ifdef __WIN32__
   #pragma pack(1)
   struct iphdr {
      unsigned char      ihl:4,
                         version:4;
      unsigned char      tos;
      unsigned short int tot_len;
      unsigned short int id;
      unsigned short int frag_off;
      unsigned char      ttl;
      unsigned char      protocol;
      unsigned short int check;
      unsigned int       saddr;
      unsigned int       daddr;
      /*The options start here. */
   };

   struct udphdr {
      unsigned short uh_sport;
      unsigned short uh_dport;
      unsigned short uh_ulen;
      unsigned short uh_sum;
   };
#else
   #include <sys/socket.h>	/* these headers are for a Linux system, but */
   #include <netinet/in.h>	/* the names on other systems are easy to guess.. */
   #include <netinet/ip.h>
   #include <arpa/inet.h>
   #define __FAVOR_BSD
   /* use bsd'ish udp header */
   #include <netinet/udp.h>
   #include <unistd.h>
#endif


unsigned short in_cksum(unsigned short *ptr, int nbytes)
{
  register long    sum;
  u_short oddbyte;
  register u_short answer;

  sum = 0;
  while(nbytes > 1)
  {
    sum += *ptr++;
    nbytes -= 2;
  }

  if(nbytes == 1)
  {
    oddbyte = 0;
    *((u_char *) &oddbyte) = *(u_char *)ptr;
    sum += oddbyte;
  }

  sum  = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  answer = ~sum;

  return(answer);
}

/* define the pseudohdr */

struct pseudohdr {              /* for creating the checksums */
  unsigned long saddr;
  unsigned long daddr;
  char useless;
  unsigned char protocol;
  unsigned short length;
};

 

ssize_t udpsend(u_int saddr, u_int daddr, unsigned short sport, unsigned short dport, char *data, unsigned short datalen)
{
            struct  sockaddr_in servaddr;
            struct    iphdr *ip;
            struct    udphdr *udp;

            struct pseudohdr *pseudo;
            char packet[sizeof(struct iphdr)+sizeof(struct udphdr)+datalen];
            int nbytes, sockfd, on = 1;

            sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
            if(sockfd < 0) {
              fprintf(stderr,"cannt creat socket\n");
              return(0);
            }

            if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, (void*)&on, sizeof(on)) == -1) {
              fprintf(stderr, "cannot setsockopt\n");
              return(0);
            }

            /*if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,
	                    (char *)&on, sizeof(on)) == -1)
            {
              printf("[socket_broadcast] can't set SO_BROADCAST option\n");
              // non fatal error 
            }*/
		    
            memset(packet, 0x00, sizeof(packet));
            memcpy(packet+sizeof(struct iphdr)+sizeof(struct udphdr), data, datalen);

            servaddr.sin_addr.s_addr = daddr;
            servaddr.sin_port = htons(dport);
            servaddr.sin_family = AF_INET;

            ip     = (struct iphdr *)packet;
            udp    = (struct udphdr *)(packet + sizeof(struct iphdr));
            pseudo = (struct pseudohdr *)(packet + sizeof(struct iphdr) - sizeof(struct pseudohdr));

            udp->uh_sport = htons(sport);
            udp->uh_dport = htons(dport);
            udp->uh_sum = 0;
            udp->uh_ulen = htons(sizeof(struct udphdr)+datalen);

            pseudo->saddr    = saddr;
            pseudo->daddr    = daddr;
            pseudo->useless     = 0;
            pseudo->protocol = IPPROTO_UDP;
            pseudo->length   = udp->uh_ulen;

            udp->uh_sum = in_cksum((u_short *)pseudo,sizeof(struct udphdr)+sizeof(struct pseudohdr)+datalen); 

            ip->ihl      = 5;
            ip->version  = 4;
            ip->tos      = 0x10;
            ip->tot_len  = sizeof(packet);
            ip->frag_off = 0;
            ip->ttl      = 69;
            ip->protocol = IPPROTO_UDP;
            ip->check    = 0;
            ip->saddr    = saddr;
            ip->daddr    = daddr;

            nbytes = sendto(sockfd, packet, ip->tot_len, 0, (struct sockaddr *)&servaddr,sizeof(servaddr));
            close(sockfd);
            return(nbytes);
}


void usage(char **argv)
 {
   fprintf(stderr,"%s <TrackManiaNation server IP> [more TMN server ips]\n",argv[0]);
   fprintf(stderr," -e  Modify behaviour to support internet routed TMN traffic, spoofing source address.\n");
   fprintf(stderr," -e : Use the first IP as the source IP instead of the received IP.\n");

#ifdef __WIN32__
      WSACleanup();
#endif

   exit(1);
 }

#ifdef __WIN32__
int optind = 1;

char getopt(int argc, char **argv, char *parse) {
   if (optind >= argc)
      return -1;

   if (argv[optind][0]=='-') {
      if (!strcmp(argv[optind],"-el")) {
         optind++;
         return 'e';
      }
      else {
         optind++;
         return '?';
      }
   }

   return -1;
}
#endif

int main(int argc, char **argv)
 {
   int r;
   char buf[BUFSIZ];
   struct sockaddr_in fromaddr, thisaddr;
   int bindno,s;
   socklen_t fromlen;

   char option;
#ifdef __WIN32__
   int opt_usevpn=1;
   WSADATA wsaData;
   WSAStartup(0x0101, &wsaData);
#else
   int opt_usevpn=0;
#endif

   while( (option=getopt(argc, argv, "el?"))!=-1 )
    {
      //printf("%c %s\n",option, optarg );
      switch( option )
       {
#ifdef __WIN32__
        case 'e':
        opt_usevpn=0;
#else
        case 'l':
        opt_usevpn=1;
#endif
         break;
         case ':':  case '?':
            usage(argv);
         break;
       };
    }

   //printf("optind=%d, opt_usevpn=%d\n",optind, opt_usevpn );
   
//   if( (opt_usevpn && argc-optind<2) || (!opt_usevpn && argc!=optind+2) )
   if( argc==1 )
     usage(argv);

   thisaddr.sin_family =AF_INET;
   thisaddr.sin_addr.s_addr=INADDR_ANY;
  // thisaddr.sin_addr.s_addr=inet_addr("255.255.255.255");  // fails for Cygwin
   thisaddr.sin_port=htons(2350);
   
   s=socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
   if( s<0 )
    {
      fprintf(stderr,"socket() failed %d\n",s);
      perror("");
#ifdef __WIN32__
      WSACleanup();
#endif
      return 0;
    }

   /* Wait for response */
   if( (bindno = bind(s, (struct sockaddr *)&thisaddr, sizeof(thisaddr) ) ) < 0 )
    {
      fprintf(stderr,"%s: errno = %d ", argv[0], errno);  perror("");
      fprintf(stderr,"%s: can't bind local address\n", argv[0]);
      perror("");
#ifdef __WIN32__
      WSACleanup();
#endif
      return 2;
    }

   while(1)
    {
      long packetsize=0;
      int i;
      time_t t;
      time(&t);

      fromlen=sizeof(fromaddr);
      r=recvfrom(s, buf,  BUFSIZ, 0, (struct sockaddr *)&fromaddr, &fromlen );
      if( r<0 )
       {
         fprintf(stderr,"recvfrom=%d\n", r );
         perror("");
         continue;
       }

      packetsize=r;

      //fromaddr.sin_addr.s_addr=inet_addr("10.1.1.10");  // works, but where can I get this info from??
      //fromaddr.sin_addr.s_addr=inet_addr("127.0.0.1");  // dont work with openvpn, because of spoofing protection
      //fromaddr.sin_addr.s_addr=inet_addr("10.1.1.0");  // dont work with openvpn, because of spoofing protection
      //fromaddr.sin_addr.s_addr=INADDR_ANY;  // dont work with openvpn, because of spoofing protection
                                          // in the end, the solution is to tell OpenVPN about the IP/subnet this print line gives, then the packet is accepted
      printf("%d %s %d %ld %s",t, inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), packetsize, ctime(&t) );
      //fromaddr.sin_addr.s_addr = daddr;
      fflush(stdout);

      for(i=optind;i<argc;i++)
       {
	 int p;
	 printf("sending to %s\n",argv[i] );
	 for(p=2350;p<=2370;p++)
 	  {
            if( opt_usevpn )
               r=udpsend(fromaddr.sin_addr.s_addr, inet_addr (argv[i]), ntohs(fromaddr.sin_port), p, buf, packetsize);
            else
               r=udpsend(inet_addr (argv[1]), inet_addr (argv[i]), ntohs(fromaddr.sin_port), p, buf, packetsize);
	    //usleep(1000); // just incase
	  }
       } // for each argument ip address
    } // while always
   
   close(s);
#ifdef __WIN32__
      WSACleanup();
#endif
   return 0;
 }

