#include "driver.h"

pcap_t *handle = NULL;

int main(int argc, char **argv) {
	pcap_if_t *alldevs, *device;
	bool is_valid_device = false;
	char errbuf[PCAP_ERRBUF_SIZE], *devname;
	char *filters = NULL;
	struct bpf_program fp;
	int filters_size, i;

	/* This program takes the interface name as argument */
	if(argc < 2) {
		fprintf(stderr, "ERROR: You must specify a network interface to listen on (and optionaly libcap filters).\n");
		exit(EXIT_FAILURE);
	}

	devname = argv[1];
	filters_size = 1;

	/* Filters string */
	for(i = 2; i < argc; i++) {
		filters_size += strlen(argv[i]) + 1;
		filters = realloc(filters, filters_size);

		if(filters == NULL) {
			fprintf(stderr, "ERROR: Memory allocation error for filters.");
			perror("send()");
			exit(errno);
		}

		snprintf(filters, filters_size, "%s %s", (i != 2 ? filters : ""), argv[i]);
	}

	while(true) {

		/* Here is a "permission denied" if the UID has not root permissions */
		if(pcap_findalldevs(&alldevs , errbuf)) {
			fprintf(stderr, "ERROR: Finding devices (%s).\n", errbuf);
			exit(EXIT_FAILURE);
		}

		/* Check if the defined interface exists or not */
		for(device = alldevs; device != NULL && !is_valid_device; device = device->next) {
			if(!strcmp(device->name, devname)) {
				is_valid_device = true;
			}
		}

		if(!is_valid_device) {
			fprintf(stderr, "ERROR: You must specify a convenable network interface.\n");
			exit(EXIT_FAILURE);
		}

		/* Create socket with the java exporter */
		create_exporter_socket(&exporterSocket);
		fprintf(stderr, "DEBUG: Connected");

		/* Open the interface */
		handle = pcap_open_live(devname, MAX_PACKET_SIZE, 1, 0, errbuf);

		if(handle == NULL) {
			fprintf(stderr, "ERROR: Couldn't open device %s (%s).\n", devname, errbuf);
			exit(EXIT_FAILURE);
		}

		if(filters_size > 1) {

			/* Compile filters */
			if(pcap_compile(handle, &fp, filters, 0, PCAP_NETMASK_UNKNOWN) == -1) {
				fprintf(stderr, "Couldn't parse filter %s: %s\n", filters, pcap_geterr(handle));
				exit(EXIT_FAILURE);
			}

			/* Apply filters */
			if(pcap_setfilter(handle, &fp) == -1) {
				fprintf(stderr, "Couldn't install filter %s: %s\n", filters, pcap_geterr(handle));
				exit(EXIT_FAILURE);
			}
		}

		/* Main loop capturing each packet */
		pcap_loop(handle, -1, process_packet, NULL);

		/* Close connection with the exporter */
		closesocket(exporterSocket);

		fprintf(stderr, "pcap_loop aborted");
	}
	
	/* Free filters */
	if(filters_size > 1) {
		free(filters);
	}

	return EXIT_SUCCESS;
}

/* Create socket with the java exporter */
void create_exporter_socket(int *sock) {
	bool connection = false;
	struct hostent *hostinfo = NULL;
	struct sockaddr_in sin = { 0 };
	const char *hostname = "localhost";
	int i;

	*sock = socket(AF_INET, SOCK_STREAM, 0);

	if(*sock == INVALID_SOCKET) {
		fprintf(stderr, "ERROR: Creating socket.");
		perror("socket()");
		exit(errno);
	}

	hostinfo = gethostbyname(hostname);

	if(hostinfo == NULL) {
		fprintf(stderr, "ERROR: Unknown host (%s).\n", hostname);
		exit(EXIT_FAILURE);
	}

	sin.sin_addr = *(struct in_addr*) hostinfo->h_addr;
	sin.sin_port = htons(PORT);
	sin.sin_family = AF_INET;

	/* TCP connection to Flowoid */
	for(i = 0; i < 10 && !connection; i++) {
		sleep(2);
	
		if(connect(*sock, (struct sockaddr*) &sin, sizeof(struct sockaddr)) != SOCKET_ERROR) {
			connection = true;
		}
	}

	if(!connection) {
		fprintf(stderr, "ERROR: Connecting to Flowoid (TCP %d).\n", PORT);
		perror("connect()");
		exit(errno);
	}
}

/* Process every sniffed packet */
void process_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
	int hsize, hl2len, hl3len = 0;
	int hl4len = NO_USEFUL_L4_FOUND;
	unsigned short ethertype, next = -1;
	
	if(handle->md.cooked) {
		struct sll_header *sll = (struct sll_header*) packet;
		ethertype = ntohs(sll->sll_protocol);
		hl2len = sizeof(struct sll_header);
		
	} else {
		struct ether_header *ether = (struct ether_header*) packet;
		hl2len = sizeof(struct ether_header);

		if((ether->ether_type & 0xff00) == ETHERTYPE_VLAN) {
			ethertype = ntohs((unsigned short) (*(&(ether->ether_type) + 2)));
			hl2len += 4;
		} else {
			ethertype = ntohs(ether->ether_type);
		}
	}

	switch(ethertype) {

		// IPv4
		case ETH_P_IP: {
			struct iphdr *ip4 = (struct iphdr*) (packet + hl2len);
			hl3len = ip4->ihl * 4;
			next = ip4->protocol;

			break;
		}

		// IPv6
		case ETH_P_IPV6: {
			struct ip6_hdr *ip6 = (struct ip6_hdr*) (packet + hl2len);
			hl3len = sizeof(struct ip6_hdr);
			next = ip6->ip6_nxt;

			break;
		}
	}

	nexthl3:
	switch(next) {

		// L3: IPv6-Route
		case 43: {
			struct ip6_rthdr *routing = (struct ip6_rthdr*) (packet + hl2len + hl3len);
			next = (unsigned short) routing->ip6r_nxt;
			hl3len += sizeof(struct ip6_rthdr);

			goto nexthl3;
		}

		// L3: IPv6-Frag
		case 44: {
			struct ip6_frag *frag = (struct ip6_frag*) (packet + hl2len + hl3len);
			next = (unsigned short) frag->ip6f_nxt;
			hl3len += sizeof(struct ip6_frag);

			goto nexthl3;
		}

		// L3: IPv6-Opts
		case 60: {
			struct ip6_opt *opt = (struct ip6_opt*) (packet + hl2len + hl3len);
			hl3len += sizeof(struct ip6_opt);
			hl3len += (unsigned short) opt->ip6o_len;

			goto nexthl3;
		}

		// L4: ICMP
		case 1: {
			hl4len = sizeof(struct icmphdr_fake);

			break;
		}

		// L4: TCP
		case 6: {
			struct tcphdr *tcp = (struct tcphdr*) (packet + hl2len + hl3len);
			hl4len = tcp->doff * 4;

			break;
		}

		// L4: UDP
		case 17: {
			hl4len = sizeof(struct udphdr);

			break;
		}

		// L4: ICMPv6
		case 58: {
			hl4len = sizeof(struct icmp6_hdr);

			break;
		}
	}
	
	if(hl4len != NO_USEFUL_L4_FOUND) {

		// If cooked reconstructs fake ethernet layer
		// with mac_src = mac_dst = 0x0000000000 and
		// ethertype correctly set from the SLL layer
		if(handle->md.cooked) {
			int fakepktlen, etherlen;
			u_char *fakepkt, *fakeethertype;

			etherlen = sizeof(struct ether_header);
			fakepktlen = etherlen + hl3len + hl4len;
			fakepkt = (u_char*) malloc(fakepktlen);

			if(fakepkt != NULL) {
				memset(fakepkt, 0, etherlen - 2);
				memcpy(fakepkt + etherlen - 2, packet + hl2len - 2, 2 + hl3len + hl4len);

				send_headers(fakepkt, etherlen + hl3len + hl4len);
				free(fakepkt);

			} else {
				fprintf(stderr, "WARNING: Memory allocation error: packet aborted.");
			}

		// Classical ethernet layer
		} else {
			send_headers(packet, hl2len + hl3len + hl4len);
		}
	}
}

/* Once the headers size is computer, these ones can be send to the exporter */
void send_headers(const u_char *packet, short hsize) {

	/* Send the total size of the following headers */
	if(send(exporterSocket, &hsize, sizeof(short), 0) >= 0) {

		/* Send headers just after the size */
		send(exporterSocket, packet, hsize, 0);
	}
}
