#include <stdlib.h> // exit
#include <unistd.h> // exit
#include <pcap.h> // packet capture
#include <arpa/inet.h> // inet_ntoa
#include <netinet/if_ether.h> // ETHER_ADDR_LEN

#define DEVICE "eth0"
#define PORTS  "udp port 3000 or udp port 3001"

struct ip_header { // Internet Protocol header
	#if BYTE_ORDER == LITTLE_ENDIAN
	u_int ip_hl:4, ip_v:4;
	#endif
	#if BYTE_ORDER == BIG_ENDIAN
	u_int ip_v:4, ip_hl:4;
	#endif
	u_char  ip_tos;            // type of service
	u_short ip_len;            // total length 
	u_short ip_id;             // identification 
	u_short ip_off;            // fragment offset field 
	#define IP_RF 0x8000       // reserved fragment flag 
	#define IP_DF 0x4000       // dont fragment flag 
	#define IP_MF 0x2000       // more fragments flag 
	#define IP_OFFMASK 0x1fff  // mask for fragmenting bits 
	u_char  ip_ttl;            // time to live 
	u_char  ip_p;              // protocol 
	u_short ip_sum;            // checksum 
	// source and destination address 
	struct in_addr ip_src, ip_dst;
};

struct tcp_header { // Transport Control Protocol header
	u_short th_sport;  // source port 
	u_short th_dport;  // destination port 
	int th_seq;    // sequence number - 32 bits 
	int th_ack;    // ack number - 32 bits 
	// unused and .... data offset ! 
	#if BYTE_ORDER == LITTLE_ENDIAN
	u_int th_x2:4, th_off:4;
	#endif
	#if BYTE_ORDER == BIG_ENDIAN
	u_int th_off:4, th_x2:4;
	#endif
	// Control bits, 6 bits [from left to right] 
	u_char  th_flags;
	#define TH_FIN   0x01
	#define TH_SYN   0x02
	#define TH_RST   0x04
	#define TH_PUSH  0x08
	#define TH_ACK   0x10
	#define TH_URG   0x20
	#define TH_ECE   0x40
	#define TH_CWR   0x80
	#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
	u_short th_win;  // window 
	u_short th_sum;  // checksum 
	u_short th_urp;  // urgent pointer 
};

struct udp_header { // Transport Control Protocol header
	u_short th_sport; // source port
	u_short th_dport; // destination port
	u_short th_len;   // length
	u_short th_sum;   // checksum
};

struct ethernet_header { // Ethernet Header
	u_char ether_dhost[ETHER_ADDR_LEN]; // destination address
	u_char ether_shost[ETHER_ADDR_LEN]; // source address
	u_short ether_type; // type of packet
};


void diep(char *s) { perror(s); exit(1); }
void pcap_handler2(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);

int main(void) {
	struct pcap *handle; // handle to the pcap object
	char errbuf[PCAP_ERRBUF_SIZE]; // pcap puts its error messages in here
	bpf_u_int32 mask, net;
	pcap_lookupnet(DEVICE, &net, &mask, errbuf);
	handle = pcap_open_live(DEVICE, 8096, 1, 1000, errbuf);
	if ((handle = pcap_open_live(DEVICE, 8096, 1, 1000, errbuf)) == NULL) { perror("Error: Can't open device"); _exit(0); }
	struct bpf_program filter; // filter to apply to pcap, ie "udp port 4020 or udp port 4030"
	pcap_compile(handle, &filter, PORTS, 0, net); // assign the filter 
	pcap_setfilter(handle, &filter); // activate filter
	unsigned char packet[65535];

	printf("\n** logging: ready to recieve packets, ctrl-c to exit **\n");
	while (1) {
		pcap_loop(handle, 10, pcap_handler2, packet); // callback pcap_handler2 on new packet
		usleep(1);
	}

	pcap_close(handle);
	return 0;
}



void pcap_handler2(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
	const struct ethernet_header *ethernet;
	const struct ip_header *ip;
	const struct tcp_header *tcp;
	const struct udp_header *udp;
	const char   *payload = NULL;
	int port = 0;

	// Define data position 
	ethernet = (struct ethernet_header *)(packet);
	ip       = (struct ip_header*)(packet + sizeof(struct ethernet_header));

	if (ip->ip_p == IPPROTO_TCP) {
		tcp      = (struct tcp_header*)(packet + sizeof(struct ethernet_header) + sizeof(struct ip_header));
		payload  = (const char*)(packet + sizeof(struct ethernet_header) + sizeof(struct ip_header) + sizeof(struct tcp_header));
		port = ntohs(udp->th_dport);
		printf("TCP:%s:%d > %s:%d = %s\n", inet_ntoa(ip->ip_src), ntohs(tcp->th_sport), inet_ntoa(ip->ip_dst), ntohs(tcp->th_dport), payload);

	} else if (ip->ip_p == IPPROTO_UDP) {
		udp      = (struct udp_header*)(packet + sizeof(struct ethernet_header) + sizeof(struct ip_header));
		payload  = (const char*)(packet + sizeof(struct ethernet_header) + sizeof(struct ip_header) + sizeof(struct udp_header));
		port = ntohs(udp->th_dport);
		printf("UDP:%s:%d > %s:%d = %s\n", inet_ntoa(ip->ip_src), ntohs(udp->th_sport), inet_ntoa(ip->ip_dst), ntohs(udp->th_dport), payload);
	}
}
