/*
 * 	sniff.c
 * 
 * 2006 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>

#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include "include/linux/types.h"
#include "net/core/alloc/avl.h"

#include "control.h"

static void zc_usage(char *p)
{
	fprintf(stderr, "Usage: %s -f sniffer_file -c nr_cpus -h\n", p);
}

static void zc_analyze_write(int out_fd, void *ptr, unsigned int size)
{
	int err;

	err = write(out_fd, ptr, size);
	if (err != (signed)size) {
		fprintf(stderr, "Failed to write %u bytes: %s [%d].\n",
				size, strerror(errno), errno);
		return;
	}
}

#define NIPE(eth) \
	(eth)->ether_shost[0], \
	(eth)->ether_shost[1], \
	(eth)->ether_shost[2], \
	(eth)->ether_shost[3], \
	(eth)->ether_shost[4], \
	(eth)->ether_shost[5], \
	(eth)->ether_dhost[0], \
	(eth)->ether_dhost[1], \
	(eth)->ether_dhost[2], \
	(eth)->ether_dhost[3], \
	(eth)->ether_dhost[4], \
	(eth)->ether_dhost[5]

#define NIPQUAD(addr) \
	((unsigned char *)&addr)[0], \
	((unsigned char *)&addr)[1], \
	((unsigned char *)&addr)[2], \
	((unsigned char *)&addr)[3]

static void zc_analyze(int out_fd, void *ptr, unsigned int size)
{
	struct ether_header *eth;
	struct iphdr *iph;
	struct tcphdr *th;
	unsigned char *p = ptr;
	__u16 sport, dport;

	eth = ptr + 18;
#if 0
	printf("%02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x, type: %04x, ",
			NIPE(eth), ntohs(eth->ether_type));
#endif
	if (eth->ether_type == 0xabab) {
		eth = ptr + 190;
#if 0
		unsigned int i;

		p = ptr + 180;
		for (i=0; i<32; ++i)
			printf("%02x ", p[i]);
		printf("\n");
		goto out_exit;
#endif
	}

	if (eth->ether_type != ntohs(ETHERTYPE_IP))
		goto out_exit;

	iph = (struct iphdr *)(eth + 1);
	sport = ((__u16 *)(((void *)iph) + (iph->ihl<<2)))[0];
	dport = ((__u16 *)(((void *)iph) + (iph->ihl<<2)))[1];
	
	if (iph->protocol != IPPROTO_TCP)
		goto out_exit;
#if 1
	printf("%u.%u.%u.%u:%u -> %u.%u.%u.%u:%u, proto: %u, ",
			NIPQUAD(iph->saddr), ntohs(sport), 
			NIPQUAD(iph->daddr), ntohs(dport), iph->protocol);
#endif

	th = (struct tcphdr *)(((void *)iph) + (iph->ihl<<2));
	printf("seq: %u, ack: %u, ", ntohl(th->seq), ntohl(th->ack_seq));
	printf("\n");
out_exit:
	return;
}

int main(int argc, char *argv[])
{
	int ch, err, out_fd;
	unsigned int i, num, nr_cpus;
	char *ctl_file, *out_file;
	struct zc_data zcb[1024];
	struct pollfd *pfd;
	struct zc_control *zc_ctl, *ctl;

	ctl_file = out_file = NULL;
	nr_cpus = 2;

	while ((ch = getopt(argc, argv, "f:o:c:h")) != -1) {
		switch (ch) {
			case 'c':
				nr_cpus = atoi(optarg);
				break;
			case 'o':
				out_file = optarg;
				break;
			case 'f':
				ctl_file = optarg;
				break;
			case 'h':
			default:
				zc_usage(argv[0]);
				return 0;
		}
	}

	if (nr_cpus > 1024) {
		fprintf(stderr, "Wrong number of CPUs %d.\n", nr_cpus);
		zc_usage(argv[0]);
		return -1;
	}

	if (!ctl_file || !out_file) {
		fprintf(stderr, "You must call %s with -f and -o parameters.\n", argv[0]);
		zc_usage(argv[0]);
		return -1;
	}

	out_fd = open(out_file, O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (!out_fd) {
		fprintf(stderr, "Failed to open output file %s: %s [%d].\n", out_file, strerror(errno), errno);
		return -1;
	}

	pfd = malloc(sizeof(struct pollfd) * nr_cpus);
	if (!pfd) {
		fprintf(stderr, "Failed to allocate polling structures for %d cpus.\n", nr_cpus);
		return -ENOMEM;
	}
	memset(pfd, 0, sizeof(struct pollfd) * nr_cpus);

	zc_ctl = zc_ctl_init(nr_cpus, ctl_file);
	if (!zc_ctl)
		return -1;
	
	for (i=0; i<nr_cpus; ++i) {
		pfd[i].fd = zc_ctl[i].fd;
		pfd[i].events = POLLIN;
		pfd[i].revents = 0;
	}

	while (1) {
		int poll_ready, j;

		poll_ready = poll(pfd, nr_cpus, 1000);
		if (poll_ready == 0)
			continue;
		if (poll_ready < 0)
			break;

		for (j=0; j<poll_ready; ++j) {
			if ((!pfd[j].revents & POLLIN))
				continue;

			pfd[j].events = POLLIN;
			pfd[j].revents = 0;

			err = read(zc_ctl[0].fd, zcb, sizeof(zcb));
			if (err <= 0) {
				fprintf(stderr, "Failed to read data from control file %s: %s [%d].\n", 
						ctl_file, strerror(errno), errno);
				break;
			}

			num = err/sizeof(struct zc_data);

			for (i=0; i<num; ++i) {
				struct zc_data *z;
				void *ptr;
				struct zc_data *e;

				z = &zcb[i];

				if (z->entry >= ZC_MAX_ENTRY_NUM || z->cpu >= nr_cpus)
					continue;

				ctl = &zc_ctl[z->cpu];

				if (zc_prepare(ctl, z->entry))
					continue;

				e = &ctl->e[z->entry];

#if 0
				printf("dump %4d.%4d: ptr: %p, start: %p, size: %u, off: %u: entry: %u, cpu: %d: ", 
					i, num, z->data.ptr, z->start.ptr, z->size, z->off, z->entry, z->cpu);
#endif
				ptr = e->data.ptr + z->off;

				zc_analyze_write(out_fd, ptr, z->size);
				//zc_analyze(out_fd, ptr, z->size);
			}
			
			err = write(zc_ctl[0].fd, zcb, num*sizeof(struct zc_data));
			if (err < 0) {
				fprintf(stderr, "Failed to write data to control file %s: %s [%d].\n", 
						ctl_file, strerror(errno), errno);
				break;
			} else if (err == 0) {
				write(zc_ctl[0].fd, zcb, num*sizeof(struct zc_data));
			}
		}
	}

	return 0;
}

