/*
	**
	** packet-parse.c
	**
	** Routines to extract data from network packets
	**
	** Copyright 1998 Damien Miller <dmiller@ilogic.com.au>
	**
	** This software is licensed under the terms of the GNU General 
	** Public License (GPL). Please see the file COPYING for details.
	** 
	** $Id: packet-parse.c,v 1.4 1998/11/29 08:53:38 dmiller Exp $
	**
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <net/bpf.h>

#include "packet-parse.h"

#define ERRORBUF_LEN 512

static char rcsid[]="$Id: packet-parse.c,v 1.4 1998/11/29 08:53:38 dmiller Exp $";
static char error_buf[ERRORBUF_LEN];

/* Routines to parse a packet, filling structure pinfo */
/* Returns 1 on success, 0 on error */
int parse_eth(const unsigned char *eth_pkt, unsigned int len, pinfo_t *pinfo);
int parse_ppp(const unsigned char *ppp_pkt, unsigned int len, pinfo_t *pinfo);
int parse_null(const unsigned char *null_pkt, unsigned int len, pinfo_t *pinfo);
int parse_ipv4(const unsigned char *ipv4_pkt, unsigned int len, pinfo_t *pinfo);
int parse_udp(const unsigned char *udp_pkt, unsigned int len, pinfo_t *pinfo);
int parse_tcp(const unsigned char *tcp_pkt, unsigned int len, pinfo_t *pinfo);

int parse_packet(int link_type, const unsigned char *pkt, 
                 unsigned int len, pinfo_t *pinfo)
{
	/* Clear error message buffer */
	error_buf[0] = '\0';

	/* Clear packet information */
	memset(pinfo, 0, sizeof(*pinfo));

	switch(link_type)
	{
		case DLT_EN10MB:
			/* Ethernet 10Mbps */
			return(parse_eth(pkt, len, pinfo));
			break;
			
		case DLT_PPP:
			/* PPP */
			return(parse_ppp(pkt, len, pinfo));
			break;
			
		case DLT_RAW:
			/* Raw IPv4 datagrams */
			return(parse_ipv4(pkt, len, pinfo));
			break;
			
		case DLT_NULL:
			/* Loopback interfaces */
			return(parse_null(pkt, len, pinfo));
			break;

		default:
			snprintf(error_buf, ERRORBUF_LEN, 
			         "Unsupported link type: %04x", link_type);
			return(0);
			break;
	}
}

const char *parse_error(void)
{
	return(error_buf);
}

int parse_eth(const unsigned char *eth_pkt, unsigned int len, pinfo_t *pinfo)
{
	int frame_type;

	/* Check for short packets */
	if (len <= 14)
	{
		snprintf(error_buf, ERRORBUF_LEN, "Ethernet packet is too short.");
		return(0);
	}

	/* Determine frame type and dispatch packet to appropriate handler */
	frame_type = ntohs((int)*(eth_pkt + 12));
	switch (frame_type)
	{
		case 0x0800:
			/* IPv4 */
			return(parse_ipv4(eth_pkt + 14, len - 14, pinfo));
			break;
		default:
			snprintf(error_buf, ERRORBUF_LEN, 
			         "Unsupported ethernet frame type: %04x", frame_type);
			return(0);
			break;
	}
}

int parse_ppp(const unsigned char *ppp_pkt, unsigned int len, pinfo_t *pinfo)
{
	int protocol;

	/* Check for short packets */
	if (len <= 5)
	{
		snprintf(error_buf, ERRORBUF_LEN, "PPP packet is too short.");
		return(0);
	}

	/* Determine protocol and dispatch packet to appropriate handler */
	protocol = ntohs((int)*(ppp_pkt + 3));
	switch (protocol)
	{
		case 0x21:
			/* IPv4 */
			return(parse_ipv4(ppp_pkt + 5, len - 5, pinfo));
			break;
		default:
			snprintf(error_buf, ERRORBUF_LEN, 
			         "Unsupported PPP protocol: %04x", protocol);
			return(0);
			break;
	}
}

int parse_null(const unsigned char *null_pkt, unsigned int len, pinfo_t *pinfo)
{
	unsigned long family;

	/* Check for short packets */
	if (len <= 4)
	{
		snprintf(error_buf, ERRORBUF_LEN, "NULL packet is too short.");
		return(0);
	}

	/* Determine protocol and dispatch packet to appropriate handler */
	family = ntohl(*(unsigned long*)null_pkt);
	switch (family)
	{
		case 0x0800: /* FIXME: is this correct? */
			/* IPv4 */
			return(parse_ipv4(null_pkt + sizeof(family), len - sizeof(family), pinfo));
			break;

		default:
			snprintf(error_buf, ERRORBUF_LEN, 
			         "Unsupported NULL address family: %04x", family);
			return(0);
			break;
	}
}

int parse_ipv4(const unsigned char *ipv4_pkt, unsigned int len, pinfo_t *pinfo)
{
	struct iphdr *ipv4h = (struct iphdr *)ipv4_pkt;
	int ipv4_hdr_length;
	
	/* Check for short packets */
	if (len < sizeof(*ipv4h))
	{
		snprintf(error_buf, ERRORBUF_LEN, "IPv4 packet is too short.");
		return(0);
	}

	if (ipv4h->version != 4)
	{
		snprintf(error_buf, ERRORBUF_LEN, 
		         "Unsupported IP version: 0x%02x", ipv4h->version);
		return(0);
	}

	ipv4_hdr_length = ipv4h->ihl * 4;
	pinfo->header_length += ipv4_hdr_length;
	pinfo->is_fragment = ((ntohs(ipv4h->frag_off) & 0x1FFF) != 0);
	pinfo->total_length = ntohs(ipv4h->tot_len);
	pinfo->src_addr = ntohl(ipv4h->saddr);
	pinfo->dest_addr = ntohl(ipv4h->daddr);
	pinfo->protocol = ipv4h->protocol;
	
	if (!pinfo->is_fragment)
	{
		switch (ipv4h->protocol)
		{
			case IPPROTO_ICMP:
				pinfo->src_port = 0;
				pinfo->dest_port = 0;
				pinfo->is_connection_request = 0;
				return(1);
				break;

			case IPPROTO_TCP:
				return(parse_tcp(ipv4_pkt + ipv4_hdr_length, len - ipv4_hdr_length, pinfo));
				break;

			case IPPROTO_UDP:
				return(parse_udp(ipv4_pkt + ipv4_hdr_length, len - ipv4_hdr_length, pinfo));
				break;

			default:
				snprintf(error_buf, ERRORBUF_LEN, 
				         "Unsupported IPv4 protocol: %04x", ipv4h->protocol);
				return(0);
				break;
		}
	} else
	{
		pinfo->src_port = 0;
		pinfo->dest_port = 0;
		pinfo->is_connection_request = 0;
		return(1);
	}
}

int parse_udp(const unsigned char *udp_pkt, unsigned int len, pinfo_t *pinfo)
{
	struct udphdr *udph = (struct udphdr *)udp_pkt;
		
	/* Check for short packets */
	if (len < sizeof(struct udphdr))
	{
		snprintf(error_buf, ERRORBUF_LEN, "UDP packet is too short.");
		return(0);
	}

	pinfo->src_port = ntohs(udph->source);
	pinfo->dest_port = ntohs(udph->dest);
	pinfo->is_connection_request = 0;
	pinfo->header_length += ntohs(udph->len);
	
	return(1);
}

int parse_tcp(const unsigned char *tcp_pkt, unsigned int len, pinfo_t *pinfo)
{
	struct tcphdr *tcph = (struct tcphdr *)tcp_pkt;
	
	/* Check for short packets */
	if (len < sizeof(struct tcphdr))
	{
		snprintf(error_buf, ERRORBUF_LEN, "TCP packet is too short.");
		return(0);
	}

	pinfo->src_port = ntohs(tcph->source);
	pinfo->dest_port = ntohs(tcph->dest);
	pinfo->is_connection_request = tcph->syn && !tcph->ack;
	pinfo->header_length += (tcph->doff * 4);
	
	return(1);
}

/* Return RCS version */
const char *pp_version(void)
{
	return(rcsid);
}
