#include <net/bpf.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <err.h>
#include <fcntl.h>
#include <gulliver.h>

#include <_pack1.h>

#define ioctl ioctl_socket

typedef struct EthHdr_t		EthHdr_t;
typedef struct IpHdr_t		IpHdr_t;
typedef struct IcmpHdr_t	IcmpHdr_t;
typedef struct Packet_t		Packet_t;

/* Ethernet header */
struct EthHdr_t {
	uint8_t		dst[6];
	uint8_t		src[6];
	uint16_t		type;
#define ETH_TYPE_IP  0x0800
};

/* IP header */
struct IpHdr_t {
	uint8_t		ver_hlen;
	uint8_t		tos;
	uint16_t	tl;

	uint16_t	id;
	uint16_t	fl_froff;

	uint8_t		ttl;
	uint8_t		proto;
#define IP_PROTO_ICMP  0x01
	uint16_t	hcks;
	uint32_t	src;
	uint32_t	dst;
};

/* ICMP header */
struct IcmpHdr_t {
	uint8_t		type;
#define ICMP_TYPE_ECHO_REQUEST  0x08
#define ICMP_TYPE_ECHO_REPLY    0x00
	uint8_t		code;
	uint16_t	cksum;
	uint16_t	id;
	uint16_t	seq;
};

/* Full packet data structure */
struct Packet_t {
	EthHdr_t	eth;
	IpHdr_t		ip;
	IcmpHdr_t	icmp;
	uint8_t		data[4096];
};


static char PktRead[sizeof (Packet_t)];

static Packet_t		Pkt;		/* Our packet buffer */


/* Calculate IP checksum over _len_ bytes at _data_ */
static uint16_t IpCksum( void *data, int len ) {
	uint32_t  sum = 0;
	uint16_t  *p = data;
	int  nwords = len >> 1;

	while ( nwords-- != 0 )
		sum += *p++;

	if ( len & 1 ) {
		union {
			uint16_t w;
			uint16_t c[2];
		} u;
		u.c[0] = *(uint8_t *)p;
		u.c[1] = 0;
		sum += u.w;
	}

	/* end-around-carry */
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return ~sum;
}

/* Convert unsigned long long to 48bit MAC address in network order */
static void htonm( void *dst, uint64_t mac ) {
	mac = ENDIAN_RET64( mac );
	memcpy( dst, &((uint8_t*) &mac)[2], 6 );
}

/* Convert packet from host to network order and vice versa */
static void Swap( Packet_t *pkt ) {
	pkt->eth.type    = ENDIAN_BE16( pkt->eth.type );
	pkt->ip.tl       = ENDIAN_BE16( pkt->ip.tl );
	pkt->ip.id       = ENDIAN_BE16( pkt->ip.id );
	pkt->ip.fl_froff = ENDIAN_BE16( pkt->ip.fl_froff );

	/* The src / dst IP addresses are put into the proper
	endian by the inet_aton function. */
//	pkt->ip.src      = ENDIAN_BE32( pkt->ip.src );
//	pkt->ip.dst      = ENDIAN_BE32( pkt->ip.dst );

	pkt->icmp.id     = ENDIAN_BE16( pkt->icmp.id );
	pkt->icmp.seq    = ENDIAN_BE16( pkt->icmp.seq );
}


static void printmac(unsigned char *mac)
{
	int i;
	for (i = 0; i < 5; i++) {
		printf("%02x:",*(mac+i));
	}
	printf("%02x",*(mac+5));

}

int main( int argc, char *argv[] ) {
	uint32_t  ipsrc;
	int  i, ret, fd, len, dlen;
	struct ifreq bound_if;
	int buf_len = sizeof(Pkt);

	if ( ( 6 > argc ) || ( 7 < argc ) ) {
		errx( 1, "Example usage: %s en0 0x003456789012 192.168.23.24 0x001234567891 192.168.23.25 [text]", argv[0] );
	}

	strcpy(bound_if.ifr_name, argv[1]);

	/* Open the raw network interface */
	if ( 0 > ( fd = open( "/dev/bpf0", O_RDWR ) ) )
		err( 1, "open" );

	/* Set the buffer length to our packet size */
	if( ioctl( fd, BIOCSBLEN, &buf_len ) == -1 )
		err(1,"BLEN");

	if( ioctl( fd, BIOCSETIF, &bound_if ) > 0 )
		err(1, "SETIF" );

	if( ioctl( fd, BIOCIMMEDIATE, &buf_len ) == -1 )
		err(1, "IMMEDIATE");


	/* Prepare ICMP data */
	if ( 7 == argc ) {
		dlen = strlen( argv[6] );
		memcpy( Pkt.data, argv[6], dlen );
	}
	else {
		for ( dlen = 56, i = 0; i < dlen; i++ )
			Pkt.data[i] = i;
	}

	/* Total packet length (headers and data) */
	len = offsetof( Packet_t, data ) + dlen;

	/* Prepare ethernet header */
	htonm( &Pkt.eth.dst, strtoull( argv[4], NULL, 0 ) );
	htonm( &Pkt.eth.src, strtoull( argv[2], NULL, 0 ) );
	Pkt.eth.type = ETH_TYPE_IP;

	/* Prepare IP header */
	Pkt.ip.ver_hlen = 0x45;		/* IPv4, header len 5 words */
	Pkt.ip.tos = 0x00;				
	Pkt.ip.tl = sizeof Pkt.ip + sizeof Pkt.icmp + dlen;
	Pkt.ip.id = 0x0000;
	Pkt.ip.fl_froff = 0x0000;
	Pkt.ip.ttl = 0x80;
	Pkt.ip.proto = IP_PROTO_ICMP;
	Pkt.ip.src = (inet_addr(argv[3]));
	Pkt.ip.dst = (inet_addr(argv[5]));
	Pkt.ip.hcks = 0;					/* Filled in later */

	/* Prepare ICMP header */
	Pkt.icmp.type  = ICMP_TYPE_ECHO_REQUEST;
	Pkt.icmp.code  = 0x00;
	Pkt.icmp.id    = 0x0000;
	Pkt.icmp.seq   = 0x0000;
	Pkt.icmp.cksum = 0;			/* Filled in later          */

	ipsrc = Pkt.ip.src;
	Swap( &Pkt );			/* Bring packet into network byte order */

	/* Calculate checksums */
	Pkt.ip.hcks    = IpCksum( &Pkt.ip, sizeof Pkt.ip );
	Pkt.icmp.cksum = IpCksum( &Pkt.icmp, sizeof Pkt.icmp + dlen );

	/* write out our fake ICMP packet */
	if ( len != ( ret = write( fd, &Pkt, len ) ) ) {
		err( 1, "write" );
	}


	/* Wait for packets */
	for (;;) {
		char *p = PktRead; 
		char *enddata;
		/* Get data from interface */
		if ( 0 > ( ret = read( fd, PktRead, sizeof(Packet_t)) ) )
			err( 1, "read" );

		/* Need to have at least one full bpf_hdr to process. */
		enddata = PktRead + (ret - sizeof(struct bpf_hdr));

		/* Process packets.  More than one can be read into the buffer. */
		/* Note that this doesn't handle the case where only a part of the
		packet is read in by the read. */
		do {
			struct bpf_hdr *hdr;
			unsigned char *t;
			hdr = (struct bpf_hdr *)p;

			/* Skip BPF header, copy packet. */
			memcpy(&Pkt, p + hdr->bh_hdrlen, hdr->bh_caplen);

			/* Move pointer to next header.*/
			p += BPF_WORDALIGN(hdr->bh_caplen + hdr->bh_hdrlen);

			Swap( &Pkt );	/* Bring packet into host byte order    */
					
			printf("%d bytes from ",hdr->bh_caplen);
			printmac(Pkt.eth.src);

			if (Pkt.eth.type == ETH_TYPE_IP) {

				printf(" IP %s ", inet_ntoa(Pkt.ip.src));	

				if (Pkt.ip.proto == IP_PROTO_ICMP) { 
					if  (Pkt.icmp.type == ICMP_TYPE_ECHO_REPLY ) {
						printf("\t   ECHO REPLY"); 
					} else {
						printf("\t   ECHO REQUEST"); 
					}
				}
			}
			printf("\n");	
		} while (p < enddata);

		printf( "\n" );
	}

	return EXIT_SUCCESS;
}
