networkprotocols/tcpipv4v6prt/src/ip6_frag.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 03 May 2010 13:58:16 +0300
changeset 18 39bb7c3571e9
parent 0 af10295192d8
permissions -rw-r--r--
Revision: 201018 Kit: 201018

// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// ip6_frag.cpp - hook for IPv6 fragment header
//

#include "frag.h"
#include <icmp6_hdr.h>	// for ICMP Type symbols
#include <ext_hdr.h>	// for IPv6 fragment header
#include "ip6_frag.h"
#include <in_pkt.h>
#include <in_chk.h>
#include <timeout.h>
#include "tcpip_ini.h"
#include "inet6log.h"

//	TIp6FragmentHeader
//	******************
//	This header is at the beginning of each fragment in the
//	fragment queue. This attempts to be the same size as
//	the real IPv6 fragment header (8 octets), but the code
//	should work with any size of this [use of this specific
//	size avoids some TrimStart() calls in some cases.]
//
class TIp6FragmentHeader
	{
public:
	TUint iOffset;		// Offset of this fragment (bytes)
	TUint iLength;		// Lenght of this fragment (bytes)
	};

//
//	RIp6Fragment
//	************
//	The IP6 fragment data structure is an RMBufChain containing
//	- TIp6FragmentHeader
//	- followed by fragment content
//	This is the interface to the RMBufFragQ class, a collection
//	of required methods for accessing the fragment information and
//	a join operation.
//
class RIp6Fragment : public RMBufFrag
	{
public:
	// Internal utility to the other methods. Assumes that the fragment
	// start is properly aligned to be cast into class pointer.
	inline TIp6FragmentHeader *Header() const { return (TIp6FragmentHeader *)(First()->Ptr()); }
	// Return offset of the fragment (bytes)
	TUint Offset() const {return Header()->iOffset;}
	// Return length of the fragment (bytes)
	// (Does not include the fragment header, using
	// this->Length() will give larger value that
	// includes the header)
	TUint FragmentLength() const {return Header()->iLength;}
	// Join another fragment to this one
	void Join(RIp6Fragment& aFrag)
		{
		TInt overlap = Offset() + FragmentLength() - aFrag.Offset();
		if (overlap < 0)
			{
			aFrag.Free();	// Should panic? This should not happen!
			User::Panic(_L("DEBUG"), 0);
			}
		else
			{
			Header()->iLength += aFrag.FragmentLength() - overlap;
			aFrag.TrimStart(sizeof(TIp6FragmentHeader) + overlap);
			Append(aFrag);
			}
		}
	};


//
//	CFragmentHandler
//	****************
//
class CAssembly;
class CFragmentHandler : public CFragmentHeaderHook
	{
public:
	CFragmentHandler(MNetworkService *aNetwork) : CFragmentHeaderHook(aNetwork) {}
	~CFragmentHandler();
	inline MNetworkService *Network() { return iNetwork; }
	CAssembly *LookupL(const RMBufRecvInfo &aInfo, TUint32 aId, TUint aVersion);

	void ConstructL();
	void Cancel(CAssembly *aAssembly);
	void CancelAll();
	TInt ApplyL(RMBufHookPacket &aPacket, RMBufRecvInfo &aInfo);
	void Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ);

	MTimeoutManager *iTimeoutManager;
	CAssembly *iCache;
	//
	TInt iCount;	// Current incomplete assemblies
	TInt iMaxCount;	// Maximum number of assemblies
	TInt iTotal;	// Total amount of RMBuf space allocated to assemblies (bytes)
	TInt iMaxTotal;	// Maximum allowed amount of RMBuf space
private:
	TInt Ip6ApplyL(RMBufRecvPacket &aPacket, RMBufRecvInfo &aInfo, const TInet6HeaderFragment &aHdr);
	TInt Ip4ApplyL(RMBufRecvPacket &aPacket, RMBufRecvInfo &aInfo, const TInet6HeaderIP4 &aHdr);
	void Ip6Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ, TInet6HeaderIP &aHdr);
	void Ip4Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ, TInet6HeaderIP4 &aHdr);

	//
	// iFagmentId is only used for IPv6. For IPv4, the Id is already in
	// in each IP packet.
	//
	TInt iFragmentId;
	};

//
//	CAssembly
//	*********
//	The data structure for a partially assembled packet
//
class CAssembly : public CBase
	{
	friend class CFragmentHandler;
	friend class CFragmentLinkage;

	CAssembly(CFragmentHandler &aHandler, const TIp6Addr &aSrc, const TIp6Addr &aDst, TInt aId, TInt aVersion);
	~CAssembly();

	TInt AddFragmentL(RMBufRecvPacket &aPacket, TInt aTrim, TInt aLength, TInt aOffset, TUint32 aFh0);
	TInt Add(RMBufRecvPacket &aPacket, TInt aTrim, TInt aLength, TInt aOffset, TUint32 aFh0 = 0);
	TInt CompletePacket(TInt aRestoreFH = 0);
	void Timeout();
public:
	//
	// Linkage and management
	//
	CFragmentHandler &iHandler;
	CAssembly *iNext;		// Linkage of the assemblies in the cache

	RTimeout iTimeout;		// Timer hook
#ifndef _LOG
protected:
#endif
	const TUint8 iVersion;	// Need to have, because TAHI wants FH in IPv6 TimeExceeded ICMP
	TUint iIsDead:1;		// =1, if this assembly is "dead" (matching fragments are dropped)
	const TUint32 iId;		// Fragment identification
	const TIp6Addr iSrcAddr;
	const TIp6Addr iDstAddr;

	TUint32 iFH0;			// Saved first 32 bits of the first FH (for TAHI)

	TInt32 iLength;			// Total length of the fragmentable part, when
							// known (e.g. last fragment received).
							// [As 32 bit int is used, there shouuld be no
							// overflow or wrap around problems because
							// offset field is only 16 bits (in bytes)]
	TInt iTotal;			// The amount of "iTotal" from this assembly.
	//
	// The unfragmentable part of the first fragment (if received)
	//
	RMBufRecvPacket iHead;
	RMBufFragQ<RIp6Fragment> iQueue;
	};

//
//	CFragmentLinkage
//	****************
//	Glue to bind timeout callback from the timeout manager into Timeout() call
//	on the CAssembly.
//
//	*NOTE*
//		This kludgery is all static and compile time, and only used in the constructor
//		of CFragmentAssembly following this.
//

// This ungainly manoevure is forced on us because the offset is not evaluated early enough by GCC3.4 to be
// passed as a template parameter
#if defined(__X86GCC__) || defined(__GCCE__)
#define KAssemblyTimeoutOffset 12
__ASSERT_COMPILE(KAssemblyTimeoutOffset == _FOFF(CAssembly, iTimeout));
#else
#define KAssemblyTimeoutOffset _FOFF(CAssembly, iTimeout)
#endif

class CFragmentLinkage : public TimeoutLinkage<CAssembly, KAssemblyTimeoutOffset>
	{
public:
	static void Timeout(RTimeout &aLink, const TTime & /*aNow*/, TAny * /*aPtr*/)
		{
		Object(aLink)->Timeout();
		}
	};

#ifdef _LOG
//
//	LogPrintId
//	**********
//	Purely for LOG output, should not be compiled in the final release
//
static void LogPrintId(const TDesC &aText, const CAssembly &a)
	{
	TInetAddr src, dst;
	TBuf<70> sbuf;
	TBuf<70> dbuf;

	src.SetAddress(a.iSrcAddr);
	dst.SetAddress(a.iDstAddr);

	src.OutputWithScope(sbuf);
	dst.OutputWithScope(dbuf);
	Log::Printf(_L("%S [src=%S dst=%S id=%d] (%d)"), &aText, &sbuf, &dbuf, a.iId, a.iHandler.iCount);
	}
#endif

//
//	CFragmentHandler::Cancel
//	************************
//	Cancel a specific assembly (delete it)
//
void CFragmentHandler::Cancel(CAssembly *aAssembly)
	{
	for (CAssembly **h = &iCache; *h != NULL; h = &(*h)->iNext)
		if (aAssembly == *h)
			{
			*h = aAssembly->iNext;
			delete aAssembly;
			break;
			}
	// Should panic, if a was not found from the list! -- msa
	}

//
//	CFragmentHandler::CancelAll
//	***************************
//	Cancel all packets waiting for assembly (delete all)
//
void CFragmentHandler::CancelAll()
	{
	CAssembly *a;
	while ((a = iCache) != NULL)
		{
		iCache = a->iNext;
		delete a;
		}
	}

//
//	ApplyL
//	******
//	Called when an IP packet contain IPv6 fragment header.
//	(does not care whether outer IP header is v4 or v6, both work!)
//
TInt CFragmentHandler::ApplyL(RMBufHookPacket &aPacket, RMBufRecvInfo &aInfo)
	{
	for (;;)	// *NOT REAL LOOP, JUST A CONSTRUCT TO ENABLE USE OF 'break!
		{
		if (aInfo.iProtocol != STATIC_CAST(TInt, KProtocolInet6Fragment))
			break;	// Incorrect call, should only get IPv6 fragments here!
		TInet6Packet<TInet6HeaderFragment> fh(aPacket, aInfo.iOffset);
		if (fh.iHdr == NULL)
			break;	// Drop! (packet too short)

		if (aInfo.iIcmp == 0)
			return Ip6ApplyL(aPacket, aInfo, *fh.iHdr);

		if (aInfo.iIcmp != KProtocolInet6Icmp)
			break;	// Only IPv6 complaints should get this far, drop!

		const TInt offset = aInfo.iOffset - aInfo.iOffsetIp;	// Relative offset within problem packet
		if (aInfo.iType == KInet6ICMP_ParameterProblem &&	// A parameter problem...
			offset <= (TInt)aInfo.iParameter &&				// after start of this header?
			STATIC_CAST(TUint, offset + sizeof(TInet6HeaderFragment)) > STATIC_CAST(TUint, aInfo.iParameter))		// and before end of this header?
				break;		// Drop! (someone doesn't like my fragments!)
		//
		// Error is not Fragment Header specific, pass it on in the chain
		// Skip over header, pass error processing to the next header
		aInfo.iPrevNextHdr = (TUint16)aInfo.iOffset;	// Fragment next header is at +0
		aInfo.iProtocol = fh.iHdr->NextHeader();
		aInfo.iOffset += sizeof(TInet6HeaderFragment);
		return KIp6Hook_DONE;
		// WAS NOT LOOP, BE SURE TO EXIT IT ALWAYS!
		}
	aPacket.Free();
	return -1;
	}

//
//	Fragment
//	********
//	Called when an outgoing IP packet needs to be fragmented
//
void CFragmentHandler::Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ)
	{
	//
	// Decide on which fragmenting from the IP header.
	//
	TIpHeader *ip = ((RMBufPacketPeek &)aPacket).GetIpHeader();
	if (ip)
		if (ip->ip4.Version() == 4)
			Ip4Fragment(aPacket, aMtu, aFragQ, ip->ip4);
		else if (ip->ip4.Version() == 6)
			Ip6Fragment(aPacket, aMtu, aFragQ, ip->ip6);
	}


//	CAssembly
//	*********
//
CAssembly::CAssembly(CFragmentHandler &aHandler, const TIp6Addr &aSrc, const TIp6Addr &aDst, TInt aId, TInt aVersion)
 : iHandler(aHandler), iTimeout(CFragmentLinkage::Timeout), iVersion((TUint8)aVersion), iId(aId),
	iSrcAddr(aSrc), iDstAddr(aDst), iLength(65536)
	{
	}

//	CAssembly::~CAssebly()
//	**********************
//
CAssembly::~CAssembly()
	{
	iHandler.iTotal -= iTotal;
	--iHandler.iCount;

	LOG(LogPrintId(_L("CAssebly::~CAssebly():"), *this));

	iTimeout.Cancel();
	iQueue.Free();
	iHead.Free();
	}


LOCAL_C TInt CompareStructures()
	{
	return sizeof(TIp6FragmentHeader) - sizeof(TInet6HeaderFragment);
	}
//
//	CAssembly::CompletePacket
//	*************************
//	Merge the iHead and first fragment into a complete packet
//	(including the IPv4/IPv6 header stuff!)
//
//	Returns,
//	== KErrNone, if packet in iHead built
//	!= KErrNone, if no packet available
//
//	*NOTE*
//		this is also used when reassembly timeout hits, thus the
//		"complete" can also be an incomplete packet (including
//		only the first fragment).
//
TInt CAssembly::CompletePacket(TInt aRestoreFH)
	{
	if (iHead.IsEmpty())
		return -1;

	RMBufRecvInfo *const info = iHead.Info();

	RIp6Fragment first;
	iQueue.Remove(first);

	TInt length = first.Header()->iLength;
	ASSERT(first.Header()->iOffset == 0);

	if (aRestoreFH)
		{
		// *********************************************************
		// TAHI tester wants the returned ICMP to include the FH on
		// time exceeded message. This is for that...
		// *********************************************************
		const TInt adjust = CompareStructures();
		// As the value of adjust is known by compile time, compiler should eliminate
		// unnecessary code below (although, it may give a warnings about constants
		// being compared--this is indication that code elimination should work!)
		if (adjust > 0)
			first.TrimStart(adjust);
		else if (adjust < 0)
			{
			TInt err = first.Prepend(-adjust);
			if (err!=KErrNone)
				{
				first.Free();
				return -1;
				}
			}
		TInet6HeaderFragment fake_fh;
		*(TUint32 *)&fake_fh = iFH0;	// Saved 1st word of original FH
		fake_fh.SetId(iId);				// ..and this SetId cover all of FH
										// (there is no need to zero anything else)
		first.CopyIn(TPtrC8((TUint8 *)&fake_fh, sizeof(TInet6HeaderFragment)), 0);
		// .. ugh, KProtocolInet6Fragment is TInt, casting to TUint8* might
		// cause "endian problems", thus this copy to true TUint8 variable... -- msa
		const TUint8 proto = KProtocolInet6Fragment;
		iHead.CopyIn(TPtrC8((TUint8 *)&proto, 1), info->iPrevNextHdr);
		length += sizeof(TInet6HeaderFragment);
		iLength = length;
		}
	else
		first.TrimStart(sizeof(TIp6FragmentHeader));
	if (length > iLength)
		{
		//
		// Just to deal with some overlapping fragment, which extends
		// beyond the last fragment... (iLength is initialized to
		// 65536 and is only less, when last fragment has arrived!)
		length = iLength;
		first.TrimEnd(length);
		}

	iHead.Append(first);
	//
	// Fix info and IP header
	//
	//
	// Only the iLength needs to be updated, all other fields in the info
	// must already have the correct values!
	//
	info->iLength = info->iOffset + length;

	TInet6Packet<TIpHeader> ip(iHead, info->iOffsetIp);
	if (ip.iHdr)
		{
		TInt ver = ip.iHdr->ip4.Version();
		if (ver == 4)
			{
			if (ip.iLength >= ip.iHdr->ip4.HeaderLength() &&
				info->iLength < 65536)
				{
				// ..just to be tidy, make sure M=0 (nobody really cares) -- msa
				ip.iHdr->ip4.SetFlags((TUint8)(ip.iHdr->ip4.Flags() & ~KInet4IP_MF));
				ip.iHdr->ip4.SetTotalLength(info->iLength);
				return KErrNone;
				}
			}
		else if (ver == 6)
			{
			if (ip.iLength >= TInet6HeaderIP::MinHeaderLength() &&
				  STATIC_CAST(TUint, info->iLength) < STATIC_CAST(TUint, 65536 + sizeof(TInet6HeaderIP)))
				{
				ip.iHdr->ip6.SetPayloadLength(info->iLength - sizeof(TInet6HeaderIP));
				return KErrNone;
				}
			}
		}
	return -1;
	}

//
//	CAssembly::Add
//	**********************
//
TInt CAssembly::Add(RMBufRecvPacket &aPacket, TInt aTrim, TInt aOffset, TInt aLength, TUint32 aFH0)
	{
		if (iIsDead == 0)
			{
			TInt ret = 0; // [ = 0, only to silence compiler]
			TRAPD(err, ret = AddFragmentL(aPacket, aTrim, aOffset, aLength, aFH0));
			if (err == KErrNone)
				return ret;
			//
			// AddFragmentL left, change the Assembly into "dead" state
			// (and make sure all unnecessary resources are released)
			//
			iIsDead = 1;
			iHead.Free();
			iQueue.Free();
			iHandler.iTotal -= iTotal;
			iTotal = 0;
			}
		ASSERT(iTotal == 0);
		//
		// The hook return code = -1 (=> packet handled and released)
		//
		aPacket.Free();
		return -1;
	}


//	CAssembly::AddFragmentL
//	***********************
//  This should only be called from Add, which must trap the leaves and 
//
//	returns
//		standard inbound hook return codes
//	leaves
//		when assembly should be declared "dead" (seed "Add")
//
TInt CAssembly::AddFragmentL(RMBufRecvPacket &aPacket, TInt aTrim, TInt aOffset, TInt aLength, TUint32 aFH0)
	{
	RMBufRecvInfo *info = aPacket.Info();
	if (aOffset == 0)
		{
		//
		// Processing the first fragment (offset == 0!)
		//

		if (!iHead.IsEmpty())
			aTrim += info->iOffset;
		else
			{
			const TInt unfrag = info->iOffset;	// = Unfragmentable length (bytes)
			iTotal += unfrag;
			iHandler.iTotal += unfrag;

			iHead.Assign(aPacket);
			iHead.SplitL(unfrag, aPacket);
			iHead.SetInfo(info);
			aPacket.SetInfo(NULL);
			//
			// Patch in the next header field
			//
			iHead.CopyIn(TPtrC8((TUint8 *)&info->iProtocol, 1), info->iPrevNextHdr);
			//
			// This fragment is assigned as the first fragment. Save the FH0 for it.
			// (if there are multiple "first fragments", the first received is used)
			// (Only needed for IPv6 and to generate TAHI-compatible ICMP Time Exceeded)
			// *NOTE* The full first word is saved because, the reserved bits must be
			// kept also, if someone is using them -- msa
			iFH0 = aFH0;
			}
		}
	else
		aTrim += info->iOffset;
	//
	// Convert the header in the packet (if present) into
	// internal fragment header
	//
	aTrim -= sizeof(TIp6FragmentHeader);
	if (aTrim > 0)
		// Cast needed to avoid TrimStart() looking for info block!!!
		((RMBufChain &)aPacket).TrimStart(aTrim);
	else if (aTrim < 0)
		aPacket.PrependL(-aTrim);
	// Make sure the fragment header is properly aligned in the First() buffer.
	aPacket.Align(sizeof(TIp6FragmentHeader));
	TIp6FragmentHeader *frag = ((RIp6Fragment &)aPacket).Header();
	frag->iLength = aLength;
	frag->iOffset = aOffset;
	//
	// Add fragment to the queue. This assumes that the RMBufChain
	// is removed from the aPacket (IsEmpty() becomes True).
	//
	iQueue.Add((RIp6Fragment &)aPacket);
	ASSERT(aPacket.IsEmpty());

	if (iQueue.First().FragmentLength() >= (TUint)iLength)
		{
#ifdef _LOG
		TBuf<80> buf;
		buf.Format(_L("CAssembly::AddFragmentL(): [%d..%d (%d)] COMPLETED"), aOffset, aOffset+aLength-1, aLength);
		LogPrintId(buf, *this);
#endif
		// Packet has been fully assembled, restore the
		// complete packet into aPacket
		//
		if (CompletePacket() == KErrNone)
			{
			aPacket.Assign(iHead);
			if (info != iHead.Info())
				{
				//
				// Need to copy the Information from the iHead
				// (can't just do "*info = *iHead.Info()", because
				// that would copy the RMBuf base class parts too!!
				//
				// ...this looks a bit scary, does this always work? Should
				// look for a cleaner way to do this? -- msa
				const TInt off = sizeof(RMBufCell);
				const TInt len = sizeof(*info) - off;
				TPtr8((TUint8 *)info + off, len).Copy(TPtrC8((TUint8 *)iHead.Info() + off, len));
				}
			else
				{
				//
				// This only happens when the only fragment is full packet!
				//
				iHead.SetInfo(NULL);
				aPacket.SetInfo(info);
				}
			iHandler.Cancel(this);		// Deletes this!
			return KIp6Hook_DONE;
			}
		aPacket.Free();					// (mainly for info block!)
		iHandler.Cancel(this);			// Deletes this!
		return -1;
		}
	else
		{
		aPacket.FreeInfo();
#ifdef _LOG
		TBuf<80> buf;
		buf.Format(_L("CAssembly::AddFragmentL(): [%d..%d (%d)]"), aOffset, aOffset+aLength-1, aLength);
		LogPrintId(buf, *this);
#endif
		// Maintain total number of allocated bytes (roughly, this "overestimates"
		// if fragments overlap!).
		//
		iTotal += aLength;
		iHandler.iTotal += aLength;
		if (iHandler.iTotal > iHandler.iMaxTotal)
			{
#ifdef _LOG
			// ...borrow the 'buf' from above LOG block!
			buf.Format(_L("CAssembly::AddFragmentL(): %d went over max (assembly total=%d)"), iHandler.iTotal, iTotal);
			LogPrintId(buf, *this);
#endif
			User::Leave(KErrOverflow); // make assebly "dead" by leaving
			}
		return -1;
		}
	}


void CAssembly::Timeout()
	{
	//
	// Reconstruct start of IPv6 packet from the unfrag part
	//
	if (iVersion == 4)
		{
		if (CompletePacket() == KErrNone)
			iHandler.Network()->Icmp4Send(iHead, KInet4ICMP_TimeExceeded, 1, 0);
		}
	else
		{
		if (CompletePacket(1) == KErrNone)	// Request restore of FH (for TAHI)
			iHandler.Network()->Icmp6Send(iHead, KInet6ICMP_TimeExceeded, 1, 0);
		}
	LOG(LogPrintId(_L("CAssembly::Timeout()"), *this));
	iHandler.Cancel(this);	// <-- Deletes this!!!
	}

CAssembly *CFragmentHandler::LookupL(const RMBufRecvInfo &aInfo, TUint32 aId, TUint aVersion)
	{
	CAssembly *a = NULL;
	CAssembly *d = NULL;
	const TIp6Addr &src = TInetAddr::Cast(aInfo.iSrcAddr).Ip6Address();
	const TIp6Addr &dst = TInetAddr::Cast(aInfo.iDstAddr).Ip6Address();
	for (a = iCache; ; a = a->iNext)
		{
		if (a == NULL)
			{
			// If the quota for incomplete assemblies is all used
			if (iCount >= iMaxCount)
				{
				LOG(Log::Printf(_L("CFragmentHandler::LookupL() MaxCount %d reached"), iCount));
				if (d == NULL)
					User::Leave(KErrOverflow); // ... should assign some specific reason code?
				Cancel(d);
				LOG(Log::Printf(_L("CFragmentHandler::LookupL() deleted dead assembly")));
				}
			a = new (ELeave) CAssembly(*this, src, dst, aId, aVersion);
			a->iNext = iCache;
			iCache = a;
			a->iTimeout.Set(iTimeoutManager, 60);	// 60 seconds or bust!
			iCount++;
			LOG(LogPrintId(_L("CFragmentHandler::LookupL(): NEW"), *a));
			break;
			}
		else if (a->iId == aId && a->iSrcAddr.IsEqual(src) && a->iDstAddr.IsEqual(dst))
			break;
		else if (a->iIsDead)
			d = a;	// remember "oldest dead" assembly
		}
	return a;
	}

//
//	CFragmentHeaderHook::NewL
//
CFragmentHeaderHook *CFragmentHeaderHook::NewL(MNetworkService *aNetwork)
	{
	return new (ELeave) CFragmentHandler(aNetwork);
	}

void CFragmentHandler::ConstructL()
	{
	iTimeoutManager = TimeoutFactory::NewL();
	iNetwork->BindL((CProtocolBase *)this, BindHookFor(KProtocolInet6Fragment));


	//
	// Setup DOS protections
	// - maximum number of incomplete assemblies (iMaxCount)
	// - maximum amount of RMBUF space (iMaxTotal)
	//
	LOG(_LIT(KFormat, "\t[%S] %S = %d"));
	TInt value;
	if (iNetwork->Interfacer()->FindVar(TCPIP_INI_IP, TCPIP_INI_FRAG_COUNT, value))
		iMaxCount = value;
	else
		iMaxCount = KTcpipIni_FragCount;
	LOG(Log::Printf(KFormat, &TCPIP_INI_IP, &TCPIP_INI_FRAG_COUNT, iMaxCount));

	if (iNetwork->Interfacer()->FindVar(TCPIP_INI_IP, TCPIP_INI_FRAG_TOTAL, value))
		iMaxTotal = value;
	else
		iMaxTotal = KTcpipIni_FragTotal;
	LOG(Log::Printf(KFormat, &TCPIP_INI_IP, &TCPIP_INI_FRAG_TOTAL, iMaxTotal));
	}

//
//	CFragmentHandler::~CFragmentHandler()
//	*************************************
//
CFragmentHandler::~CFragmentHandler()
	{
	CancelAll();
	delete iTimeoutManager;
	}


//	******************
//	Reassembly section
//	******************
//	This a normal extension header receiver hook which is called when the
//	processing of headers reaches the Fragmentation Header.
//
TInt CFragmentHandler::Ip6ApplyL(RMBufRecvPacket &aPacket, RMBufRecvInfo &aInfo, const TInet6HeaderFragment &aHdr)
	{
	//
	// Extract fragment information off from the RMBufs
	// (after this, the fragment header can be disposed as needed)
	//
	aInfo.iProtocol = aHdr.NextHeader();
	const TUint32 fh0 = *(TUint32 *)&aHdr;		// needed for ICMP Time Exceeded.
	const TInt length = aInfo.iLength - aInfo.iOffset - sizeof(TInet6HeaderFragment);
	const TInt offset = aHdr.FragmentOffset();
	const TInt last = (aHdr.MFlag() == 0);

	CAssembly *a = LookupL(aInfo, aHdr.Id(), aInfo.iVersion);

	if (length <= 0 || ((length & 0x7) && !last))
		{
		// Zero length fragment, or the length of non-last fragment payload is not multiple of 8.
		// (always reply with ICMPv6, because IPv6 fragment header was used. If the packet was
		// actually an IPv4 packet, just adjust use different the parameter offset).
		//
		LOG(Log::Printf(_L("CFragmentHandler::IP6ApplyL(...): Payload Length=%d"), length));
		Network()->Icmp6Send(aPacket, KInet6ICMP_ParameterProblem, 0,
			aInfo.iVersion == 4 ? TInet6HeaderIP4::O_TotalLength : TInet6HeaderIP::O_PayloadLength);
		Cancel(a);
		// Note: Icmp6Send takes the packet -- no packet Free required!
		return -1;
		}
	if (offset + length > 65535)
		{
		//
		// This fragment would result too long payload for the final IPv6 packet
		//
		// *** above test is not correct! It should also take into account the
		//	   length of the unfragmentable part (if that includes extension
		//	   headers in addition to IPv6 header). However, this information
		//	   is not available until the first fragment is received. Thus, it
		//	   is possible that illegal fragments get entered into the fragment
		//	   queue! Needs some checking!! -- msa
		//
		LOG(Log::Printf(_L("CFragmentHandler::IP6ApplyL(...): offset(%d) + length(%d) > 65535"), offset, length));
		Network()->Icmp6Send(aPacket, KInet6ICMP_ParameterProblem, 0,
							aInfo.iOffset + TInet6HeaderFragment::O_FragmentOffset);
		Cancel(a);
		// Note: Icmp6Send takes the packet -- no packet Free required!
		return -1;
		}
	if (last)
		{
		//
		// If this is the last fragment, the total length is now known
		//
		a->iLength = offset + length;
		}

	return a->Add(aPacket, sizeof(TInet6HeaderFragment), offset, length, fh0);
	}

//	*******************
//	Fragmenting section
//	*******************
//	When entered, aPacket contains full IPv6 packet, and
//	it has been already tested that this does not fit the
//	Path MTU!
//	If called with a packet that fits the MTU, this generates a
//	single fragment (just inserts the IPv6 fragment header into
//	the packet with offset==0 and M=1)
//
void CFragmentHandler::Ip6Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ, TInet6HeaderIP &aHdr)
	{
	//
	// Check extension headers that need to be included into
	// unfragmentable part
	// It is somewhat fuzzy what should be included in addition
	// of Hop-by-hop and Routing header. Currently including
	// destination options, if they are before the routing header
	// -- msa

	// unfrag_next
	//	point always to the last next header field of the
	//	unfragmentable part.
	TUint8 *unfrag_next = ((TUint8 *)&aHdr) + TInet6HeaderIP::O_NextHeader;
	//
	// unfrag_size
	//	size of the unfragmentable part.
	TInt unfrag_size = sizeof(TInet6HeaderIP);

	TInt offset = sizeof(TInet6HeaderIP);
	for (TInt header = (TInt)*unfrag_next;;)
		{
		TInet6Packet<TInet6HeaderExtension> ext(aPacket, offset);
		if (ext.iHdr == NULL)
			// If the next header (8 octets) cannot be mapped from
			// the packet it cannot be a valid extension header anyways.
			// This search can be terminated with what we have now.
			break;
		offset += ext.iHdr->HeaderLength();

		if (header == STATIC_CAST(TInt, KProtocolInet6HopOptions))
			{
			// The mapped header is Hop-by-hop options. This is
			// included into unfragmentable part, but must look
			// forward if there is a routing header. Just remember
			// this part
			unfrag_size = offset;
			unfrag_next = (TUint8 *)ext.iHdr;	// point to Next Hdr field.
			}
		else if (header != STATIC_CAST(TInt, KProtocolInet6DestinationOptions))
			{
			// Terminate search (we got either unknown extension
			// or routing header). If the latter, then include it
			// into unfragmentable part.
			if (header == STATIC_CAST(TInt, KProtocolInet6RoutingHeader))
				{
				unfrag_size = offset;
				unfrag_next = (TUint8 *)ext.iHdr;	// point to Next Hdr field.
				}
			break;
			}
		// Look for more
		header = ext.iHdr->NextHeader();
		}
	//
	// The NextHeader of the last header (either IPv6 or an extension
	// need to be changed into KProtocolInet6Fragment. And, the old
	// value is placed into next header of every generated fragment
	// header.
	TInt next_header = (TInt)*unfrag_next;
	*unfrag_next = KProtocolInet6Fragment;
	//
	// Remove the fragmentable part off the packet, leaving
	// only the unfragmentable part into the original packet
	// (and info block!)
	//
	RMBufChain fragmentable;	// Fragmentable part
	RMBufSendPacket fragment;
	RMBufChain tailpart;
	RMBufPktInfo *info;
	TInet6Packet<TInet6HeaderFragment> fh;
	TInt remainder, chunk;

	TInt err = aPacket.Split(unfrag_size, fragmentable);
	if (err == KErrNone)
		err = aPacket.Append(sizeof(TInet6HeaderFragment));
	if (err != KErrNone)
		goto drop_all;		// -- probably out of memory (RMBUF's)

	remainder = aPacket.Info()->iLength - unfrag_size;
	//
	// Prepare access for the fragment header
	//
	fh.Set(aPacket, unfrag_size, sizeof(TInet6HeaderFragment));
	unfrag_size += sizeof(TInet6HeaderFragment);
	if (err != KErrNone ||		// Memory allocation error, or...
		fh.iHdr == NULL ||		// Cannot access fragment header, or..
		aMtu < (unfrag_size+8))
		goto drop_all;			// Mission impossible! Path MTU is less than
								// required unfragmentable size + minimal payload!
	fh.iHdr->ZeroAll();
	fh.iHdr->SetNextHeader(next_header);
	fh.iHdr->SetId(++iFragmentId);
	fh.iHdr->SetMFlag(1);
	//
	// Fragment payload size is multiple of 8
	//
	chunk = (aMtu - unfrag_size) & ~0x7;
	offset = 0;
	//
	// Patch in fragment payload size into IPv6 header, this will
	// be the same for all but last fragment, so it can be set outside
	// the loop...
	// (making a big assumption that the aHdr pointer is still valid!!
	// the current operations done to the aPacket are safe, but beware
	// when changing this code...) -- msa
	//
	aHdr.SetPayloadLength(unfrag_size + chunk - sizeof(TInet6HeaderIP));
	while (remainder > chunk)
		{
		//
		// Setup unfragmentable part
		//
		TRAP(err,
			aPacket.CopyL(fragment);
			aPacket.CopyInfoL(fragment);
			);
		if (err != KErrNone)
			goto drop_all;
		info = fragment.Info();
		info->iLength = chunk + unfrag_size;
		//
		// Extract next fragment payload
		//
		err = fragmentable.Split(chunk, tailpart);
		if (err != KErrNone)
			goto drop_all;
		fragment.Append(fragmentable);
		fragment.Pack();
		aFragQ.Append(fragment);
		fragmentable.Assign(tailpart);
		offset += chunk;
		remainder -= chunk;
		fh.iHdr->SetFragmentOffset(offset);
		}
	//
	// Make aPacket as the last fragment
	//
	fh.iHdr->SetMFlag(0);
	aHdr.SetPayloadLength(unfrag_size + remainder - sizeof(TInet6HeaderIP));
	aPacket.Append(fragmentable);
	info = aPacket.Info();
	info->iLength = unfrag_size + remainder;
	aPacket.Pack();
	aFragQ.Append(aPacket);
	return;

drop_all:
	aFragQ.Free();
	fragmentable.Free();
	fragment.Free();
	tailpart.Free();
	aPacket.Free();
	LOG(Log::Printf(_L("CFragmentHandler::Ip6Fragment(...) FAILED")));
	}


//	**********************
//	IPv4 Fragment handling
//	**********************

//	******************
//	Reassembly section
//	******************
//	This a called when an IPv4 fragment is received. On entry
//	aHead.ip4 already contains a full copy of the IPv4 header
//	and aHead.iOffset == ip4.HeaderLength()
//
//	*NOTE*
//		On return, the IPv4 header checksum is not recomputed
//		(should it? maybe for possible ICMP error message? -- msa)
//
TInt CFragmentHandler::Ip4ApplyL(RMBufRecvPacket &aPacket, RMBufRecvInfo &aInfo, const TInet6HeaderIP4 &aHdr)
	{
	//
	// Extract fragment information off from the RMBufs
	// (after this, the fragment header can be disposed as needed)
	//
	aInfo.iProtocol = aHdr.Protocol();				// Put the protocol into info
	const TInt length = aInfo.iLength - aInfo.iOffset;	// Actual payload length
	const TInt offset = (aHdr.FragmentOffset()) << 3;
	const TInt last = (aHdr.MF() == 0);
	//
	// Locate or create an assemble matching this fragment
	//
	CAssembly *a = LookupL(aInfo, aHdr.Identification() | (aInfo.iProtocol << 16), 4);

	if (length <= 0 || ((length & 0x7) && !last))
		{
		// Zero length fragment or,
		// the length of non-last fragment payload is not multiple of 8.
		//
		Network()->Icmp4Send(aPacket, KInet4ICMP_ParameterProblem, 0,
			TInet6HeaderIP4::O_TotalLength);
		Cancel(a);
		// Note: Icmp4Send takes the packet -- no packet Free required!
		return -1;
		}
	if (offset + length > 65535 - TInet6HeaderIP4::MinHeaderLength())
		{
		//
		// This fragment would result too long payload for the final IPv4 packet
		// (NOTE: above is not full proof, if the first fragment has options!
		//	The intent is just to drop obviously wrong fragments before doing
		//  anything more with them... -- msa)
		//
		Network()->Icmp4Send(aPacket, KInet4ICMP_ParameterProblem, 0,
			TInet6HeaderIP4::O_FragmentOffset);
		Cancel(a);
		// Note: Icmp4Send takes the packet -- no packet Free required!
		return -1;
		}

	if (last)
		a->iLength = offset + length;	// Last fragment, real payload length is now known

	return a->Add(aPacket, 0, offset, length);
	}

//
//	CrunchOptions
//	-------------
//		Only options marked as "copy" are copied into every fragment header.
//
//		This function takes options from the original packet and removes
//		all except the copiable options (process in place).
//
//		Returns the length of the "crunched" IPv4 header
//
static TInt CrunchOptions(TInet6HeaderIP4 &aHdr)
	{
	TInt length = TInet6HeaderIP4::MinHeaderLength();
	TUint8 *opt = ((TUint8 *)&aHdr) + length;
	TUint8 *dst = opt;
	TInt count = aHdr.HeaderLength() - length;
	while (count > 0)
		{
		if (*opt < 2)
			{
			// Q: Is it legal to have "Copy" on PAD? If so, this
			// needs some fixing yet! -- msa
			//
			++opt;	// END/PAD bytes, skip
			count -= 1;
			}
		else
			{
			int optlen = opt[1];
			if (optlen > count || optlen < 2)
				// An illegal option length, just bail out.
				// (The packet is broken anyway)
				break;
			count -= optlen;
			if (*opt & 0x80)
				{
				//
				// Copy the option
				//
				length += optlen;
				while (--optlen >= 0)
					*dst++ = *opt++;
				}
			else
				//
				// Crunch this
				//
				opt += optlen;
			}
		}
	//
	// The IPv4 header length is multiple of 4, and thus options length
	// must be padded, if not already aligned.. (Pad with END marker)
	//
	while (length & 0x3)
		{
		*dst++ = 0;	// EMD
		length++;
		}
	aHdr.SetHeaderLength(length);
	return length;
	}

//	*******************
//	Fragmenting section
//	*******************
//	When entered, aPacket contains full IPv4 packet, and
//	it has been already tested that this does not fit the
//	Path MTU!
void CFragmentHandler::Ip4Fragment(RMBufPacketBase &aPacket, TInt aMtu, RMBufPktQ &aFragQ, TInet6HeaderIP4 &aHdr)
	{
	RMBufPacketBase fragmentable;	// Fragmentable part
	RMBufChain tailpart;			// Just work space
	TInt hlen, chunk, remainder, offset;
	TInet6HeaderIP4 ip4;
	TPtr8 ip4ptr((TUint8 *)&ip4, sizeof(ip4), sizeof(ip4));
	TInt err = KErrNone;
	
	offset = aHdr.FragmentOffset() << 3;
	hlen = aHdr.HeaderLength();

	chunk = (aMtu - hlen) & ~7;
	if (chunk <= 0 || aHdr.DF())
		goto drop_all;	// Unreasonable Mtu... or fragmenting not allowed
	//
	// Split off the first fragment...
	//
	err = aPacket.Split(hlen + chunk, fragmentable);
	if (err != KErrNone)
		goto drop_all;
	aHdr.SetTotalLength(hlen + chunk);
	offset += chunk;
	remainder = aPacket.Info()->iLength - hlen - chunk;
	aPacket.Info()->iLength = hlen + chunk;
	//
	// Setup a fragment header including the options that
	// need to be copied for all the remaining fragments
	//
	ip4ptr = TPtrC8((TUint8 *)&aHdr, hlen);
	hlen = CrunchOptions(ip4);
	ip4ptr.SetLength(hlen);
	//
	// Generate "middle fragments". All of these will
	// have the more (MF) as 1.
	//
	chunk = (aMtu - hlen) & ~7;
	ip4.SetTotalLength(hlen + chunk);
	ip4.SetFlags(KInet4IP_MF);
	while (remainder > chunk)
		{
		//
		// Extract next fragment payload
		//
		err = fragmentable.Split(chunk, tailpart);
		if (err == KErrNone)
			err = fragmentable.Prepend(hlen);
		if (err != KErrNone)
			goto drop_all;
		ip4.SetFragmentOffset((TUint16)(offset >> 3));
		ip4.SetChecksum(0);
		ip4.SetChecksum(TChecksum::ComplementedFold(TChecksum::Calculate((TUint16*)&ip4, hlen)));
		fragmentable.CopyIn(ip4ptr);
		TRAP(err, aPacket.CopyInfoL(fragmentable));
		if (err != KErrNone)
			goto drop_all;
		fragmentable.Info()->iLength = chunk + hlen;
		fragmentable.Pack();
		aFragQ.Append(fragmentable);
		fragmentable.Assign(tailpart);
		offset += chunk;
		remainder -= chunk;
		}
	//
	// Generate the last fragment
	//
	err = fragmentable.Prepend(hlen);
	if (err != KErrNone)
		goto drop_all;
	ip4.SetFragmentOffset((TUint16)(offset >> 3));
	ip4.SetTotalLength(hlen + remainder);
	ip4.SetFlags((TUint8)(aHdr.Flags()));	// need to copy More flag from the original packet!
	ip4.SetChecksum(0);
	ip4.SetChecksum(TChecksum::ComplementedFold(TChecksum::Calculate((TUint16*)&ip4, hlen)));
	fragmentable.CopyIn(ip4ptr);
	TRAP(err, aPacket.CopyInfoL(fragmentable));
	if (err != KErrNone)
		goto drop_all;
	fragmentable.Info()->iLength = hlen + remainder;
	fragmentable.Pack();
	aFragQ.Append(fragmentable);

	//
	// Complete the header of the first fragment
	//
	aHdr.SetFlags(KInet4IP_MF);

	aHdr.SetChecksum(0);
	aHdr.SetChecksum(TChecksum::ComplementedFold(TChecksum::Calculate((TUint16*)&aHdr, aHdr.HeaderLength())));
	aPacket.Pack();
	aFragQ.Prepend(aPacket);
	//
	// All fragments successfully built.
	//
	return;
	//
	// Cleanup everything
	//
drop_all:
	aFragQ.Free();
	fragmentable.Free();
	tailpart.Free();
	aPacket.Free();
	LOG(Log::Printf(_L("CFragmentHandler::Ip4Fragment(...) FAILED")));
	}