// $Id: Frag.cc,v 1.18 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1998, 1999, 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 "util.h"
#include "Hash.h"
#include "Frag.h"
#include "NetVar.h"
#include "Sessions.h"

#define MIN_ACCEPTABLE_FRAG_SIZE 64
#define MAX_ACCEPTABLE_FRAG_SIZE 64000

void FragTimer::Dispatch(double t, int /* is_expire */)
	{
	if ( ! active )
		return;

	f->Expire(t);
	}

FragReassembler::FragReassembler(NetSessions* arg_s,
			const IP_Hdr* ip, const u_char* pkt,
			uint32 frag_field, HashKey* k, double t)
: Reassembler(0, ip->DstAddr(), REASSEM_IP)
	{
	s = arg_s;
	key = k;
	const struct ip* ip4 = ip->IP4_Hdr();
	proto_hdr_len = ip4->ip_hl * 4;
	proto_hdr = (struct ip*) new u_char[64];	// max IP header
	// Don't do a structure copy - need to pick up options, too.
	memcpy((void*) proto_hdr, (const void*) ip4, proto_hdr_len);

	reassembled_pkt = 0;
	frag_size = 0;	// flag meaning "not known"

	AddFragment(t, ip, pkt, frag_field);

	if ( frag_timeout != 0.0 )
		{
		expire_timer = new FragTimer(this, t + frag_timeout);
		timer_mgr->Add(expire_timer);
		}
	else
		expire_timer = 0;
	}

FragReassembler::~FragReassembler()
	{
	DeleteTimer();
	delete [] proto_hdr;
	delete reassembled_pkt;
	delete key;
	}

void FragReassembler::AddFragment(double t, const IP_Hdr* ip, const u_char* pkt,
				uint32 frag_field)
	{
	const struct ip* ip4 = ip->IP4_Hdr();

	if ( ip4->ip_p != proto_hdr->ip_p || ip4->ip_hl != proto_hdr->ip_hl )
		// || ip4->ip_tos != proto_hdr->ip_tos
		// don't check TOS, there's at least one stack that actually
		// uses different values, and it's hard to see an associated
		// attack.
		s->Weird("fragment_protocol_inconsistency", ip);

	if ( frag_field & 0x4000 )
		// Linux MTU discovery for UDP can do this, for example.
		s->Weird("fragment_with_DF", ip);

	int offset = (ntohs(ip4->ip_off) & 0x1fff) * 8;
	int len = ntohs(ip4->ip_len);
	int hdr_len = proto_hdr->ip_hl * 4;
	int upper_seq = offset + len - hdr_len;

	if ( (frag_field & 0x2000) == 0 )
		{
		// Last fragment.
		if ( frag_size == 0 )
			frag_size = upper_seq;

		else if ( upper_seq != frag_size )
			{
			s->Weird("fragment_size_inconsistency", ip);

			if ( upper_seq > frag_size )
				frag_size = upper_seq;
			}
		}

	else if ( len < MIN_ACCEPTABLE_FRAG_SIZE )
		s->Weird("excessively_small_fragment", ip);

	if ( upper_seq > MAX_ACCEPTABLE_FRAG_SIZE )
		s->Weird("excessively_large_fragment", ip);

	if ( frag_size && upper_seq > frag_size )
		{
		// This can happen if we receive a fragment that's *not*
		// the last fragment, but still imputes a size that's
		// larger than the size we derived from a previously-seen
		// "last fragment".

		s->Weird("fragment_size_inconsistency", ip);
		frag_size = upper_seq;
		}

	// Do we need to check for consistent options?  That's tricky
	// for things like LSRR that get modified in route.

	// Remove header.
	pkt += hdr_len;
	len -= hdr_len;

	NewBlock(network_time, offset, len, pkt);
	}

void FragReassembler::Overlap(const u_char* b1, const u_char* b2, int n)
	{
	IP_Hdr proto_h((const struct ip*) proto_hdr);

	if ( memcmp((const void*) b1, (const void*) b2, n) )
		s->Weird("fragment_inconsistency", &proto_h);
	else
		s->Weird("fragment_overlap", &proto_h);
	}

void FragReassembler::BlockInserted(double /* t */, DataBlock* /* start_block */)
	{
	if ( blocks->seq > 0 || ! frag_size )
		// For sure don't have it all yet.
		return;

	// We might have it all - look for contiguous all the way.
	DataBlock* b;
	for ( b = blocks; b->next; b = b->next )
		if ( b->upper != b->next->seq )
			break;

	if ( b->next )
		{
		// We have a hole.
		if ( b->seq >= frag_size )
			{
			IP_Hdr proto_h((const struct ip*) proto_hdr);
			s->Weird("fragment_size_inconsistency", &proto_h);
			}
		else
			return;
		}

	else if ( last_block->upper > frag_size )
		{
		IP_Hdr proto_h((const struct ip*) proto_hdr);
		s->Weird("fragment_size_inconsistency", &proto_h);
		}

	else if ( last_block->upper < frag_size )
		// Missing the tail.
		return;

	// We have it all.
	u_char* pkt = new u_char[proto_hdr_len + frag_size];
	memcpy((void*) pkt, (const void*) proto_hdr, proto_hdr_len);

	struct ip* reassem4 = (struct ip*) pkt;
	reassem4->ip_len = htons(frag_size + proto_hdr_len);

	pkt += proto_hdr_len;

	for ( b = blocks; b; b = b->next )
		memcpy((void*) &pkt[b->seq], (const void*) b->block,
			b->upper - b->seq);

	reassembled_pkt = new IP_Hdr(reassem4);

	DeleteTimer();
	}

void FragReassembler::Expire(double t)
	{
	while ( blocks )
		{
		DataBlock* b = blocks->next;
		delete blocks;
		blocks = b;
		}

	expire_timer = 0;	// timer manager will delete it

	sessions->Remove(this);
	}

void FragReassembler::DeleteTimer()
	{
	if ( expire_timer )
		{
		expire_timer->Cancel();
		expire_timer = 0;	// timer manager will delete it
		}
	}
