#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. As has been seen to work with 
OpenVPN, just make sure OpenVPN knows about the source IP this prog is using,
or OpenVPN will think a spoofed packet is coming down the tunnel and drop it.
You need to be looking at the 'route' and 'iroute' OpenVPN server side options.

Compile with:
gcc -O2 rain6relay.c -o rain6relay

Cygwin compile:
gcc rain6relay.c -o rain6relay -lws2_32 -mno-cygwin

Usage:
see http://www.csc.liv.ac.uk/~greg/rain6relay.html

Run on the client sides of the Rainbow 6 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 <Rainbow6 server ip> [more Rainbow 6 server ips]\n",argv[0]);
   //fprintf(stderr," -e  Modify behaviour to support internet routed NFS2 traffic, spoofing source address.\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(8777);
   
   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=8777;p<=8786;p++)
 	  {
          r=udpsend(fromaddr.sin_addr.s_addr, 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;
 }

