// $Id: ICMP.cc,v 1.16 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 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 "Net.h"
#include "NetVar.h"
#include "Event.h"
#include "ICMP.h"

ICMP_Connection::ICMP_Connection(NetSessions* s, HashKey* k, double t,
		const ConnID* id, const struct icmp* /* icmpp */)
: Connection(s, k, t, id)
	{
	icmp_conn_val = 0;
	}

void ICMP_Connection::Done()
	{
	FinishEndpointMatcher();
	finished = 1;
	}

void ICMP_Connection::NextPacket(double t, int /* is_orig */,
			const IP_Hdr* ip, int len, int caplen,
			const u_char*& data,
			int& /* record_packet */, int& /* record_content */,
			const struct pcap_pkthdr* /* hdr */,
			const u_char* const /* pkt */,
			int /* hdr_size */)
	{
	const struct icmp* icmpp = (const struct icmp*) data;

	if ( ! ignore_checksums && caplen >= len &&
	     icmp_checksum(icmpp, len) != 0xffff )
		{
		Weird("bad_ICMP_checksum");
		return;
		}

	last_time = t;

	type = icmpp->icmp_type;
	code = icmpp->icmp_code;

	// Move past common portion of ICMP header.
	data += 8;
	caplen -= 8;
	len -= 8;

	NextICMP(t, icmpp, len, caplen, data);

	if ( rule_matcher )
		{
		if ( ! orig_match_state )
			InitEndpointMatcher(ip, caplen, 1);
		else
			ClearMatchState(1);	 // don't match stream-wise

		Match(Rule::PAYLOAD, data, len, false, false, 1);
		}
	}

void ICMP_Connection::NextICMP(double /* t */, const struct icmp* /* icmpp */,
				int /* len */, int /* caplen */,
				const u_char*& /* data */)
	{
	ICMPEvent(icmp_sent);
	}

void ICMP_Connection::ICMPEvent(EventHandlerPtr f)
	{
	if ( ! f )
		return;

	val_list* vl = new val_list;
	vl->append(BuildICMPVal());

	mgr.QueueEvent(f, vl);
	}

RecordVal* ICMP_Connection::BuildICMPVal()
	{
	if ( ! icmp_conn_val )
		{
		icmp_conn_val = new RecordVal(icmp_conn);

		icmp_conn_val->Assign(0, new AddrVal(orig_addr));
		icmp_conn_val->Assign(1, new AddrVal(resp_addr));
		icmp_conn_val->Assign(2, new Val(type, TYPE_COUNT));
		icmp_conn_val->Assign(3, new Val(code, TYPE_COUNT));
		}

	Ref(icmp_conn_val);

	return icmp_conn_val;
	}

int ICMP_Connection::IsReuse(double /* t */, const u_char* /* pkt */)
	{
	return 0;
	}

void ICMP_Connection::Describe(ODesc* d) const
	{
	d->Add(start_time);
	d->Add("(");
	d->Add(last_time);
	d->AddSP(")");

	d->Add(dotted_addr(orig_addr));
	d->Add(".");
	d->Add(type);
	d->Add(".");
	d->Add(code);

	d->SP();
	d->AddSP("->");

	d->Add(dotted_addr(resp_addr));
	}

void ICMP_Connection::UpdateEndpointVal(RecordVal* /* endp */, int /* is_orig */)
	{
	EnableStatusUpdateTimer();
	}


ICMP_Echo::ICMP_Echo(NetSessions* s, HashKey* k, double t, const ConnID* id,
			const struct icmp* icmpp)
: ICMP_Connection(s, k, t, id, icmpp)
	{
	}

void ICMP_Echo::NextICMP(double /* t */, const struct icmp* icmpp,
			int /* len */, int caplen, const u_char*& data)
	{
	EventHandlerPtr f = type == ICMP_ECHO ? icmp_echo_request : icmp_echo_reply;
	if ( ! f )
		return;

	int id = ntohs(icmpp->icmp_hun.ih_idseq.icd_id);
	int seq = ntohs(icmpp->icmp_hun.ih_idseq.icd_seq);

	BroString* payload = new BroString(data, caplen, 0);

	val_list* vl = new val_list;
	vl->append(BuildICMPVal());
	vl->append(new Val(id, TYPE_COUNT));
	vl->append(new Val(seq, TYPE_COUNT));
	vl->append(new StringVal(payload));

	mgr.QueueEvent(f, vl);
	}


ICMP_Unreach::ICMP_Unreach(NetSessions* s, HashKey* k, double t,
			const ConnID* id, const struct icmp* icmpp)
: ICMP_Connection(s, k, t, id, icmpp)
	{
	}

void ICMP_Unreach::NextICMP(double /* t */, const struct icmp* /* icmpp */,
			int /* len */, int caplen, const u_char*& data)
	{
	val_list* vl = new val_list;
	vl->append(BuildICMPVal());
	vl->append(new Val(code, TYPE_COUNT));

	const struct ip* ip = (const struct ip *) data;
	uint32 ip_hdr_len = ip->ip_hl * 4;

	uint32 ip_len, frag_offset;
	int proto;
	int DF, MF, bad_hdr_len, bad_checksum;
	uint32 src_addr, dst_addr;
	uint32 src_port, dst_port;

	if ( ip_hdr_len < sizeof(struct ip) || ip_hdr_len > uint32(caplen) )
		{ // We don't have an entire IP header.
		bad_hdr_len = 1;
		ip_len = frag_offset = 0;
		proto = 0;
		DF = MF = bad_checksum = 0;
		src_addr = dst_addr = 0;
		src_port = dst_port = 0;
		}

	else
		{
		bad_hdr_len = 0;
		ip_len = ntohs(ip->ip_len);
		bad_checksum = ones_complement_checksum((void*) ip, ip_hdr_len, 0) != 0xffff;

		src_addr = uint32(ip->ip_src.s_addr);
		dst_addr = uint32(ip->ip_dst.s_addr);

		proto = ip->ip_p;
		uint32 frag_field = ntohs(ip->ip_off);
		DF = frag_field & 0x4000;
		MF = frag_field & 0x2000;
		frag_offset = frag_field & /* IP_OFFMASK not portable */ 0x1fff;
		const u_char* transport_hdr = ((u_char *) ip + ip_hdr_len);

		if ( uint32(caplen) < ip_hdr_len + 4 )
			{
			// 4 above is the magic number meaning that both
			// port numbers are included in the ICMP.
			bad_hdr_len = 1;
			src_port = dst_port = 0;
			}

		if ( proto == IPPROTO_TCP )
			{
			const struct tcphdr* tp =
				(const struct tcphdr *) transport_hdr;
			src_port = ntohs(tp->th_sport);
			dst_port = ntohs(tp->th_dport);
			}

		else if ( proto == IPPROTO_UDP )
			{
			const struct udphdr* up =
				(const struct udphdr *) transport_hdr;
			src_port = ntohs(up->uh_sport);
			dst_port = ntohs(up->uh_dport);
			}

		else
			src_port = dst_port = 0;
		}

	RecordVal* iprec = new RecordVal(icmp_context);
	RecordVal* id_val = new RecordVal(conn_id);

	id_val->Assign(0, new AddrVal(src_addr));
	id_val->Assign(1, new PortVal(src_port, proto == IPPROTO_TCP));
	id_val->Assign(2, new AddrVal(dst_addr));
	id_val->Assign(3, new PortVal(dst_port, proto == IPPROTO_TCP));
	iprec->Assign(0, id_val);

	iprec->Assign(1, new Val(ip_len, TYPE_COUNT));
	iprec->Assign(2, new Val(proto, TYPE_COUNT));
	iprec->Assign(3, new Val(frag_offset, TYPE_COUNT));
	iprec->Assign(4, new Val(bad_hdr_len, TYPE_BOOL));
	iprec->Assign(5, new Val(bad_checksum, TYPE_BOOL));
	iprec->Assign(6, new Val(MF, TYPE_BOOL));
	iprec->Assign(7, new Val(DF, TYPE_BOOL));

	vl->append(iprec);

	mgr.QueueEvent(icmp_unreachable, vl);
	}
