/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    ipinip\send.c

Abstract:

    The file contains the part of interface of the IP in IP tunnel driver
    to the TCP/IP stack that deals with sending data

    The code is a cleaned up version of wanarp\ipif.c which in turn
    was derived from HenrySa's ip\arp.c

Revision History:

    AmritanR

--*/

#define __FILE_SIG__    SEND_SIG

#include "inc.h"

IP_STATUS
SendICMPErr(IPAddr Src, IPHeader UNALIGNED *Header, uchar Type, uchar Code,
            ulong Pointer, uchar Len);

VOID
SendIcmpError(
    DWORD           dwLocalAddress,
    PNDIS_BUFFER    pnbFirstBuff,
    PVOID           pvFirstData,
    ULONG           ulFirstLen,
    BYTE            byType,
    BYTE            byCode
    );

NDIS_STATUS
IpIpSend(
    PVOID           pvContext,
    NDIS_PACKET     **ppPacketArray,
    UINT            uiNumPackets,
    DWORD           dwDestAddr,
    RouteCacheEntry *pRce,
    PVOID           pvLinkContext
    )

/*++

Routine Description

    Function called by IP to send a packet

Locks

    The TUNNEL is refcounted (by virtue of being in IP)

Arguments

    pvContext       Our context to IP for the interface - the PTUNNEL
    ppPacketArray   The array of NDIS_PACKETs to send
    uiNumPackets    The number of packets in the array
    dwDestAddr      The destination (next hop) address
    pRce            Pointer to RCE.
    pvLinkContext   Only for P2MP interfaces

Return Value

    NDIS_STATUS_SUCCESS

--*/

{
    PTUNNEL          pTunnel;
    PWORK_QUEUE_ITEM pWorkItem;
    PQUEUE_NODE      pQueueNode;
    KIRQL            kiIrql;
    DWORD            dwLastAddr;

#if PROFILE

    LONGLONG         llTime, llNow;

    KeQueryTickCount((PLARGE_INTEGER)&llTime);

#endif

    TraceEnter(SEND, "IpIpSend");

    pTunnel = (PTUNNEL)pvContext;

    //
    // TODO: No one has a clue as to how to deal with multi-packet
    // sends. Right now we assume we get one packet. Later we can fix this
    //

    RtAssert(uiNumPackets is 1);

    //
    // All our packets are queued onto the TUNNEL before the transmit
    // routine is called. Allocate a link in the queue
    //

    pQueueNode  = AllocateQueueNode();

    if(pQueueNode is NULL)
    {
        //
        // Running out of memory
        //

        Trace(SEND, INFO,
              ("IpIpSend: Couldnt allocate queue node\n"));

        TraceLeave(SEND, "IpIpSend");

        return NDIS_STATUS_RESOURCES;
    }

    pWorkItem  = &(pQueueNode->WorkItem);

    pQueueNode->ppPacketArray = &(pQueueNode->pnpPacket);
    pQueueNode->pnpPacket     = ppPacketArray[0];
    pQueueNode->uiNumPackets  = uiNumPackets;
    pQueueNode->dwDestAddr    = dwDestAddr;

    //
    // If we are not at PASSIVE, just schedule a worker to come back and
    // handle this
    //

    if(KeGetCurrentIrql() > PASSIVE_LEVEL)
    {
        Trace(SEND, INFO,
              ("IpIpSend: Irql too high, queueing packet\n"));

        //
        // We dont need to reference the TUNNEL because IP has a reference
        // to the INTERFACE
        //

        RtAcquireSpinLockAtDpcLevel(&(pTunnel->rlLock));

        //
        // Hack for quenching ICMP errors to the same destination
        //

        dwLastAddr = 0;

        if(pTunnel->dwOperState isnot IF_OPER_STATUS_OPERATIONAL)
        {
            ULONG           i, ulFirstLen, ulTotalLen;
            PNDIS_PACKET    pnpPacket;
            PNDIS_BUFFER    pnbFirstBuff, pnbNewBuffer;
            PVOID           pvFirstData;
            PIP_HEADER      pHeader;

            //
            // Cant transmit on this, either because we are deleting this
            // interface, or because the admin has shut us down
            //

            for(i = 0; i < uiNumPackets; i++)
            {
                pnpPacket = ppPacketArray[i];

                //
                // Get the information about the packet and buffer
                //

                NdisGetFirstBufferFromPacket(pnpPacket,
                                             &pnbFirstBuff,
                                             &pvFirstData,
                                             &ulFirstLen,
                                             &ulTotalLen);

                RtAssert(pvFirstData isnot NULL);

                RtAssert(ulFirstLen >= sizeof(IP_HEADER));

                pHeader = (PIP_HEADER)pvFirstData;

                if(IsUnicastAddr(pHeader->dwDest))
                {
                    pTunnel->ulOutUniPkts++;
                }
                else
                {
                    pTunnel->ulOutNonUniPkts++;
                }

                pTunnel->ulOutDiscards++;

                //
                // Send an ICMP error
                //

                if(dwLastAddr isnot pHeader->dwSrc)
                {
                    SendIcmpError(pTunnel->LOCALADDR,
                                  pnbFirstBuff,
                                  pvFirstData,
                                  ulFirstLen,
                                  ICMP_TYPE_DEST_UNREACHABLE,
                                  ICMP_CODE_HOST_UNREACHABLE);

                    dwLastAddr = pHeader->dwSrc;
                }

            }

            RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));

            FreeQueueNode(pQueueNode);

            Trace(SEND, INFO,
                  ("IpIpSend: Tunnel %x has admin state %d so not sending\n",
                   pTunnel,
                   pTunnel->dwAdminState));


            TraceLeave(SEND, "IpIpSend");

            return NDIS_STATUS_SUCCESS;
        }

        //
        // Insert at the end of the queue
        //

        InsertTailList(&(pTunnel->lePacketQueueHead),
                       &(pQueueNode->leQueueItemLink));

#if PROFILE

        //
        // The time at which IP called us for these packets
        //

        pQueueNode->llSendTime = llTime;

#endif
        if(pTunnel->bWorkItemQueued is FALSE)
        {
            //
            // Need to schedule a work item since one is not already scheduled
            //

            ExInitializeWorkItem(pWorkItem,
                                 IpIpDelayedSend,
                                 pTunnel);


            //
            // TODO: For delayed sends we ref the tunnel. Do we need to?
            //

            ReferenceTunnel(pTunnel);

            //
            // Reference the driver since the worker has to be scheduled
            //

            RtAcquireSpinLockAtDpcLevel(&g_rlStateLock);

            g_ulNumThreads++;

            RtReleaseSpinLockFromDpcLevel(&g_rlStateLock);

            pTunnel->bWorkItemQueued = TRUE;


            ExQueueWorkItem(pWorkItem,
                            CriticalWorkQueue);

        }

#if PROFILE

        //
        // We update this field after queing the work item, but it is
        // still safe since the field is protected by the tunnel lock
        // This is the time at which the work item was queued
        //

        KeQueryTickCount((PLARGE_INTEGER)&(pQueueNode->llCallTime));

#endif

        RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));


        TraceLeave(SEND, "IpIpSend");

        return NDIS_STATUS_PENDING;
    }

    //
    // We dont need to reference the TUNNEL because IP has a reference to
    // the INTERFACE
    //

    //
    // If we are here, it is because we are at passive
    //


    //
    // Just hook the queue item to the end of the list and call the
    // transmit routine
    //

    RtAcquireSpinLock(&(pTunnel->rlLock),
                      &kiIrql);

    InsertTailList(&(pTunnel->lePacketQueueHead),
                   &(pQueueNode->leQueueItemLink));

    RtReleaseSpinLock(&(pTunnel->rlLock),
                      kiIrql);

#if PROFILE

    pQueueNode->llSendTime  = llTime;

    KeQueryTickCount((PLARGE_INTEGER)&llNow);

    pQueueNode->llCallTime  = llNow;

#endif

    IpIpTransmit(pTunnel,
                 FALSE);

    return NDIS_STATUS_PENDING;
}


VOID
IpIpDelayedSend(
    PVOID   pvContext
    )

/*++

Routine Description

    The worker function called when we find that the send from IP was
    not at PASSIVE

Locks

    None

Arguments

    None

Return Value

    None

--*/

{
    PTUNNEL         pTunnel;
    ULONG           i;
    KIRQL           irql;
    NDIS_STATUS     nsStatus;


    TraceEnter(SEND, "IpIpDelayedSend");

    pTunnel = (PTUNNEL)pvContext;

    RtAssert(pTunnel);

    IpIpTransmit(pTunnel,
                 TRUE);

    //
    // Either IpIpTransmit or TdixSendComplete will do the SendComplete
    //


    //
    // We referenced the tunnel if we put it on the work queue
    // Deref it now
    //

    DereferenceTunnel(pTunnel);

    RtAcquireSpinLock(&g_rlStateLock,
                      &irql);

    g_ulNumThreads--;

    if((g_dwDriverState is DRIVER_STOPPED) and
       (g_ulNumThreads is 0))
    {
        KeSetEvent(&g_keStateEvent,
                   0,
                   FALSE);
    }

    RtReleaseSpinLock(&g_rlStateLock,
                      irql);

    TraceLeave(SEND, "IpIpDelayedSend");
}

VOID
IpIpTransmit(
    PTUNNEL     pTunnel,
    BOOLEAN     bFromWorker
    )

/*++

Routine Description

    Called to transmit any queued packets on the tunnel

Locks

    This MUST be called at passive

Arguments

    pTunnel     The tunnel whose queue needs to be transmitted
    bFromWorker TRUE if called off a worker

Return Value

    None
    This is an implicit asynchronous call

--*/

{
    PIP_HEADER      pHeader, pNewHeader;
    USHORT          usLength;
    KIRQL           irql;
    UINT            i;
    ULONG           ulFirstLen, ulTotalLen;
    PNDIS_PACKET    pnpPacket;
    PNDIS_BUFFER    pnbFirstBuff, pnbNewBuffer;
    PVOID           pvFirstData;
    NTSTATUS        nStatus;
    PLIST_ENTRY     pleNode;
    PQUEUE_NODE     pQueueNode;

#if PROFILE

    LONGLONG        llCurrentTime;

    KeQueryTickCount((PLARGE_INTEGER)&llCurrentTime);

#endif

    TraceEnter(SEND, "IpIpTransmit");

    RtAcquireSpinLock(&(pTunnel->rlLock),
                      &irql);

    if(pTunnel->dwOperState isnot IF_OPER_STATUS_OPERATIONAL)
    {
        //
        // Cant transmit on this, either because we are deleting this
        // interface, or because the admin has shut us down
        // Just walk all the packets, increment the stats and then
        // call SendComplete for the packet
        //

        while(!IsListEmpty(&(pTunnel->lePacketQueueHead)))
        {
            DWORD   dwLastAddr;

            pleNode = RemoveHeadList(&(pTunnel->lePacketQueueHead));

            pQueueNode = CONTAINING_RECORD(pleNode,
                                           QUEUE_NODE,
                                           leQueueItemLink);

            dwLastAddr = 0;

            for(i = 0; i < pQueueNode->uiNumPackets; i++)
            {

                pnpPacket = pQueueNode->ppPacketArray[i];

                //
                // Get the information about the packet and buffer
                //

                NdisGetFirstBufferFromPacket(pnpPacket,
                                             &pnbFirstBuff,
                                             &pvFirstData,
                                             &ulFirstLen,
                                             &ulTotalLen);

                RtAssert(pvFirstData isnot NULL);

                RtAssert(ulFirstLen >= sizeof(IP_HEADER));

                pHeader = (PIP_HEADER)pvFirstData;

                if(IsUnicastAddr(pHeader->dwDest))
                {
                    pTunnel->ulOutUniPkts++;
                }
                else
                {
                    pTunnel->ulOutNonUniPkts++;
                }

                pTunnel->ulOutDiscards++;

                RtReleaseSpinLock(&(pTunnel->rlLock),
                                  irql);

                //
                // Send an ICMP error
                //

                if(dwLastAddr isnot pHeader->dwSrc)
                {
                    SendIcmpError(pTunnel->LOCALADDR,
                                  pnbFirstBuff,
                                  pvFirstData,
                                  ulFirstLen,
                                  ICMP_TYPE_DEST_UNREACHABLE,
                                  ICMP_CODE_HOST_UNREACHABLE);

                    dwLastAddr = pHeader->dwSrc;
                }

                g_pfnIpSendComplete(pTunnel->pvIpContext,
                                    pnpPacket,
                                    NDIS_STATUS_ADAPTER_NOT_READY);

                RtAcquireSpinLock(&(pTunnel->rlLock),
                                  &irql);
            }

            FreeQueueNode(pQueueNode);

        }

        if(bFromWorker)
        {
            pTunnel->bWorkItemQueued = FALSE;
        }

        RtReleaseSpinLock(&(pTunnel->rlLock),
                          irql);

        TraceLeave(SEND, "IpIpTransmit");

        return;
    }

    while(!IsListEmpty(&(pTunnel->lePacketQueueHead)))
    {
        pleNode = RemoveHeadList(&(pTunnel->lePacketQueueHead));

        pQueueNode = CONTAINING_RECORD(pleNode,
                                       QUEUE_NODE,
                                       leQueueItemLink);

        for(i = 0; i < pQueueNode->uiNumPackets; i++)
        {

            pnpPacket = pQueueNode->ppPacketArray[i];

            //
            // Get the information about the packet and buffer
            //

            NdisGetFirstBufferFromPacket(pnpPacket,
                                         &pnbFirstBuff,
                                         &pvFirstData,
                                         &ulFirstLen,
                                         &ulTotalLen);

            RtAssert(pvFirstData isnot NULL);

            //
            // Remove this till NK fixes the bug in IPTransmit
            // NB:
            //RtAssert(ulFirstLen >= sizeof(IP_HEADER));

            pHeader = (PIP_HEADER)pvFirstData;

            if(IsUnicastAddr(pHeader->dwDest))
            {
                pTunnel->ulOutUniPkts++;
            }
            else
            {
                pTunnel->ulOutNonUniPkts++;

                if(IsClassEAddr(pHeader->dwDest))
                {
                    //
                    // Bad address - throw it away
                    //

                    pTunnel->ulOutErrors++;

                    //
                    // Release the spinlock, call IP's SendComplete,
                    // reacquire the spinlock and continue processing the
                    // array
                    //

                    RtReleaseSpinLock(&(pTunnel->rlLock),
                                      irql);

                    g_pfnIpSendComplete(pTunnel->pvIpContext,
                                        pnpPacket,
                                        NDIS_STATUS_INVALID_PACKET);

                    RtAcquireSpinLock(&(pTunnel->rlLock),
                                      &irql);

                    continue;
                }
            }


            //
            // We dont need to muck with the TTL, since the IP stack would have
            // decremented it
            //

            //
            // RFC 2003 pg 6:
            // If the IP Source Address of the datagram matches router's own
            // IP Address, on any of its network interfaces, the router MUST NOT
            // tunnel the datagram; instead the datagram SHOULD be discarded
            //

            // TODO: This means comparing it against all the addresses that we
            // have


            //
            // RFC 2003 pg 6:
            // If the IP Source Address of the datagram matches the IP Address
            // of the Tunnel Destination, the router MUST NOT tunnel the
            // datagram; instead the datagram SHOULD be discarded
            //

            if(pHeader->dwDest is pTunnel->REMADDR)
            {
                Trace(SEND, ERROR,
                      ("IpIpTransmit: Packet # %d had dest of %d.%d.%d.%d which matches the remote endpoint\n",
                       i, PRINT_IPADDR(pHeader->dwDest)));

                pTunnel->ulOutErrors++;

                RtReleaseSpinLock(&(pTunnel->rlLock),
                                  irql);

                g_pfnIpSendComplete(pTunnel->pvIpContext,
                                    pnpPacket,
                                    NDIS_STATUS_INVALID_PACKET);

                RtAcquireSpinLock(&(pTunnel->rlLock),
                                  &irql);

                continue;
            }

            //
            // Slap on an IP header
            //

            pNewHeader = GetIpHeader(pTunnel);

            if(pNewHeader is NULL)
            {
                pTunnel->ulOutDiscards++;

                //
                // Not enough resources
                //

                Trace(SEND, ERROR,
                      ("IpIpTransmit: Could not get buffer for header\n"));

                RtReleaseSpinLock(&(pTunnel->rlLock),
                                  irql);

                g_pfnIpSendComplete(pTunnel->pvIpContext,
                                    pnpPacket,
                                    NDIS_STATUS_RESOURCES);

                RtAcquireSpinLock(&(pTunnel->rlLock),
                                  &irql);

                continue;
            }

            pNewHeader->byVerLen    = IP_VERSION_LEN;
            pNewHeader->byTos       = pHeader->byTos;

            //
            // Currently we dont have any options, so all we do
            // is add 20 bytes to the length
            //

            usLength = RtlUshortByteSwap(pHeader->wLength) + MIN_IP_HEADER_LENGTH;

            pNewHeader->wLength = RtlUshortByteSwap(usLength);

            //
            // Id is set up by IP stack
            // If the DF flag is set, copy that out
            //

            pNewHeader->wFlagOff    = (pHeader->wFlagOff & IP_DF_FLAG);
            pNewHeader->byTtl       = pTunnel->byTtl;
            pNewHeader->byProtocol  = PROTO_IPINIP;

            //
            // XSum is done by IP, but we need to zero it out
            //

            pNewHeader->wXSum       = 0x0000;
            pNewHeader->dwSrc       = pTunnel->LOCALADDR;
            pNewHeader->dwDest      = pTunnel->REMADDR;

            //
            // Slap on the buffer in front of the current packet
            // and we are done
            //

            pnbNewBuffer = GetNdisBufferFromBuffer((PBYTE)pNewHeader);

            RtAssert(pnbNewBuffer);

#if DBG

            //
            // Query the buffer to see that everything is setup OK
            //

#endif

            NdisChainBufferAtFront(pnpPacket,
                                   pnbNewBuffer);

            //
            // Reference the tunnel, once for every send
            // ulOutDiscards, ulOutOctets are incremented in
            // SendComplete handler.
            //

            pTunnel->ulOutQLen++;

            ReferenceTunnel(pTunnel);

            RtReleaseSpinLock(&(pTunnel->rlLock),
                              irql);

            //
            // Dont really care about the return code from here.
            // Even if it is an error, TdixSendDatagram will call our send
            // complete handler
            //

#if PROFILE

            TdixSendDatagram(pTunnel,
                             pnpPacket,
                             pnbNewBuffer,
                             usLength,
                             pQueueNode->llSendTime,
                             pQueueNode->llCallTime,
                             llCurrentTime);

#else

            TdixSendDatagram(pTunnel,
                             pnpPacket,
                             pnbNewBuffer,
                             usLength);

#endif

            //
            // If we come till here, we will always have our SendComplete called
            // The DereferenceTunnel() will be done there
            //

            RtAcquireSpinLock(&(pTunnel->rlLock),
                          &irql);
        }

        FreeQueueNode(pQueueNode);
    }

    //
    // Dont have a work item queued
    //

    if(bFromWorker)
    {
        pTunnel->bWorkItemQueued = FALSE;
    }

    RtReleaseSpinLock(&(pTunnel->rlLock),
                      irql);


    TraceLeave(SEND, "IpIpTransmit");
}

VOID
IpIpInvalidateRce(
    PVOID           pvContext,
    RouteCacheEntry *pRce
    )

/*++

Routine Description

    Called by IP when an RCE is closed or otherwise invalidated.

Locks


Arguments


Return Value
    NO_ERROR

--*/
{

}


UINT
IpIpReturnPacket(
    PVOID           pARPInterfaceContext,
    PNDIS_PACKET    pPacket
    )
{
    return STATUS_SUCCESS;
}

VOID
IpIpSendComplete(
    NTSTATUS        nSendStatus,
    PTUNNEL         pTunnel,
    PNDIS_PACKET    pnpPacket,
    ULONG           ulPktLength
    )

/*++

Routine Description


Locks

    We acquire the TUNNEL lock

Arguments


Return Value



--*/

{
    KIRQL        irql;
    PNDIS_BUFFER pnbFirstBuffer;
    UINT         uiFirstLength;
    PVOID        pvFirstData;

    TraceEnter(SEND, "IpIpSendComplete");

    //
    // The tunnel was refcounted, so could not have gone away
    // Lock it
    //

    RtAcquireSpinLock(&(pTunnel->rlLock),
                      &irql);

    //
    // If the status was success, increment the bytes sent
    // otherwise increment the bytes
    //

    if(nSendStatus isnot STATUS_SUCCESS)
    {
        Trace(SEND, ERROR,
              ("IpIpSendComplete: Status %x sending data\n",
               nSendStatus));

        pTunnel->ulOutDiscards++;
    }
    else
    {
        pTunnel->ulOutOctets    += ulPktLength;
    }

    //
    // Decrement the Qlen
    //

    pTunnel->ulOutQLen--;

    RtReleaseSpinLock(&(pTunnel->rlLock),
                      irql);

    //
    // Free the IP header we slapped on
    //

    NdisUnchainBufferAtFront(pnpPacket,
                             &pnbFirstBuffer);

    NdisQueryBuffer(pnbFirstBuffer,
                    &pvFirstData,
                    &uiFirstLength);

    RtAssert(uiFirstLength is MIN_IP_HEADER_LENGTH);

    FreeIpHeader(pTunnel,
                 pvFirstData);

    //
    // We are done. Just indicate everything back up to IP
    //

    g_pfnIpSendComplete(pTunnel->pvIpContext,
                        pnpPacket,
                        nSendStatus);

    //
    // Done with the tunnel, deref it
    //

    DereferenceTunnel(pTunnel);

    TraceEnter(SEND, "IpIpSendComplete");
}

#if 0
NDIS_STATUS
IpIpTransferData(
    PVOID        pvContext,
    NDIS_HANDLE  nhMacContext,
    UINT         uiProtoOffset,
    UINT         uiTransferOffset,
    UINT         uiTransferLength,
    PNDIS_PACKET pnpPacket,
    PUINT        puiTransferred
    )

/*++

Routine Description


Locks


Arguments


Return Value
    NO_ERROR

--*/

{
    NTSTATUS        nStatus;
    PNDIS_PACKET    pnpOriginalPacket;
    ULONG           ulTotalSrcLen, ulTotalDestLen;
    ULONG           ulDestOffset, ulSrcOffset;
    ULONG           ulCopyLength, ulBytesCopied;
    PNDIS_BUFFER    pnbSrcBuffer, pnbDestBuffer;
    PVOID           pvDataToCopy;

    //
    // The TD context we gave IP was just a pointer to the NDIS_PACKET
    //

    pnpOriginalPacket = (PNDIS_PACKET)nhMacContext;

    //
    // Get info about the first buffer in the src packet
    //

    NdisQueryPacket(pnpOriginalPacket,
                    NULL,
                    NULL,
                    &pnbSrcBuffer,
                    &ulTotalSrcLen);


    //
    // Query the given packet to get the Destination buffer
    // and the Total length
    //

    NdisQueryPacket(pnpPacket,
                    NULL,
                    NULL,
                    &pnbDestBuffer,
                    &ulTotalDestLen);


    ulSrcOffset = uiTransferOffset + uiProtoOffset;

    //
    // Make sure that we have enough data to fulfil the request
    //


    RtAssert((ulTotalSrcLen - ulSrcOffset) >= uiTransferLength);

    RtAssert(pnbDestBuffer);


    //
    // ulDestOffset is also a count of the bytes copied till now
    //

    ulDestOffset    = 0;

    while(pnbSrcBuffer)
    {

        NdisQueryBuffer(pnbSrcBuffer,
                        &pvDataToCopy,
                        &ulCopyLength);

        //
        // See if we need to copy the whole buffer or only part
        // of it. ulDestOffset is also a count of he bytes copied
        // up till this point
        //

        if(uiTransferLength - ulDestOffset < ulCopyLength)
        {
            //
            // Need to copy less than this buffer
            //

            ulCopyLength = uiTransferLength - ulDestOffset;
        }

#if NDISBUFFERISMDL

        nStatus = TdiCopyBufferToMdl(pvDataToCopy,
                                     ulSrcOffset,
                                     ulCopyLength,
                                     pnbDestBuffer,
                                     ulDestOffset,
                                     &ulBytesCopied);

#else
#error "Fix this"
#endif

        if((nStatus isnot STATUS_SUCCESS) and
           (ulBytesCopied isnot ulCopyLength))
        {
            //
            // something bad happened in the copy
            //

        }

        ulSrcOffset     = 0;
        ulDestOffset   += ulBytesCopied;

        NdisGetNextBuffer(pnbSrcBuffer, &pnbSrcBuffer);
    }

    *puiTransferred = ulDestOffset;
}
#endif

NDIS_STATUS
IpIpTransferData(
    PVOID        pvContext,
    NDIS_HANDLE  nhMacContext,
    UINT         uiProtoOffset,
    UINT         uiTransferOffset,
    UINT         uiTransferLength,
    PNDIS_PACKET pnpPacket,
    PUINT        puiTransferred
    )

/*++

Routine Description


Locks


Arguments


Return Value

    NO_ERROR

--*/

{
    PTRANSFER_CONTEXT    pXferCtxt;

    TraceEnter(SEND, "IpIpTransferData");

    pXferCtxt = (PTRANSFER_CONTEXT)nhMacContext;

    pXferCtxt->pvContext         = pvContext;
    pXferCtxt->uiProtoOffset     = uiProtoOffset;
    pXferCtxt->uiTransferOffset  = uiTransferOffset;
    pXferCtxt->uiTransferLength  = uiTransferLength;
    pXferCtxt->pnpTransferPacket = pnpPacket;

    *puiTransferred = 0;

    pXferCtxt->bRequestTransfer  = TRUE;

    TraceLeave(SEND, "IpIpTransferData");

    return NDIS_STATUS_PENDING;
}

VOID
SendIcmpError(
    DWORD           dwLocalAddress,
    PNDIS_BUFFER    pnbFirstBuff,
    PVOID           pvFirstData,
    ULONG           ulFirstLen,
    BYTE            byType,
    BYTE            byCode
    )

/*++

Routine Description:

    Internal routine called to send an icmp error message

Locks:

    None needed, the buffers shouldnt be modified while the function is
    in progress

Arguments:

    dwLocalAddress  NTE on which this packet was received
    pnbFirstBuffer  The buffer that has the IP Header
    pvFirstData     Pointer to the data in the buffer
    ulFirstLen      Size of the buffer
    byType          ICMP type to return
    byCode          ICMP code to return

Return Value:

    None

--*/

{
    struct IPHeader *pErrorHeader;
    BYTE            FlatHeader[MAX_IP_HEADER_LENGTH + ICMP_HEADER_LENGTH];
    ULONG           ulSecondLen, ulLeft;
    PVOID           pvSecondBuff;

    //
    // If the error is being sent in response to an ICMP
    // packet, tcpip will touch the icmp header also
    // So we copy it into a flat buffer
    //

    pErrorHeader = NULL;

    if((ulFirstLen < MAX_IP_HEADER_LENGTH + ICMP_HEADER_LENGTH) and
       (ulFirstLen < (ULONG)RtlUshortByteSwap(((PIP_HEADER)pvFirstData)->wLength)))
    {
        NdisQueryBufferSafe(NDIS_BUFFER_LINKAGE(pnbFirstBuff),
                            &pvSecondBuff,
                            &ulSecondLen,
                            LowPagePriority);

        if(pvSecondBuff isnot NULL)
        {
            //
            // First copy out what's in the first buffer
            //

            RtlCopyMemory(FlatHeader,
                          pvFirstData,
                          ulFirstLen);

            //
            // How much is left in the flat buffer?
            //

            ulLeft = (MAX_IP_HEADER_LENGTH + ICMP_HEADER_LENGTH) - ulFirstLen;

            //
            // Copy out MIN(SecondBuffer, What's Left)
            //

            ulLeft = (ulSecondLen < ulLeft) ? ulSecondLen: ulLeft;

            RtlCopyMemory(FlatHeader + ulFirstLen,
                          pvSecondBuff,
                          ulLeft);

            pErrorHeader = (struct IPHeader *)&FlatHeader;
        }
    }
    else
    {
        pErrorHeader = (struct IPHeader *)pvFirstData;
    }

    if(pErrorHeader isnot NULL)
    {
        SendICMPErr(dwLocalAddress,
                    pErrorHeader,
                    ICMP_TYPE_DEST_UNREACHABLE,
                    ICMP_CODE_HOST_UNREACHABLE,
                    0,
                    0);
    }
}