diff -r 000000000000 -r af10295192d8 networkprotocols/tcpipv4v6prt/src/ip6_frag.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/networkprotocols/tcpipv4v6prt/src/ip6_frag.cpp Tue Jan 26 15:23:49 2010 +0200 @@ -0,0 +1,1183 @@ +// 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 // for ICMP Type symbols +#include // for IPv6 fragment header +#include "ip6_frag.h" +#include +#include +#include +#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 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 + { +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 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 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 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 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"))); + }