/*
	build-udp.c
	ems 4/2001
*/

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define PROTO_UDP       17
#define IP_HEADER_SIZE  20
#define UDP_HEADER_SIZE 8


const char port1_ip[] = "10.0.0.1";
const char port2_ip[] = "10.0.0.2";

unsigned char ip_header[20];
unsigned char udp_header[2*4];

const unsigned char ttl = 255;
const unsigned char tos = 0x00;
const unsigned char ip_protocol = PROTO_UDP;

/* prototypes */
unsigned short calc_udp_checksum(unsigned char *udp_packet, unsigned short udp_len, unsigned char protocol, unsigned char *src_ip, unsigned char *dst_ip);
unsigned short calc_ip_checksum(unsigned char *data, unsigned short len);
unsigned int build_udp(unsigned char **udp_packet_p, unsigned short src_port, unsigned short dst_port, unsigned char *data, int data_len, unsigned char *src_ip, unsigned char *dst_ip);
unsigned int build_ip(unsigned char **ip_packet_p, unsigned char *src_ip,
	unsigned char *dst_ip, unsigned char *data, int data_len);
void string_to_ip(char *address, unsigned char *a, unsigned char *b, unsigned char *c, unsigned char *d);
unsigned short compute_inet_checksum(unsigned short *addr, unsigned short count);

int debug = 1;

int main(int argc, char* argv[]) {
	int ip_len = 0, udp_len = 0;
	int i;
	unsigned char *udp_packet, *ip_packet;
	unsigned char src_ip[4], dst_ip[4];
	
	unsigned short src_port = 100;
	unsigned short dst_port = 100;
	
	char	*src_ip_str = "10.0.0.1",
		*dst_ip_str = "10.0.0.2";
		
	unsigned char data[16];
	int data_len;

	if(argc == 5) {
		src_ip_str = argv[1];
		src_port   = atoi(argv[2]);
		dst_ip_str = argv[3];
		dst_port   = atoi(argv[4]);
	} else
	if(argc>1) {
		fprintf(stderr,"usage: %s src_ip src_port dst_ip dst_port\n",argv[0]);
		exit(1);
	}
	
	data[0] = 0xC0;
	data[1] = 0xDE;
	data[2] = 0xAC;
	data[3] = 0xE1;
	data_len = 4;

	string_to_ip(src_ip_str,  src_ip+0, src_ip+1, src_ip+2, src_ip+3);
	string_to_ip(dst_ip_str,  dst_ip+0, dst_ip+1, dst_ip+2, dst_ip+3);

	udp_len = build_udp(&udp_packet, src_port, dst_port, data, data_len, src_ip, dst_ip);

	if(debug) {
		fprintf(stderr,"UDP packet: [");
		for(i=0;i<udp_len;i++) fprintf(stderr," %02x",udp_packet[i]);
		fprintf(stderr,"]\n");
	}

	ip_len  = build_ip(&ip_packet, src_ip, dst_ip, udp_packet, udp_len);

	if(debug) fprintf(stderr,"IP packet: [");
	for(i=0;i<ip_len;i++) printf("%s%02x",i?" ":"",ip_packet[i]);
	fflush(stdout);
	if(debug) fprintf(stderr,"]\n");

	free(udp_packet);
	free(ip_packet);

	return 0;
}




unsigned int build_udp(unsigned char **udp_packet_p, unsigned short src_port, unsigned short dst_port, unsigned char *data, int data_len, unsigned char *src_ip, unsigned char *dst_ip) {
	unsigned short udp_len = 0;
	unsigned char *udp_packet;
	unsigned short checksum;


	udp_len = data_len + 2*4;
	udp_packet = (unsigned char *)malloc(udp_len);

	udp_packet[0] = src_port >> 8;
	udp_packet[1] = src_port & 0xFF;

	udp_packet[2] = dst_port >> 8;
	udp_packet[3] = dst_port & 0xFF;

	udp_packet[4] = udp_len >> 8;
	udp_packet[5] = udp_len & 0xFF;

	udp_packet[6] = 0x00;
	udp_packet[7] = 0x00;
	/* payload starts 8 bytes in */
	memcpy(udp_packet+UDP_HEADER_SIZE,data,data_len);

	checksum = calc_udp_checksum(udp_packet,udp_len,PROTO_UDP,src_ip,dst_ip);
	
	udp_packet[6] = checksum >> 8;
	udp_packet[7] = checksum & 0xFF;

	*udp_packet_p = udp_packet;
	return udp_len;
}

unsigned short calc_udp_checksum(unsigned char *udp_packet, unsigned short udp_len, unsigned char protocol, unsigned char *src_ip, unsigned char *dst_ip) {
	unsigned char *fake_udp_packet;
	unsigned char udp_fakeheader[12];
	unsigned short checksum;
	unsigned short fake_udp_len = 0;

	/* build the fake udp header used for checksum purposes */
	memcpy(udp_fakeheader+0,src_ip,4);
	memcpy(udp_fakeheader+4,dst_ip,4);
	udp_fakeheader[ 8] = 0x00;
	udp_fakeheader[ 9] = PROTO_UDP;
	udp_fakeheader[10] = udp_len >> 8;
	udp_fakeheader[11] = udp_len & 0xFF;
	fake_udp_len = udp_len + 12;
	fake_udp_packet = malloc(fake_udp_len);
	memcpy(fake_udp_packet+ 0, udp_fakeheader, 12);
	memcpy(fake_udp_packet+12, udp_packet,      udp_len);

	free(fake_udp_packet);

	checksum = calc_ip_checksum(fake_udp_packet,fake_udp_len);
	checksum = compute_inet_checksum(fake_udp_packet,fake_udp_len);
	
	return checksum;
}

unsigned int build_ip(unsigned char **ip_packet_p, unsigned char *src_ip,
	unsigned char *dst_ip, unsigned char *data, int data_len) {
	
	unsigned short checksum;
	
	int ip_len = 0;
	unsigned char *ip_packet;
	
	ip_len = data_len + IP_HEADER_SIZE;
	ip_packet = malloc(ip_len);
	
	ip_packet[ 0] = 0x45;
	ip_packet[ 1] = tos;
	ip_packet[ 2] = (unsigned char)(ip_len>>8);
	ip_packet[ 3] = (unsigned char)(ip_len&0xFF);

	ip_packet[ 4] = 0x00; /* identification */
	ip_packet[ 5] = 0x00; /*    (16-bit)    */
	ip_packet[ 6] = 0x00;  /*         flags +       */
	ip_packet[ 7] = 0x00;  /* frag offset - frag #0 */

	ip_packet[6] |= 0x40; /* set Don't Fragment flag */

	ip_packet[ 8] = ttl;
	ip_packet[ 9] = ip_protocol;
	ip_packet[10] = 0x00; /*  header  */
	ip_packet[11] = 0x00; /* checksum */

	/* Source * Destination IP */
	memcpy(ip_packet+12,src_ip,4);
	memcpy(ip_packet+16,dst_ip,4);

	/* Calculate header checksum */
	checksum = compute_inet_checksum(ip_packet,IP_HEADER_SIZE);
	ip_packet[10] = checksum>>8;
	ip_packet[11] = checksum&0xFF;

	/* payload starts 20 bytes in */
	memcpy(ip_packet+IP_HEADER_SIZE,data,data_len);

	*ip_packet_p = ip_packet;
	return ip_len;
}

void string_to_ip(char *address, unsigned char *a, unsigned char *b, unsigned char *c, unsigned char *d) {
	int A,B,C,D;
	
	/* Source * Destination IP */
	if(debug) {
		fprintf(stderr,"string_to_ip(\"%s\") =", address); 
		fflush(stderr);
	}
	sscanf(address, "%u.%u.%u.%u", &A, &B, &C, &D);
	*a = A; *b = B; *c = C, *d = D;

	if(debug) {
		fprintf(stderr," %u.%u.%u.%u\n", *a, *b, *c, *d);
		fflush(stderr);
	}
}

/*
	calc_ip_checksum()
	
	From RFC 791:
	The checksum field is the 16 bit one's complement of the one's
	complement sum of all 16 bit words in the header.  For purposes of
	computing the checksum, the value of the checksum field is zero.
	
	See RFC 1071
*/          
unsigned short calc_ip_checksum(unsigned char *data, unsigned short len) {
	unsigned long sum=0;
	unsigned int i;

	if(debug) fprintf(stderr,"calc_ip_checksum(*,%u) =",len);
	
	for(i=0;i<len;i+=2) {
if(debug) fprintf(stderr,"calc_ip_checksum():%4d: data[i]=%02x, data[i+1]=%02x, sum += %04x\n",
len, data[i],data[i+1], (unsigned short)(data[i]<<8 | data[i+1]));
		sum += (unsigned long)(data[i]<<8 | data[i+1]);
	}

	sum = ~sum & 0xFFFF;

	if(debug) fprintf(stderr," %04x\n",sum);
	return sum;
}

unsigned short compute_inet_checksum(unsigned short *addr, unsigned short count) {
	/* Compute Internet Checksum for "count" bytes
	 * beginning at location "addr".
	 */
	register long sum = 0;
	unsigned short checksum = 0;

	if(debug) fprintf(stderr,"compute_inet_checksum(*,%u) = ", count);
      
	while( count > 1 )  {
		/*  This is the inner loop */
		sum += * (unsigned short *) addr++;
		count -= 2;
	}
      
	/*  Add left-over byte, if any */
	if( count > 0 )
		sum += * (unsigned char *) addr;
      
	/*  Fold 32-bit sum to 16 bits */
	while (sum>>16)
		sum = (sum & 0xffff) + (sum >> 16);
      
	checksum = ~sum;

	if(debug) fprintf(stderr," 0x%04x\n", checksum);
	return checksum;
}


/*

set ttl 255
# UDP=17
set ip_protocol 17

set udp_len 8
set ip_len 20+$udp_len

# port1 -> port2
# IP = 0800
set ether_header "$port2_mac $port1_mac 0800"


# IPv4, 5-word IP header
set ip_header "45"

# TOS = 00
set ip_header "$ip_header 00"

# total length (from 45.. to end of whole ip packet)
set ip_header "$ip_header [format "%02X" $ip_len]"


# The checksum field is the 16 bit one's complement of the one's
# complement sum of all 16 bit words in the header.  For purposes of
# computing the checksum, the value of the checksum field is zero.

set ip_header "$ip_header [format "%04X" $ip_header_sum]"


# src ip
set ip_header "$ip_header $port1_ip_hex $port2_ip_hex [format "%02X" $ttl] [format "%02X" $ip_protocol]"



puts "Ether header: $ether_header"
puts "IP header:    $ip_header"


4500001C ip
FB2B4000 ip
FF178FA3 ip
 AC17C886 src ip
 C6CEB5A4 dst ip
0800ADE8 data
4A170000 data


00 00 01 00 00 01
00 00 02 00 00 02
08 00
4500001C ip
64D24000 ip
FE1726FD ip
 C6CEB5A4 src
 AC17C886 dst
0000B5E7 data
4A180000 data

*/

