/*
 * $Id: pktfilter.c,v 1.12 2009/04/21 05:42:08 c4chris Exp $
 */

#include "os.h"

/*
 * pktfilter.c - filters to count the packets.
 *
 * David A. Curry				Jeffrey C. Mogul
 * Purdue University				Digital Equipment Corporation
 * Engineering Computer Network			Western Research Laboratory
 * 1285 Electrical Engineering Building		250 University Avenue
 * West Lafayette, IN 47907-1285		Palo Alto, CA 94301
 * davy@ecn.purdue.edu				mogul@decwrl.dec.com
 * 
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#if	!defined(ultrix) && !defined(__osf__)
#include <net/if_arp.h>
#endif
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#ifndef LINUX
#include <netinet/ip_var.h>
#include <netinet/udp_var.h>
#endif
#include <netinet/ip_icmp.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#include "nfswatch.h"
#include "externs.h"
#include "screen.h"

#ifdef SUNOS4
#define NND	1
#include <sun/ndio.h>
#endif

#ifdef ultrix
#include "ultrix.map.h"
#include "ipports.h"
#endif

#ifdef	__osf__
	/* or perhaps "#if defined(__osf__) && defined(__alpha)"? */
#include "osf.map.h"
#include "ipports.h"
#endif

#ifdef sgi
#include <rpc/types.h>
#include "sgi.map.h"
#include "ipports.h"
#endif

#ifdef LINUX
#define uh_sport source
#define uh_dport dest
#define uh_ulen len
#define uh_sum check
#define th_sport source
#define th_dport dest
#define th_off doff
#endif

/*
 * Ethernet broadcast address.
 */
static	struct ether_addr ether_broadcast = {
	{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
};

#ifndef SUNOS5
static void pkt_dispatch(char *, int, u_short, struct timeval *);
#endif
static void ip_filter(struct ip *, ipaddrt, ipaddrt, struct timeval *);
static void tcp_filter(struct tcphdr *, u_int, ipaddrt, ipaddrt,
		       struct timeval *);
static void udp_filter(struct udphdr *, ipaddrt, ipaddrt,
		       struct timeval *);
static void icmp_filter(void);

/*
 * pkt_filter_ether - Parse Ethernet header,
 *			pass rest of packet to pkt_dispatch()
 */
void
pkt_filter_ether(char *cp, u_int length, struct timeval *tstamp)
{
	char packet[PACKETSIZE];
	register int bdcst;
	struct ether_header eheader;

	/*
	 * Extract the ethernet header.
	 */
	memcpy(&eheader, cp, sizeof(struct ether_header));
	memcpy(packet, cp + sizeof(struct ether_header),
		length - sizeof(struct ether_header));

	/*
	 * See if it's a broadcast packet.
	 */
#if defined(ultrix) || defined(sgi) || defined(__osf__)
	bdcst = !bcmp((char *) eheader.ether_dhost, (char *) &ether_broadcast,
			sizeof(struct ether_addr));
#else
	bdcst = !bcmp((char *) &eheader.ether_dhost, (char *) &ether_broadcast,
			sizeof(struct ether_addr));
#endif

	/*
	 * Figure out what kind of packet it is, and pass
	 * it off to the appropriate filter.
	 */
	pkt_dispatch(packet, bdcst, eheader.ether_type, tstamp);
}

#ifndef	LLC_SNAP_LSAP
#define LLC_UI		0x3
#define LLC_SNAP_LSAP	0xaa
#endif

/*
 * pkt_filter_fddi - Parse FDDI and LLC headers,
 *			pass rest of packet to pkt_dispatch()
 */
void
pkt_filter_fddi(char *cp, u_int length, struct timeval *tstamp)
{
	char packet[PACKETSIZE];
	register int bdcst;
	struct fddi_header {
	        u_char	fddi_fc;
	        u_char	fddi_dhost[6];
	        u_char	fddi_shost[6];
	} fheader;
	struct llc_header {
		u_char	llc_dsap;
		u_char	llc_ssap;
		u_char	llc_control;
		u_char	llc_org_code[3];
		u_short	llc_ether_type;
	} lheader;
	u_short etype;

#define	FDDI_LLC_LEN (sizeof(struct fddi_header) + sizeof(struct llc_header))

	if (length <= FDDI_LLC_LEN)
	    return;	/* runt */

	/*
	 * Extract the FDDI and LLC headers.
	 */
	(void) bcopy(cp, (char *) &fheader, sizeof(struct fddi_header));
	(void) bcopy(cp + sizeof(struct fddi_header), (char *) &lheader,
			sizeof(struct llc_header));
	(void) bcopy(cp + FDDI_LLC_LEN, (char *) packet,
			(int) (length - FDDI_LLC_LEN));

	/* Should check FDDI frame control ... */

	/*
	 * See if it's a broadcast packet.
	 */
	bdcst = !bcmp((char *) fheader.fddi_dhost, (char *) &ether_broadcast,
			sizeof(struct ether_addr));

	/*
	 * Check LLC encapsulation type, extract Ethernet type if SNAP
	 */
	if ((lheader.llc_dsap == LLC_SNAP_LSAP)
		&& (lheader.llc_ssap == LLC_SNAP_LSAP)
		&& (lheader.llc_control == LLC_UI)
		&& (lheader.llc_org_code[0] == 0)
		&& (lheader.llc_org_code[1] == 0)
		&& (lheader.llc_org_code[2] == 0)) {
	    etype = lheader.llc_ether_type;
	}
	else
	    etype = 0;

	/*
	 * Figure out what kind of packet it is, and pass
	 * it off to the appropriate filter.
	 */
	pkt_dispatch(packet, bdcst, etype, tstamp);
}

#ifdef USE_LINUX
/*
 * A DLT_LINUX_SLL fake link-layer header.
 */
#define SLL_HDR_LEN 16	/* total header length */
#define SLL_ADDRLEN 8	/* length of address field */
#define LINUX_SLL_HOST		0
#define LINUX_SLL_BROADCAST	1
#define LINUX_SLL_MULTICAST	2
#define LINUX_SLL_OTHERHOST	3
#define LINUX_SLL_OUTGOING	4

struct sll_header {
	u_int16_t	sll_pkttype;		/* packet type */
	u_int16_t	sll_hatype;		/* link-layer address type */
	u_int16_t	sll_halen;		/* link-layer address length */
	u_int8_t	sll_addr[SLL_ADDRLEN];	/* link-layer address */
	u_int16_t	sll_protocol;		/* protocol */
};

/*
 * pkt_filter_sll - Parse LINUX_SLL header,
 *			pass rest of packet to pkt_dispatch()
 */
void
pkt_filter_sll(char *cp, u_int length, struct timeval *tstamp)
{
	char packet[PACKETSIZE];
	register int bdcst;
	struct sll_header sheader;

	/*
	 * Extract the SLL header.
	 */
	memcpy(&sheader, cp, sizeof(struct sll_header));
	memcpy(packet, cp + sizeof(struct sll_header),
		length - sizeof(struct sll_header));

	/*
	 * See if it's a broadcast packet.
	 */
	bdcst = ntohs(sheader.sll_pkttype) == LINUX_SLL_BROADCAST;

	/*
	 * Figure out what kind of packet it is, and pass
	 * it off to the appropriate filter.
	 */
	pkt_dispatch(packet, bdcst, sheader.sll_protocol, tstamp);
}
#endif /* USE_LINUX */

/*
 * pkt_dispatch - count a packet, and pass it off to the appropriate filter.
 *		Caller tells us the Ethernet type code and if the packet
 *		was a broadcast.
 */
#ifdef SUNOS5
void
#else
static void
#endif
/* packet: address of IP or ARP header */
/* bdcst: was packet a LAN broadcast? */
/* etype: still in network byte-order */
pkt_dispatch(char *packet, int bdcst,
	     u_short etype, struct timeval *tstamp)
{
	struct ip *ip;
	struct ether_arp *arp;
	int sender, target;
	register int want;

	/*
	 * Count this packet in the network totals.
	 */
	int_pkt_total++;
	pkt_total++;

	/*
	 * See if it's a broadcast packet, and count it if it is.
	 */
	if (bdcst) {
		pkt_counters[PKT_BROADCAST].pc_interval++;
		pkt_counters[PKT_BROADCAST].pc_total++;
	}

	/*
	 * Figure out what kind of packet it is, and pass
	 * it off to the appropriate filter.
	 */
	switch (ntohs(etype)) {
	case ETHERTYPE_IP:		/* IP packet			*/
		ip = (struct ip *) packet;
		want = want_packet(ip->ip_src.s_addr, ip->ip_dst.s_addr);

		/*
		 * If we want this packet, count it in the host
		 * totals and pass it off.
		 */
		if (bdcst || want) {
			int_dst_pkt_total++;
			dst_pkt_total++;

			ip_filter(ip, ip->ip_src.s_addr,
				ip->ip_dst.s_addr, tstamp);
		}

		break;
	case ETHERTYPE_ARP:		/* Address Resolution Protocol	*/
		arp = (struct ether_arp *) packet;
		sender = (arp->arp_spa[0] << 24) +
			 (arp->arp_spa[1] << 16) +
			 (arp->arp_spa[2] << 8) +
			 arp->arp_spa[3];
		target = (arp->arp_tpa[0] << 24) +
			 (arp->arp_tpa[1] << 16) +
			 (arp->arp_tpa[2] << 8) +
			 arp->arp_tpa[3];
		want = want_packet(sender, target);

		/*
		 * If we want this packet, count it in the host
		 * totals and then count it in the packet
		 * type counters.
		 */
		if (bdcst || want) {
			int_dst_pkt_total++;
			dst_pkt_total++;

			pkt_counters[PKT_ARP].pc_interval++;
			pkt_counters[PKT_ARP].pc_total++;
		}

		break;
	case ETHERTYPE_REVARP:		/* Reverse Addr Resol Protocol	*/
		arp = (struct ether_arp *) packet;
		sender = (arp->arp_spa[0] << 24) +
			 (arp->arp_spa[1] << 16) +
			 (arp->arp_spa[2] << 8) +
			 arp->arp_spa[3];
		target = (arp->arp_tpa[0] << 24) +
			 (arp->arp_tpa[1] << 16) +
			 (arp->arp_tpa[2] << 8) +
			 arp->arp_tpa[3];
		want = want_packet(sender, target);

		/*
		 * If we want this packet, count it in the host
		 * totals and then count it in the packet
		 * type counters.
		 */
		if (bdcst || want) {
			int_dst_pkt_total++;
			dst_pkt_total++;

			pkt_counters[PKT_RARP].pc_interval++;
			pkt_counters[PKT_RARP].pc_total++;
		}

		break;
#ifdef notdef
	case ETHERTYPE_PUP:		/* Xerox PUP			*/
#endif
	default:			/* who knows...			*/
		int_dst_pkt_total++;
		dst_pkt_total++;

		pkt_counters[PKT_OTHER].pc_interval++;
		pkt_counters[PKT_OTHER].pc_total++;
		break;
	}
}

/*
 * ip_filter - strip off the IP header and pass off to the appropriate
 *	       filter.
 */
static void
ip_filter(struct ip *ip, ipaddrt src, ipaddrt dst,
	  struct timeval *tstamp)
{
	register int *data;
	register int datalength;

	data = (int *) ip;
	data += ip->ip_hl;
	datalength = ntohs(ip->ip_len) - (4 * ip->ip_hl);

	/*
	 * Figure out what kind of IP packet this is, and
	 * pass it off to the appropriate filter.
	 */
	switch (ip->ip_p) {
	case IPPROTO_TCP:		/* transmission control protocol*/
		tcp_filter((struct tcphdr *) data, datalength,
			src, dst, tstamp);
		break;
	case IPPROTO_UDP:		/* user datagram protocol	*/
		udp_filter((struct udphdr *) data,
			src, dst, tstamp);
		break;
	case IPPROTO_ICMP:		/* control message protocol	*/
		icmp_filter();
		break;
#ifdef notdef
	case IPPROTO_IGMP:		/* group message protocol	*/
	case IPPROTO_GGP:		/* gateway-gateway protocol	*/
	case IPPROTO_EGP:		/* exterior gateway protocol	*/
	case IPPROTO_PUP:		/* Xerox pup protocol		*/
	case IPPROTO_IDP:		/* XNS IDP			*/
#endif
	default:			/* who knows...			*/
		break;
	}
}

/* We should get this from the services database...  */
#define NFS_PORT 2049

/*
 * tcp_filter - count TCP packets, pass RPC packets to the RPC filter.
 */
static void
tcp_filter(struct tcphdr *tcp, u_int length, ipaddrt src, ipaddrt dst,
	   struct timeval *tstamp)
{
	unsigned short source = ntohs(tcp->th_sport);
	unsigned short dest = ntohs(tcp->th_dport);
	unsigned int hdr_len = tcp->th_off * 4;
	/*
	 * Count as a TCP packet.
	 */
	pkt_counters[PKT_TCP].pc_interval++;
	pkt_counters[PKT_TCP].pc_total++;
	hdr_len += 4; /* For some reason...  */
	if (length < hdr_len)
	  return;
	if (source == NFS_PORT
	    || dest == NFS_PORT) {
		rpc_filter((char *) tcp + hdr_len, length - hdr_len,
			src, dst, tstamp);
		return;
	}
	if (rpcPortCnt > 0) {
	  unsigned int i;
	  for (i = 0; i < rpcPortCnt; i++) {
	    if (dest == rpcPort[i]) {
	      rpc_filter((char *) tcp + hdr_len, length - hdr_len,
			 src, dst, tstamp);
	      return;
	    }
	  }
	}
}

/*
 * udp_filter - count UDP packets, pass RPC packets to the RPC filter.
 */
static void
udp_filter(struct udphdr *udp, ipaddrt src, ipaddrt dst,
	   struct timeval *tstamp)
{
	unsigned short source = ntohs(udp->uh_sport);
	unsigned short dest = ntohs(udp->uh_dport);
	/*
	 * Count as a UDP packet.
	 */
	pkt_counters[PKT_UDP].pc_interval++;
	pkt_counters[PKT_UDP].pc_total++;
	if (source == IPPORT_ROUTESERVER
	    || dest == IPPORT_ROUTESERVER) {
		pkt_counters[PKT_ROUTING].pc_interval++;
		pkt_counters[PKT_ROUTING].pc_total++;
		return;
	}
	if (source == NFS_PORT
	    || dest == NFS_PORT) {
		rpc_filter((char *) udp + sizeof(struct udphdr),
			ntohs(udp->uh_ulen) - sizeof(struct udphdr),
			src, dst, tstamp);
		return;
	}
	if (rpcPortCnt > 0) {
	  unsigned int i;
	  for (i = 0; i < rpcPortCnt; i++) {
	    if (dest == rpcPort[i]) {
	      rpc_filter((char *) udp + sizeof(struct udphdr),
			 ntohs(udp->uh_ulen) - sizeof(struct udphdr),
			 src, dst, tstamp);
	      return;
	    }
	  }
	}
}

/*
 * icmp_filter - count ICMP packets.
 */
static void
icmp_filter(void)
{
	pkt_counters[PKT_ICMP].pc_interval++;
	pkt_counters[PKT_ICMP].pc_total++;
}
