/*
 *	Copyright (c) 2004 Rinet Corp., Novosibirsk, Russia
 *
 * Redistribution and use in source forms, with and without modification,
 * are permitted provided that this entire comment appears intact.
 *
 * THIS SOURCE CODE IS PROVIDED ``AS IS'' WITHOUT ANY WARRANTIES OF ANY KIND.
 */

#ifdef	HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#ifdef INET6
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <netdb.h>
#include <pthread.h>

#include "cisco_netflow.h"
#include "trafshow.h"
#include "session.h"
#include "netstat.h"
#include "show_dump.h"
#include "addrtoname.h"


static void read_netflow(SESSION *sd, const unsigned char *data, int len);
static PCAP_HANDLER *match_feeder(PCAP_HANDLER *ph_list, const struct sockaddr *sa);
static void parse_netflow(PCAP_HANDLER *ph, const unsigned char *data, int len);
static char *get_name(const struct sockaddr *sa, char *dst, int size);
static void fprint_tcpflags(FILE *fp, int flags);
static void fprint_tos(FILE *fp, int tos);
static void dump_netflow_v1(const CNF_DATA_V1 *data);
static void dump_netflow_v5(const CNF_DATA_V5 *data);
static void dump_netflow_v7(const CNF_DATA_V7 *data);

int
cisco_netflow_init(ph_list, port)
	PCAP_HANDLER **ph_list;
	int port;
{
	SESSION *sd;
	int sock, on = 1;
	socklen_t slen;
	static struct sockaddr_in sin; /* why static? */

	if (!ph_list) return -1;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);

	if ((sd = session_open(-1, 0, DataSequence)) == 0) {
		perror("session_open");
		return -1;
	}
	sock = session_sock(sd);

	slen = sizeof(on);
#ifdef	SO_REUSEPORT
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, slen) < 0) {
		perror("setsockopt SO_REUSEPORT");
		return -1;
	}
#elif	SO_REUSEADDR
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, slen) < 0) {
		perror("setsockopt SO_REUSEADDR");
		return -1;
	}
#endif
	slen = sizeof(sin);
	if (bind(sock, (struct sockaddr *)&sin, slen) < 0) {
		perror("bind");
		return -1;
	}

	session_setcallback(sd, 0, 0, read_netflow);
	session_setcookie(sd, ph_list);
	return 0;
}

static PCAP_HANDLER *
match_feeder(ph, sa)
	PCAP_HANDLER *ph;
	const struct sockaddr *sa;
{
	const pcap_addr_t *ap;

	if (!sa) return 0;

	for (; ph; ph = ph->next) {
		if (ph->pcap) /* skip pcap devices */
			continue;

		for (ap = ph->addr; ap; ap = ap->next) {
			if (!ap->addr || ap->addr->sa_family != sa->sa_family)
				continue;

			if (ap->addr->sa_family == AF_INET) {
				if (!memcmp(&((struct sockaddr_in *)ap->addr)->sin_addr,
					    &((struct sockaddr_in *)sa)->sin_addr,
					    sizeof(struct in_addr)))
					return ph;
			}
#ifdef	INET6
			else if (ap->addr->sa_family == AF_INET6) {
				if (!memcmp(&((struct sockaddr_in6 *)ap->addr)->sin6_addr,
					    &((struct sockaddr_in6 *)sa)->sin6_addr,
					    sizeof(struct in6_addr)))
					return ph;
			}
#endif
		}
	}
	return 0;
}

static char *
get_name(sa, dst, size)
	const struct sockaddr *sa;
	char *dst;
	int size;
{
	struct hostent *hp = 0;

	if (!sa) return 0;

	if (sa->sa_family == AF_INET) {
		hp = gethostbyaddr((char *)&((struct sockaddr_in *)sa)->sin_addr,
				   sizeof(struct in_addr), AF_INET);
	}
#ifdef	INET6
	else if (sa->sa_family == AF_INET6) {
		hp = gethostbyaddr((char *)&((struct sockaddr_in6 *)sa)->sin6_addr,
				   sizeof(struct in6_addr), AF_INET6);
	}
#endif
	if (hp) {
		int i;
		for (i = 0; i < size-1; i++) {
			if (hp->h_name[i] == '\0' || hp->h_name[i] == '.')
				break;
			dst[i] = hp->h_name[i];
		}
		dst[i] = '\0';
		return dst;
	}
	return 0;
}

static void
read_netflow(sd, data, len)
	SESSION *sd;
	const unsigned char *data;
	int len;
{
	const struct sockaddr *from;
	PCAP_HANDLER *ph, **ph_list = (PCAP_HANDLER **)session_cookie(sd);

	/* sanity check */
	if (!ph_list || !data || len < sizeof(CNF_HDR_V1))
		return;

	if ((from = session_from(sd)) == 0)
		return; /* should not happen */

	if ((ph = match_feeder(*ph_list, from)) == 0) { /* insert new one */
		int cnt = 0;
		PCAP_HANDLER *ph_prev = 0;
		char buf[256];
		pcap_addr_t *ap;

		for (ph = *ph_list; ph; ph = ph->next) {
			if (!ph->pcap) cnt++;
			ph_prev = ph;
		}

		if ((ph = (PCAP_HANDLER *)malloc(sizeof(PCAP_HANDLER))) == 0) {
			perror("malloc");
			return;
		}
		memset(ph, 0, sizeof(PCAP_HANDLER));

		ph->masklen = aggregate;
		if (!get_name(from, buf, sizeof(buf)))
			sprintf(buf, "netflow%d", cnt);
		ph->name = strdup(buf);

		sprintf(buf, "Netflow V%d", ntohs(((CNF_HDR_V1 *)data)->version));
		ph->descr = strdup(buf);

		if ((ap = (pcap_addr_t *)malloc(sizeof(struct pcap_addr))) != 0) {
			memset(ap, 0, sizeof(struct pcap_addr));
			if ((ap->addr = (struct sockaddr *)malloc(sizeof(struct sockaddr))) == 0) {
				perror("malloc");
				return;
			}
			memcpy(ap->addr, from, sizeof(struct sockaddr));
		}
		ph->addr = ap;

		if ((ph->ns_mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t))) == 0) {
			perror("malloc");
			return;
		}
		pthread_mutex_init(ph->ns_mutex, 0);

		ph->prev = ph_prev;
		if (ph_prev)
			ph_prev->next = ph;
		else    *ph_list = ph;
	}

	parse_netflow(ph, data, len);
}

static void
parse_netflow(ph, data, len)
	PCAP_HANDLER *ph;
	const unsigned char *data;
	int len;
{
	struct timeval now;
	int version, counter, msec, hdrlen, dump_it;
	CNF_HDR_V1 *v1h;
	CNF_HDR_V5 *v5h;
	CNF_HDR_V7 *v7h;
	CNF_DATA_V1 *v1d = 0;
	CNF_DATA_V5 *v5d = 0;
	CNF_DATA_V7 *v7d = 0;
	NETSTAT ns;

	v1h = (CNF_HDR_V1 *)data;
	if (!v1h || len < sizeof(CNF_HDR_V1))
		return;

	version = ntohs(v1h->version);
	counter = ntohs(v1h->counter);
	if (version == 1) {
		v1d = (CNF_DATA_V1 *)(data + sizeof(CNF_HDR_V1));
		len -= sizeof(sizeof(CNF_HDR_V1));
		len /= sizeof(CNF_DATA_V1);
	} else if (version == 5) {
		v5h = (CNF_HDR_V5 *)data;
		v5d = (CNF_DATA_V5 *)(data + sizeof(CNF_HDR_V5));
		len -= sizeof(sizeof(CNF_HDR_V5));
		len /= sizeof(CNF_DATA_V5);
	} else if (version == 7) {
		v7h = (CNF_HDR_V7 *)data;
		v7d = (CNF_DATA_V7 *)(data + sizeof(CNF_HDR_V7));
		len -= sizeof(sizeof(CNF_HDR_V7));
		len /= sizeof(CNF_DATA_V7);
	} else	return;

	gettimeofday(&now, 0);

	while (counter-- > 0 && len-- > 0) {
		struct ip_address *src = &ns.ip_src_addr;
		struct ip_address *dst = &ns.ip_dst_addr;

		memset(&ns, 0, sizeof(NETSTAT));
		ns.ip_ver = 4; /* XXX what about IPv6? */
		ns.mtime = now;
		msec = 0;
		dump_it = 0;

		if (version == 1 && v1d) {
			ns.ip_proto = v1d->proto;

			src->ip_addr.s_addr = v1d->src_addr;
			src->ip_port = v1d->src_port;

			dst->ip_addr.s_addr = v1d->dst_addr;
			dst->ip_port = v1d->dst_port;

			ns.pkt_cnt = ntohl(v1d->dpkts);
			ns.pkt_len = ntohl(v1d->doctets);

			msec = ntohl(v1d->lasttime) - ntohl(v1d->firsttime);

		} else if (version == 5 && v5d) {
			ns.ip_proto = v5d->proto;

			src->ip_addr.s_addr = v5d->src_addr;
			src->ip_port = v5d->src_port;

			dst->ip_addr.s_addr = v5d->dst_addr;
			dst->ip_port = v5d->dst_port;

			ns.pkt_cnt = ntohl(v5d->dpkts);
			ns.pkt_len = ntohl(v5d->doctets);

			msec = ntohl(v5d->lasttime) - ntohl(v5d->firsttime);

		} else if (version == 7 && v7d) {
			ns.ip_proto = v7d->proto;

			src->ip_addr.s_addr = v7d->src_addr;
			src->ip_port = v7d->src_port;

			dst->ip_addr.s_addr = v7d->dst_addr;
			dst->ip_port = v7d->dst_port;

			ns.pkt_cnt = ntohl(v7d->dpkts);
			ns.pkt_len = ntohl(v7d->doctets);

			msec = ntohl(v7d->lasttime) - ntohl(v7d->firsttime);
		}

		/* suggest data length (dirty fake) */
		hdrlen = sizeof(struct ip);
		switch (ns.ip_proto) {
		case IPPROTO_TCP:
			hdrlen += sizeof(struct tcphdr);
			break;
		case IPPROTO_UDP:
			hdrlen += sizeof(struct udphdr);
			break;
		case IPPROTO_ICMP:
			hdrlen += sizeof(struct icmp);
			break;
		}
		hdrlen *= ns.pkt_cnt;
		if (ns.pkt_len >= hdrlen)
			ns.data_len = ns.pkt_len - hdrlen;

		if (msec > 0) {
			ns.pkt_cnt_rate = ns.pkt_cnt * 1000 / msec;
			ns.pkt_len_rate = ns.pkt_len * 1000 / msec;
			ns.data_len_rate = ns.data_len * 1000 / msec;
		}

		pcap_save(ph, &ns);

		if (cisco_netflow_dump && ph->name &&
		    !strcmp(cisco_netflow_dump, ph->name) &&
		    netstat_match(&ns, dump_match)) {
			dump_it++;
		}
		if (version == 1 && v1d) {
			if (dump_it) dump_netflow_v1(v1d);
			v1d++;
		} else if (version == 5 && v5d) {
			if (dump_it) dump_netflow_v5(v5d);
			v5d++;
		} else if (version == 7 && v7d) {
			if (dump_it) dump_netflow_v7(v7d);
			v7d++;
		}
	}
}

static void
fprint_tcpflags(fp, flags)
	FILE *fp;
	int flags;
{
	fprintf(fp, "TCPflags: %02x", flags);

	if (flags & 0x01) fprintf(fp, " FIN");
	if (flags & 0x02) fprintf(fp, " SYN");
	if (flags & 0x04) fprintf(fp, " RST");
	if (flags & 0x08) fprintf(fp, " PUSH");
	if (flags & 0x10) fprintf(fp, " ACK");
	if (flags & 0x20) fprintf(fp, " URG");

	fprintf(fp, "\n");
}

static void
fprint_tos(fp, tos)
	FILE *fp;
	int tos;
{
	fprintf(fp, "TOS:      %02x", tos);

	switch (tos & 0xe0) { /* precedence bits */
	case 0xe0: fprintf(fp, " NETCONTROL"); break;
	case 0xc0: fprintf(fp, " INTERNETCONTROL"); break;
	case 0xa0: fprintf(fp, " CRITIC_ECP"); break;
	case 0x80: fprintf(fp, " FLASHOVERRIDE"); break;
	case 0x60: fprintf(fp, " FLASH"); break;
	case 0x40: fprintf(fp, " IMMEDIATE"); break;
	case 0x20: fprintf(fp, " PRIORITY"); break;
	}
	tos &= 0x1e; /* type of service bits */
	if (tos & 0x10) fprintf(fp, " LOWDELAY");
	if (tos & 0x08) fprintf(fp, " THROUGHPUT");
	if (tos & 0x04) fprintf(fp, " RELIABILITY");
	if (tos & 0x02) fprintf(fp, " LOWCOST");

	fprintf(fp, "\n");
}

static void
dump_netflow_v1(dp)
	const CNF_DATA_V1 *dp;
{
	FILE *fp;

	if (!dump_file || (fp = fopen(dump_file, "a")) == 0)
		return;

	fprintf(fp, "\nNetflow:  V1\n");
	fprintf(fp, "SrcAddr:  %s\n", intoa(dp->src_addr));
	fprintf(fp, "DstAddr:  %s\n", intoa(dp->dst_addr));
	fprintf(fp, "NextHop:  %s\n", intoa(dp->nexthop));
	fprintf(fp, "InputIf:  %d\n", (int)ntohs(dp->ifin));
	fprintf(fp, "OutputIf: %d\n", (int)ntohs(dp->ifout));
	fprintf(fp, "Packets:  %u\n", (u_int32_t)ntohl(dp->dpkts));
	fprintf(fp, "Octets:   %u\n", (u_int32_t)ntohl(dp->doctets));
	fprintf(fp, "First:    %u\n", (u_int32_t)ntohl(dp->firsttime));
	fprintf(fp, "Last:     %u\n", (u_int32_t)ntohl(dp->lasttime));
	if (dp->proto == IPPROTO_TCP) {
		fprintf(fp, "SrcPort:  %s\n",  tcpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  tcpport_string(ntohs(dp->dst_port)));
	} else if (dp->proto == IPPROTO_UDP) {
		fprintf(fp, "SrcPort:  %s\n",  udpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  udpport_string(ntohs(dp->dst_port)));
	} else {
		fprintf(fp, "SrcPort:  %d\n",  (int)ntohs(dp->src_port));
		fprintf(fp, "DstPort:  %d\n",  (int)ntohs(dp->dst_port));
	}
	fprintf(fp, "Protocol: %s\n", ipproto_string(dp->proto));
	fprint_tos(fp, dp->tos);
	fprint_tcpflags(fp, dp->flags);

	(void)fclose(fp);
}

static void
dump_netflow_v5(dp)
	const CNF_DATA_V5 *dp;
{
	FILE *fp;

	if (!dump_file || (fp = fopen(dump_file, "a")) == 0)
		return;

	fprintf(fp, "\nNetflow:  V5\n");
	fprintf(fp, "SrcAddr:  %s\n", intoa(dp->src_addr));
	fprintf(fp, "DstAddr:  %s\n", intoa(dp->dst_addr));
	fprintf(fp, "NextHop:  %s\n", intoa(dp->nexthop));
	fprintf(fp, "InputIf:  %d\n", (int)ntohs(dp->ifin));
	fprintf(fp, "OutputIf: %d\n", (int)ntohs(dp->ifout));
	fprintf(fp, "Packets:  %u\n", (u_int32_t)ntohl(dp->dpkts));
	fprintf(fp, "Octets:   %u\n", (u_int32_t)ntohl(dp->doctets));
	fprintf(fp, "First:    %u\n", (u_int32_t)ntohl(dp->firsttime));
	fprintf(fp, "Last:     %u\n", (u_int32_t)ntohl(dp->lasttime));
	if (dp->proto == IPPROTO_TCP) {
		fprintf(fp, "SrcPort:  %s\n",  tcpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  tcpport_string(ntohs(dp->dst_port)));
	} else if (dp->proto == IPPROTO_UDP) {
		fprintf(fp, "SrcPort:  %s\n",  udpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  udpport_string(ntohs(dp->dst_port)));
	} else {
		fprintf(fp, "SrcPort:  %d\n",  (int)ntohs(dp->src_port));
		fprintf(fp, "DstPort:  %d\n",  (int)ntohs(dp->dst_port));
	}
	fprint_tcpflags(fp, dp->flags);
	fprintf(fp, "Protocol: %s\n", ipproto_string(dp->proto));
	fprint_tos(fp, dp->tos);

	fprintf(fp, "SrcASN:   %d\n", (int)ntohs(dp->src_as));
	fprintf(fp, "DstASN:   %d\n", (int)ntohs(dp->dst_as));
	fprintf(fp, "SrcMask:  %d\n", (int)dp->src_mask);
	fprintf(fp, "DstMask:  %d\n", (int)dp->dst_mask);

	(void)fclose(fp);
}

static void
dump_netflow_v7(dp)
	const CNF_DATA_V7 *dp;
{
	FILE *fp;

	if (!dump_file || (fp = fopen(dump_file, "a")) == 0)
		return;

	fprintf(fp, "\nNetflow:  V7\n");
	fprintf(fp, "SrcAddr:  %s\n", intoa(dp->src_addr));
	fprintf(fp, "DstAddr:  %s\n", intoa(dp->dst_addr));
	fprintf(fp, "NextHop:  %s\n", intoa(dp->nexthop));
	fprintf(fp, "InputIf:  %d\n", (int)ntohs(dp->ifin));
	fprintf(fp, "OutputIf: %d\n", (int)ntohs(dp->ifout));
	fprintf(fp, "Packets:  %u\n", (u_int32_t)ntohl(dp->dpkts));
	fprintf(fp, "Octets:   %u\n", (u_int32_t)ntohl(dp->doctets));
	fprintf(fp, "First:    %u\n", (u_int32_t)ntohl(dp->firsttime));
	fprintf(fp, "Last:     %u\n", (u_int32_t)ntohl(dp->lasttime));
	if (dp->proto == IPPROTO_TCP) {
		fprintf(fp, "SrcPort:  %s\n",  tcpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  tcpport_string(ntohs(dp->dst_port)));
	} else if (dp->proto == IPPROTO_UDP) {
		fprintf(fp, "SrcPort:  %s\n",  udpport_string(ntohs(dp->src_port)));
		fprintf(fp, "DstPort:  %s\n",  udpport_string(ntohs(dp->dst_port)));
	} else {
		fprintf(fp, "SrcPort:  %d\n",  (int)ntohs(dp->src_port));
		fprintf(fp, "DstPort:  %d\n",  (int)ntohs(dp->dst_port));
	}
	fprint_tcpflags(fp, dp->flags);
	fprintf(fp, "Protocol: %s\n", ipproto_string(dp->proto));
	fprint_tos(fp, dp->tos);

	fprintf(fp, "SrcASN:   %d\n", (int)ntohl(dp->src_as));
	fprintf(fp, "DstASN:   %d\n", (int)ntohl(dp->dst_as));
	fprintf(fp, "SrcMask:  %d\n", (int)dp->src_mask);
	fprintf(fp, "DstMask:  %d\n", (int)dp->dst_mask);

	fprintf(fp, "RouterSc: %s\n", intoa(dp->router_sc));

	(void)fclose(fp);
}

