/*
* frag
* by: Fryxar
* e-mail: fryxar@yahoo.com.ar
*
* Fragment ICMP packet generator
*/

#include<stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>

#define ERROR(msg)  {perror(msg); exit -1;}

#define FRAGS_ALL   0
#define FRAGS_ODD   1
#define FRAGS_EVEN  2 

int open_packet() {
int s, on = 1;
 
  if((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
                ERROR("socket");

   if(setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0)
                ERROR("setsockopt");
 
return s;
}

unsigned short in_cksum(unsigned short *addr,int len)
{
        register int sum = 0;
        u_short answer = 0;
        register u_short *w = addr;
        register int nleft = len;
 
        /*
         * Our algorithm is simple, using a 32 bit accumulator (sum), we add
         * sequential 16 bit words to it, and at the end, fold back all the
         * carry bits from the top 16 bits into the lower 16 bits.
         */
        while (nleft > 1)  {
                sum += *w++;
                nleft -= 2;
        }
 
        /* mop up an odd byte, if necessary */
        if (nleft == 1) {
                *(u_char *)(&answer) = *(u_char *)w ;
                sum += answer;
        }
 
        /* add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
        sum += (sum >> 16);                     /* add carry */
        answer = ~sum;                          /* truncate to 16 bits */
        return(answer);
}

int send_packet(int s, struct sockaddr_in saddr, struct sockaddr_in daddr, int protocol, char frags, int id, int frag_len, int tot_len) {
unsigned char          packet[IP_MAXPACKET];
struct iphdr           *iphdr;
struct icmphdr         *icmphdr;
int                    offset, length;
int                    start, step;

   switch(frags) {
   case FRAGS_ALL:
      start =    0;
      step =     (frag_len<<3);
      break;
   case FRAGS_EVEN:
      start =    0;
      step =     2*(frag_len<<3);
      break;
   case FRAGS_ODD:
      start =    (frag_len<<3);
      step =     2*(frag_len<<3);
      break;
   }

   memset(packet, 0, IP_MAXPACKET);
   length = sizeof(struct iphdr) + (frag_len<<3);
   
   iphdr =            (struct iphdr *)packet;
   icmphdr =          (struct icmphdr *)(packet + sizeof(struct iphdr));

	iphdr->ihl =       5;
	iphdr->version =   IPVERSION;
	iphdr->tot_len =   htons(length);
	iphdr->id =        htons(id);
	iphdr->ttl =       IPDEFTTL;
	iphdr->protocol =  protocol;
	iphdr->saddr =     saddr.sin_addr.s_addr;
	iphdr->daddr =     daddr.sin_addr.s_addr;

   for(offset = start; offset < tot_len; offset += step) {

      if(offset) {
		   // Not first fragment
         iphdr->frag_off = htons(offset>>3);
         bzero(packet + sizeof(struct iphdr), IP_MAXPACKET 
								 - sizeof(struct iphdr));
      } else {
			// First fragment
         iphdr->frag_off = 0; 
			if(protocol == IPPROTO_ICMP) {
            icmphdr->type =               ICMP_ECHO;
            icmphdr->code =               0;
            icmphdr->un.echo.id =         0;
            icmphdr->un.echo.sequence =   0;
            icmphdr->checksum = (unsigned short)in_cksum((unsigned short *)icmphdr, tot_len);
         }
     }

     if(offset + (frag_len<<3) < tot_len) {
        iphdr->frag_off |= htons(IP_MF);
     } else {
        length = sizeof(struct iphdr) + tot_len - offset;
        iphdr->tot_len = htons(length);
     }
  

      iphdr->check =    (unsigned short)in_cksum((unsigned short *)iphdr, sizeof(struct iphdr));

      if(sendto(s, packet, length, 0x0, (struct sockaddr *)&daddr, sizeof(struct sockaddr)) != length)
          ERROR("sendto");
   }
}

void usage(char *program) {
   fprintf(stderr, "frag v"VERSION"\n"
      "usage: %s [options] <source_host> <destination_host>\n\n"
      "options:\n"
      " -i <id>                   Starting session id (range: 1-65535)\n"
      " -s <fragmentsize>         Fragments size (x 8)\n"
      " -l <packetsize>           Total packet size\n"
      " -t <type>                 Set send policity (odd|even|all)\n"
      " -p <protocol>             Set protocol (tcp|udp|icmp...)\n"
      " -a <n>                    Amount of packet to send\n"
      "\ndefault:\n"
      "%s -i 1 -t all -s 7 -p icmp -l 64000 -a 1 my_host.com your_host.com\n"
      "\n", program, program);
exit(-1);
}


int main(int argc, char *argv[]) {
char                *shost, *dhost;
struct hostent      *hostentry;
struct sockaddr_in  saddr, daddr;
struct protoent     *protoent;
int                 s, i;
int                 id = 1, size = 7, len = 64000, amount = 1;
int                 protocol = IPPROTO_ICMP, type = FRAGS_ALL;

  if(argc < 3) usage(argv[0]);

   while((i = getopt(argc, argv, "a:i:s:l:t:p:")) != -1) {
      switch(i) {

         case 'i':
            if(strlen(optarg) == 0) usage(argv[0]);
            id = atoi(optarg);
         break;

         case 's':
            if(strlen(optarg) == 0) usage(argv[0]);
            size = atoi(optarg);
         break;

         case 'a':
            if(strlen(optarg) == 0) usage(argv[0]);
            amount = atoi(optarg);
         break;

         case 'l':
            if(strlen(optarg) == 0) usage(argv[0]);
            len = atoi(optarg);
         break;

         case 't':
            if(!memcmp(optarg, "odd", 4)) type = FRAGS_ODD; 
				else if(!memcmp(optarg, "even", 5)) type = FRAGS_EVEN; 
				else if(!memcmp(optarg, "all", 4)) type = FRAGS_ALL; 
				else usage(argv[0]);
         break;

         case 'p':
            if((protoent=getprotobyname(optarg)) == NULL) usage(argv[0]);
				protocol = protoent->p_proto;
         break;

			default:
			   usage(argv[0]);
				break;
		}
	}
  
  shost = argv[argc-2];
  dhost = argv[argc-1];

// Source address
    if((hostentry = gethostbyname(shost)) == NULL) ERROR("gethostbyname source address");
    memset(&saddr, 0, sizeof(struct sockaddr));
    saddr.sin_family = AF_INET;
    saddr.sin_addr = *((struct in_addr *)hostentry->h_addr);

// Destination address
    if((hostentry = gethostbyname(dhost)) == NULL) ERROR("gethostbyname destination address");
    memset(&daddr, 0, sizeof(struct sockaddr));
    daddr.sin_family = AF_INET;
    daddr.sin_addr = *((struct in_addr *)hostentry->h_addr);

// MAIN
    s = open_packet();

	 for(i = 0; i < amount; i++) {
	    printf("Sending packets with ID %d (frags length=%d, total length=%d)\n",
							  (id + i)%65535, (size<<3), len);
       send_packet(s, saddr, daddr, protocol, type, (id + i)%65535, size, len);
	 }

    close(s);

return(0);
}
