// $Id: Net.cc,v 1.41 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <sys/types.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#include "NetVar.h"
#include "Sessions.h"
#include "Event.h"
#include "Timer.h"
#include "Var.h"
#include "Logger.h"
#include "Net.h"
#include "TCP_Rewriter.h"
#include "Anon.h"
#include "PacketSort.h"
#include "Serializer.h"

extern "C" {
#include "setsignal.h"
};

extern "C" {
extern int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
}

PList(PktSrc) pkt_srcs;

pcap_dumper_t* pkt_dump = 0;
PacketDumper* transformed_pkt_dump = 0;
int transformed_pkt_dump_MTU = 1514;

int reading_live = 0;	// 1 if reading from network interface, 0 if save file
char* user_pcap_filter = 0;

double network_time = 0.0;	// time according to last packet timestamp
double processing_start_time = 0.0;	// time started working on current pkt
double last_watchdog_proc_time = 0.0;	// value of above during last watchdog
static double next_packet_time = 0.0;	// earliest time next packet may arrive
const double max_packet_time = 1e30;
#define sort_packets()	(packet_sort_window > 0)

static const struct pcap_pkthdr* current_hdr = 0;
static const u_char* current_pkt = 0;
static int current_dispatched = 0;

static int do_checkpoint = 0; // set by signal handler to trigger state checkpointing

RETSIGTYPE watchdog(int /* signo */)
	{
	if ( processing_start_time != 0.0 )
		{
		// The signal arrived while we're processing a packet and/or
		// its corresponding event queue.  Check whether we've been
		// spending too much time, which we take to mean we've wedged.

		// Note that it's subtle how exactly to test this.  In
		// processing_start_time we have the timestamp of the packet
		// we're currently working on.  But that *doesn't* mean that
		// we began work on the packet at that time; we could have
		// begun at a much later time, depending on how long the
		// packet filter waited (to fill its buffer) before handing
		// up this packet.  So what we require is that the current
		// processing_start_time matches the processing_start_time we
		// observed last time the watchdog went off.  If so, then
		// we've been working on the current packet for at least
		// watchdog_interval seconds.

		if ( processing_start_time == last_watchdog_proc_time )
			{
			char msg[512];
			sprintf(msg, "**watchdog timer expired, t = %.06f, start = %.06f, dispatched = %d", current_time(), processing_start_time, current_dispatched);
			bro_logger->Log(msg);
			run_time("watchdog timer expired");

			if ( current_hdr )
				pcap_dump((u_char*) pkt_dump, current_hdr, current_pkt);

			net_get_final_stats();
			net_finish(0);

			abort();
			exit(1);
			}
		}

	last_watchdog_proc_time = processing_start_time;

	(void) alarm(watchdog_interval);
	return RETSIGVAL;
	}

RETSIGTYPE checkpoint_signal(int signo)
	{
	do_checkpoint = 1;
	return RETSIGVAL;
	}

void net_init(name_list& interfaces, name_list& readfiles,
	        const char* writefile, const char* transformed_writefile,
	        const char* filter, int do_watchdog)
	{
	init_net_var();

	if ( readfiles.length() > 0 )
		{
		reading_live = 0;

		for ( int i = 0; i < readfiles.length(); ++i )
			{
			PktFileSrc* ps = new PktFileSrc(readfiles[i], filter);

			if ( ps->NotOpen() )
				{
				fprintf(stderr, "%s: problem with trace file %s - %s\n",
					prog, readfiles[i], ps->ErrorMsg());
				exit(1);
				}
			else
				pkt_srcs.append(ps);
			}
		}
	else
		{
		reading_live = 1;

		for ( int i = 0; i < interfaces.length(); ++i )
			{
			PktInterfaceSrc* ps =
				new PktInterfaceSrc(interfaces[i], filter);

			if ( ps->NotOpen() )
				{
				fprintf(stderr, "%s: problem with interface %s - %s\n",
					prog, interfaces[i], ps->ErrorMsg());
				exit(1);
				}
			else
				pkt_srcs.append(ps);
			}
		}

	if ( writefile )
		{
		// ### This will fail horribly if there are multiple
		// interfaces with different-lengthed media.
		pkt_dump = pkt_srcs[0]->OpenDumper(writefile);
		if ( ! pkt_dump )
			{
			fprintf(stderr, "%s: can't open write file \"%s\" - %s\n",
				prog, writefile, pkt_srcs[0]->ErrorMsg());
			exit(1);
			}
		}

	if ( transformed_writefile )
		{
		pcap_dumper_t* dumper = pkt_srcs[0]->OpenDumper(transformed_writefile);
		if ( ! dumper )
			{
			fprintf(stderr, "%s: can't open trace transformation write file \"%s\" - %s\n",
				prog, writefile, pkt_srcs[0]->ErrorMsg());
			exit(1);
			}

		transformed_pkt_dump = new PacketDumper(dumper);
		}

#if 0
	else if ( pkt_dump )
		transformed_pkt_dump = new PacketDumper(pkt_dump);
#endif

	if ( anonymize_ip_addr )
		init_ip_addr_anonymizers();
	else
		for ( int i = 0; i < NUM_ADDR_ANONYMIZATION_METHODS; ++i )
			ip_anonymizer[i] = 0;

	packet_sorter = new PacketSortGlobalPQ();

	sessions = new NetSessions();

	(void) setsignal(SIGHUP, checkpoint_signal);

	if ( do_watchdog )
		{
		// Set up the watchdog to make sure we don't wedge.
		(void) setsignal(SIGALRM, watchdog);
		(void) alarm(watchdog_interval);
		}
	}

void extract_next_packet(PktSrc* soonest_ps, double soonest,
			 struct pcap_pkthdr*& hdr,
			 const u_char*& pkt,
			 int& hdr_size,
			 double& ts)
	{
	pkt = soonest_ps->NextPacket(hdr);
	hdr_size = soonest_ps->HdrSize();
	ts = soonest;

	if ( ! pkt )
		internal_error("dispatch packet missing");

	soonest_ps->ConsumePacket();

	next_packet_time = soonest;
	}

int dispatch_next_packet()
	{
	int has_packet_to_dispatch = 0;
	const struct pcap_pkthdr* hdr;
	const u_char* pkt;
	int hdr_size;
	double ts;
	PacketSortElement* packet_to_dispatch = 0;

	// Extract packets from sources till a packet can be
	// dispatched or all packets are exhausted.
	do {
		// Look for the packet source with the earliest timestamp
		// and dispatch it.  We assume here that there are few
		// sources, so that brute force across them is likely as or
		// more efficient than a fancier approach such as a priority
		// queue.
		double soonest = 0.0;
		PktSrc* soonest_ps = 0;

		loop_over_list(pkt_srcs, i)
			{
			PktSrc* ps = pkt_srcs[i];
			double t = ps->NextTimestamp();

			if ( t >= 0.0 )
				{
				if ( ! soonest_ps || t < soonest )
					{
					soonest_ps = ps;
					soonest = t;
					}
				}

			else
				{
				// Source has gone dry.  If it's a network interface,
				// this just means it's timed out.  If it's a file,
				// though, then the file has been exhausted, and
				// we should delete it as a packet source.
				if ( ! ps->IsLive() )
					{
					delete ps;
					pkt_srcs.remove_nth(i);
					--i;	// because it's about to be incremented
					}
				}
			}

		// Check if there are events received earlier from an
		// external source than our soonest packet. If so,
		// queue it and advance the time.
		if ( external_event_sources.length() > 0 )
			{
			EventSource* soonest_external_src = 0;
			int remove_src = -1;

			loop_over_list(external_event_sources, i)
				{
				EventSource* s = external_event_sources[i];
				if ( s->NextTimestamp() &&
				     (! soonest || s->NextTimestamp() < soonest) )
					{
					soonest_external_src = s;
					soonest = s->NextTimestamp();
					}
				else
					{
					// For simplicity, we remove at most
					// one inactive src.  (We cannot modify
					// the list during iteration.)
					if ( ! s->IsActive() )
						remove_src = i;
					}
				}

			if ( remove_src >= 0 )
				external_event_sources.remove_nth(remove_src);

			if ( soonest_external_src )
				{
				network_time = soonest;
				soonest_external_src->DispatchNextEvent();
				current_dispatched =
					timer_mgr->Advance(network_time,
							   max_timer_expires);
				return 1;
				}
			}

		if ( soonest_ps )
			next_packet_time = soonest;
		else if ( pkt_srcs.length() == 0 )
			next_packet_time = max_packet_time;

		if ( sort_packets() )
			{
			packet_to_dispatch = packet_sorter->RemoveMin(next_packet_time - packet_sort_window);
			if ( packet_to_dispatch )
				{
				has_packet_to_dispatch = 1;
				hdr = packet_to_dispatch->Hdr();
				pkt = packet_to_dispatch->Pkt();
				hdr_size = packet_to_dispatch->HdrSize();
				ts = packet_to_dispatch->TimeStamp();
				}
			}
		else	// skip packet sort
			{
			if ( soonest_ps )
				{
				has_packet_to_dispatch = 1;
				struct pcap_pkthdr* next_hdr;
				extract_next_packet(soonest_ps, soonest, next_hdr, pkt, hdr_size, ts);
				hdr = next_hdr;
				}
			}

		if ( ! has_packet_to_dispatch )
			{
			if ( soonest_ps )
				{
				struct pcap_pkthdr* next_hdr;
				extract_next_packet(soonest_ps, soonest, next_hdr, pkt, hdr_size, ts);
				ASSERT(sort_packets());
				packet_sorter->Add(
					new PacketSortElement(soonest, next_hdr, pkt, hdr_size));
				}
			else
				{
				if ( pkt_srcs.length() == 0 && packet_sorter->Empty() )
					// We've run out of sources.
					return 0;

				ASSERT(reading_live);
				if ( reading_live )
					{
					// Take advantage of the lull to get up
					// to date on timers.
					timer_mgr->Advance(current_time(),
							   max_timer_expires);
					next_packet_time = current_time();
					return 1;
					}
				}
			}
	} while ( ! has_packet_to_dispatch );

	// now dispatch the packet
	if ( has_packet_to_dispatch )
		{
		current_hdr = hdr;
		current_pkt = pkt;

		// # NOTE: network_time is no longer monotonic!
		if ( ts < network_time )
			DEBUG_MSG("%.6f: packet %.6f delivered after packet %.6f\n",
				  next_packet_time, ts, network_time);

		processing_start_time = network_time = ts;

		current_dispatched =
			timer_mgr->Advance(network_time, max_timer_expires);

		sessions->NextPacket(network_time, hdr, pkt, hdr_size, packet_to_dispatch);

		if ( packet_to_dispatch )
			delete packet_to_dispatch;

		current_hdr = 0;	// done with these
		current_pkt = 0;

		mgr.Drain();

		extern void termination_signal();
		if ( signal_val == SIGTERM || signal_val == SIGINT )
			// We received a signal while processing the
			// current packet and its related events.
			termination_signal();

		processing_start_time = 0.0;	// = "we're not processing now"
		current_dispatched = 0;
		}

	if ( signal_val )
		{
		if ( bro_signal )
			{
			val_list* args = new val_list;
			args->append(new Val(signal_val, TYPE_COUNT));
			signal_val = 0;

			// Don't queue the event - deliver it
			// immediately.
			bro_signal->Call(args);
			delete args;
			}

		else
			{
			Val sval(signal_val, TYPE_COUNT);
			run_time("exiting due to signal %s", &sval);
			return 0;
			}
		}

	return 1;
	}

void net_run()
	{
	while ( dispatch_next_packet() )
		;

	// Get the final statistics now, and not when net_finish() is
	// called, since that might happen quite a bit in the future
	// due to expiring pending timers, and we don't want to ding
	// for any packets dropped beyond this point.
	net_get_final_stats();
	}

void net_get_final_stats()
	{
	loop_over_list(pkt_srcs, i)
		{
		PktSrc* ps = pkt_srcs[i];

		if ( ps->IsLive() )
			{
			struct pcap_stat s;

			if ( pcap_stats(ps->PcapHandle(), &s) < 0 )
				fprintf(stderr, "%s stats: %s\n",
						prog, ps->ErrorMsg());
			else
				fprintf(stderr, "%d packets received on interface %s, %d dropped\n",
						s.ps_recv, ps->Interface(), s.ps_drop);
			}
		}
	}

void net_finish(int drain_events)
	{
	if ( drain_events )
		{
		if ( sessions )
			sessions->Drain();

		mgr.Drain();

		if ( sessions )
			sessions->Done();
		}

	if ( pkt_dump )
		pcap_dump_close(pkt_dump);
	}

void net_delete()
	{
	delete sessions;
	delete packet_sorter;

	// Can't put this in net_finish() because packets might be
	// dumped when connections are deleted.
	if ( transformed_pkt_dump )
		delete transformed_pkt_dump;

	for ( int i = 0; i < NUM_ADDR_ANONYMIZATION_METHODS; ++i )
		delete ip_anonymizer[i];
	}
