/*

Just takes a tcpdump trace file and dumps information about the different
protocols in each packet.

elparis@cisco.com

$Id: tcp-md5.c,v 1.2 2003/02/10 14:46:34 peloy Exp $

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <pcap.h>

#include "tcp-md5.h"

#define TCPOPT_SIGNATURE 19

struct bgphdr {
    u_int8_t marker[16];
    u_int16_t length;
    u_int8_t type;
};

char *bgp_messages[] = {
	NULL,
	"Open",
	"Update",
	"Notification",
	"Keep-alive"
};

void dump_tcp(struct iphdr *ip);
void dump_udp(struct iphdr *ip);
void dump_icmp(struct iphdr *ip);
void dump_ip(struct iphdr *ip);
void dump_bgp(struct tcphdr *tcp);
void usage(char *progname);
void got_packet(u_char *args, const struct pcap_pkthdr *header,
		const u_char *packet);

char *program_name;

int packet_counter;
int dump_packet;
int tflag = 1;			/* print packet arrival time */
int32_t thiszone;		/* seconds offset from gmt to local time */

int main(int argc, char **argv)
{
	int c;
	char dumpfile[512];
	int have_fname = 0;
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t *pcap;
	char *pcap_expression;
	int optimize = 1; /* Optimize compilation of pcap expression? */
	bpf_u_int32 netmask = 0; /* for pcap_compile() */
	struct bpf_program fcode;
	char *cp;

	if ((cp = strrchr(argv[0], '/')) != NULL)
		program_name = cp + 1;
	else
		program_name = argv[0];

	while ( (c = getopt(argc, argv, "f:d") ) != -1)
		switch (c) {
			case 'f':
				strncpy(dumpfile, optarg, sizeof(dumpfile) );
				have_fname = 1;
				break;
			case 'd':
				dump_packet = 1;
				break;
			default:
				usage(argv[0]);
		}

	if (!have_fname)
		usage(argv[0]);

	thiszone = gmt2local(0);

	if ( (pcap = pcap_open_offline(dumpfile, errbuf) ) == NULL)
		error("%s", errbuf);

	printf("libcap version: %d.%d\n\n", pcap_major_version(pcap),
		pcap_minor_version(pcap) );

	pcap_expression = copy_argv(&argv[optind]);

	if (pcap_compile(pcap, &fcode, pcap_expression, optimize, netmask) < 0)
		error("%s", pcap_geterr(pcap) );

	if (pcap_setfilter(pcap, &fcode) < 0)
		error("%s", pcap_geterr(pcap) );

	/* We'll use this inside our callback function */
	packet_counter = 1;

	pcap_loop(pcap, -1, got_packet, NULL);

	pcap_close(pcap);

	return 0;
}

void usage(char *progname)
{
	fprintf(stderr, "Usage: %s -f <capture file> [-d]\n", progname);
	exit(1);
}

void dump_ip(struct iphdr *ip)
{
	char src_ip[20], dest_ip[20];

	/* Get source and destination addresses */
	strcpy(src_ip, inet_ntoa( *(struct in_addr *) &ip->saddr) );
	strcpy(dest_ip, inet_ntoa( *(struct in_addr *) &ip->daddr) );

	printf("%s -> %s\n", src_ip, dest_ip);
	printf("Length of IP data: %d\n", ntohs(ip->tot_len) - ip->ihl*4);
}

void dump_icmp(struct iphdr *ip)
{
	printf("IP protocol is ICMP\n");
}

void dump_udp(struct iphdr *ip)
{
	struct udphdr *udp;

	printf("IP protocol is UDP\n");

	udp = (struct udphdr *) ( (char *) ip + sizeof(struct iphdr) );

	printf("Source port: %d, destination port %d\n",
				ntohs(udp->source), ntohs(udp->dest) );
}

void dump_tcp(struct iphdr *ip)
{
	struct tcphdr *tcp;
	u_char *options;
	int opt, optlen, optnum;
	int i;
	int size_of_tcp_data;

	printf("IP protocol is TCP\n");

	tcp = (struct tcphdr *) ( (char *) ip + ip->ihl*4);

	printf("Source port: %d, destination port %d\n",
				ntohs(tcp->source), ntohs(tcp->dest) );

	size_of_tcp_data = ntohs(ip->tot_len) - ip->ihl*4 - tcp->doff*4;
	printf("Size of TCP data: %d\n", size_of_tcp_data);

	printf("TCP flags: ");
    if (tcp->syn) printf("SYN ");
    if (tcp->ack) printf("ACK ");
    if (tcp->urg) printf("URG ");
    if (tcp->psh) printf("PSH ");
    if (tcp->rst) printf("RST ");
    if (tcp->fin) printf("FIN ");
	printf("\n");

	if (tcp->doff*4 != 20) {
		printf("This packet has %d bytes of TCP options\n",
			tcp->doff*4 - sizeof(struct tcphdr) );

		for (optnum = 0, optlen = 0,
			 options = (u_char *) tcp + sizeof(struct tcphdr);
			 (opt = *options); options += optlen, optnum++) {

			optlen = opt == 1 ? 1 : options[1];

			printf("Option %d (%d bytes): ", optnum, optlen);

			switch (opt) {
				case TCPOPT_NOP:
					printf("No-operation (0x%02x)\n", opt);
					break;

				case TCPOPT_MAXSEG:
					printf("Maximum Segment Size (0x%02x). MSS = %d\n",
						opt, htons(*(u_int16_t *) &options[2]) );
					break;

				case TCPOPT_SIGNATURE:
					printf("TCP MD5 signature (0x%02x)\nMD5 hash: ", opt);
					for (i = 0; i < optlen - 2; i++)	
						printf("%02x", options[i + 2]);
					printf("\n");
					break;

				default:
					printf("Unknown TCP option 0x%02x\n", *options);
			}
		}
	}

	if (size_of_tcp_data &&
			(ntohs(tcp->dest) == 179 || ntohs(tcp->source) == 179) )
		dump_bgp(tcp);
}

void dump_bgp(struct tcphdr *tcp)
{
	struct bgphdr *bgp;
	u_int8_t type;

	printf("TCP port is BGP\n");

	bgp = (struct bgphdr *) ( (char *) tcp + tcp->doff*4);

	type = ntohs(bgp->type);

	if (type == 0 || type > 4)
		printf("Invalid BGP message type (%d)\n", type);
	else
		printf("Length of BGP message: %d, message type: %s (%d)\n",
			ntohs(bgp->length), bgp_messages[type], type);
}

void got_packet(u_char *args, const struct pcap_pkthdr *header,
		const u_char *packet)
{
	struct ether_header *ether;
	struct iphdr *ip;

	if (header->caplen != header->len) {
		printf("%d != %d!!! Don't have complete packet. Skipping.\n",
			header->caplen, header->len);
		goto exit;
	}

	ether = (struct ether_header *) packet;

	if (ntohs(ether->ether_type) != ETHERTYPE_IP) {
		printf("Packet %d: protocol is not IP. Skipping.\n", packet_counter);
		goto exit;
	}

	ip = (struct iphdr *) (packet + sizeof(struct ether_header) );

	printf("Packet %d, ", packet_counter);
	Ts_print(&header->ts);
	printf("\n\n");

	dump_ip(ip);

	switch (ip->protocol) {
		case IPPROTO_ICMP:
			dump_icmp(ip);
			break;

		case IPPROTO_TCP:
			dump_tcp(ip);
			break;

		case IPPROTO_UDP:
			dump_udp(ip);
			break;

		default:
			printf("Unknown IP protocol %d.\n", ip->protocol);
	}

exit:
	printf("--------------------------------------------------\n");

	packet_counter++;
}
