/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    ipinip\ioctl.c

Abstract:

    IOCTL handlers for IP in IP encapsulation driver

Author:

    Amritansh Raghav

Revision History:

    AmritanR    Created

Notes:

--*/


#define __FILE_SIG__    IOCT_SIG

#include "inc.h"


NTSTATUS
AddTunnelInterface(
    IN  PIRP   pIrp,
    IN  ULONG  ulInLength,
    IN  ULONG  ulOutLength
    )

/*++

Routine Description

    This is the handler for IOCTL_IPINIP_CREATE_TUNNEL.  We do the normal
    buffer length checks. 

Locks

    None

Arguments

    pIrp          IRP 
    ulInLength    The length of the Input Buffer
    ulOutLength   The length of the Output Buffer

Return Value

    STATUS_SUCCESS
    STATUS_BUFFER_TOO_SMALL      
    STATUS_INFO_LENGTH_MISMATCH
    STATUS_INVALID_PARAMETER

--*/

{
    PVOID       pvIoBuffer;
    NTSTATUS    nStatus;
    PTUNNEL     pTunnel;
    KIRQL       irql; 
    ULONG       i, ulMaxLength;
    BOOLEAN     bTerminated;
    DWORD       dwNewIndex;

    PIPINIP_CREATE_TUNNEL   pCreateInfo;

    TraceEnter(TUNN, "AddTunnelInterface");

    //
    // Get the user buffer
    //

    pvIoBuffer   = pIrp->AssociatedIrp.SystemBuffer;

    pCreateInfo = (PIPINIP_CREATE_TUNNEL)pvIoBuffer;

    //
    // Always clean out the information field
    //

    pIrp->IoStatus.Information   = 0;

    if(ulInLength < sizeof(IPINIP_CREATE_TUNNEL))
    {
        Trace(TUNN, ERROR,
              ("AddTunnelInterface: In Length %d too small\n",
               ulInLength));

        TraceLeave(TUNN, "AddTunnelInterface");

        return STATUS_BUFFER_TOO_SMALL;
    }

    if(ulOutLength < sizeof(IPINIP_CREATE_TUNNEL))
    {
        Trace(TUNN, ERROR,
              ("AddTunnelInterface: Out Length %d too small\n",
               ulInLength));

        TraceLeave(TUNN, "AddTunnelInterface");

        return STATUS_BUFFER_TOO_SMALL;
    }

    nStatus = IpIpCreateAdapter(pCreateInfo,
                                0,
                                &dwNewIndex);

   
    if(nStatus is STATUS_SUCCESS)
    {
        pIrp->IoStatus.Information = sizeof(IPINIP_CREATE_TUNNEL);

        pCreateInfo->dwIfIndex = dwNewIndex;
    }

    TraceLeave(TUNN, "AddTunnelInterface");
    
    return nStatus;
}



NTSTATUS
DeleteTunnelInterface(
    IN  PIRP   pIrp,
    IN  ULONG  ulInLength,
    IN  ULONG  ulOutLength
    )

/*++

Routine Description

    This is the handler for IOCTL_IPINIP_DELETE_TUNNEL.  

Locks

    Takes the tunnel list lock as writer and the tunnel lock

Arguments

    pIrp          IRP 
    ulInLength    The length of the Input Buffer
    ulOutLength   The length of the Output Buffer

Return Value

    STATUS_SUCCESS
    STATUS_BUFFER_TOO_SMALL      
    STATUS_OBJECT_NAME_NOT_FOUND

--*/

{
    PVOID       pvIoBuffer;
    NTSTATUS    nStatus;
    PTUNNEL     pTunnel;
    KIRQL       irql; 
    ULONG       i;
    LIST_ENTRY  leTempList;

    PIPINIP_DELETE_TUNNEL   pDeleteInfo;

    TraceEnter(TUNN, "DeleteTunnelInterface");

    //
    // Get the user buffer
    //

    pvIoBuffer   = pIrp->AssociatedIrp.SystemBuffer;

    pDeleteInfo = (PIPINIP_DELETE_TUNNEL)pvIoBuffer;

    //
    // Always clean out the information field
    //

    pIrp->IoStatus.Information   = 0;

    if(ulInLength < sizeof(IPINIP_DELETE_TUNNEL))
    {
        Trace(TUNN, ERROR,
              ("DeleteTunnelInterface: In Length %d too small\n",
               ulInLength));

        TraceLeave(TUNN, "DeleteTunnelInterface");

        return STATUS_BUFFER_TOO_SMALL;
    }

    InitializeListHead(&leTempList);

    EnterReader(&g_rwlTunnelLock,
                &irql);
    
    pTunnel = FindTunnelGivenIndex(pDeleteInfo->dwIfIndex);

    if(pTunnel is NULL)
    {
        ExitReader(&g_rwlTunnelLock,
                   irql);
        
        //
        // Could not find the tunnel for the given index
        //
        
        Trace(TUNN, ERROR,
              ("DeleteTunnelInterface: Couldnt find tunnel for index %d\n",
               pDeleteInfo->dwIfIndex));

        TraceLeave(TUNN, "DeleteTunnelInterface");
    
        return STATUS_OBJECT_NAME_NOT_FOUND;
    }

    if(IsTunnelMapped(pTunnel))
    {
        //
        // Remove it from the address blocks
        //

        RemoveEntryList(&(pTunnel->leAddressLink));
    }

    //
    // Mark the tunnel as unmapped
    //

    MarkTunnelUnmapped(pTunnel);

    //
    // Remove the tunnel from the list
    //

    RemoveEntryList(&(pTunnel->leTunnelLink));

    pTunnel->dwAdminState |= TS_DELETING;

    pTunnel->dwOperState = IF_OPER_STATUS_NON_OPERATIONAL;
  
    //
    // If there are queued packets, copy out the queue
    //

    if(!IsListEmpty(&(pTunnel->lePacketQueueHead)))
    {
        //
        // Copy out Flink and Blink
        //

        leTempList = pTunnel->lePacketQueueHead;

        //
        // Set Flink's Blink
        //

        leTempList.Flink->Blink = &leTempList;

        //
        // Set Blink's Flink
        //

        leTempList.Blink->Flink = &leTempList;
    }

    RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));


    //
    // Deref the tunnel once for deleting it from the list
    //

    DereferenceTunnel(pTunnel);

    //
    // Protected by the tunnel lock
    //

    g_ulNumTunnels--;

    //
    // Let go of the lock
    //

    ExitReader(&g_rwlTunnelLock, irql);

    //
    // Before deleting from IP, free all the packets
    //

    while(!IsListEmpty(&leTempList))
    {
        PLIST_ENTRY pleNode;
        PQUEUE_NODE pQueueNode;

        pleNode = RemoveHeadList(&leTempList);

        pQueueNode = CONTAINING_RECORD(pleNode,
                                       QUEUE_NODE,
                                       leQueueItemLink);

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

            pnpPacket = pQueueNode->ppPacketArray[i];

            //
            // ok to access pvIpContext since we have a reference
            // and the tunnel is not going away
            //

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

        FreeQueueNode(pQueueNode);
    }

    //
    // Now delete the interface
    //

    g_pfnIpDeleteInterface(pTunnel->pvIpContext,
                           TRUE);

    //
    // Dereference the tunnel for deleting it from IP
    // and once more because FindTunnel... put a ref on it
    //

    DereferenceTunnel(pTunnel);
    DereferenceTunnel(pTunnel);

    TraceLeave(TUNN, "DeleteTunnelInterface");
    
    return STATUS_SUCCESS;
}


NTSTATUS
SetTunnelInfo(
    IN  PIRP   pIrp,
    IN  ULONG  ulInLength,
    IN  ULONG  ulOutLength
    )

/*++

Routine Description

    This is the handler for IOCTL_IPINIP_SET_TUNNEL.  We do the normal
    buffer length checks. 

Locks

    Takes the tunnel list lock as writer and the tunnel lock

Arguments

    pIrp          IRP 
    ulInLength    The length of the Input Buffer
    ulOutLength   The length of the Output Buffer

Return Value

    STATUS_SUCCESS
    STATUS_BUFFER_TOO_SMALL      
    STATUS_INSUFFICIENT_RESOURCES
    STATUS_OBJECT_NAME_NOT_FOUND
    STATUS_INVALID_PARAMETER

--*/

{
    PVOID       pvIoBuffer;
    NTSTATUS    nsStatus;
    PTUNNEL     pTunnel;
    KIRQL       irql; 
    LIST_ENTRY  leTempList;
    ULONG       i;

    PIPINIP_SET_TUNNEL_INFO pSet;
    PTDI_ADDRESS_IP         pTdiIp;
    PADDRESS_BLOCK          pAddrBlock;

    TraceEnter(TUNN, "SetTunnelInfo");

    //
    // Get the user buffer
    //

    pvIoBuffer   = pIrp->AssociatedIrp.SystemBuffer;

    pSet = (PIPINIP_SET_TUNNEL_INFO)pvIoBuffer;

    //
    // Always clean out the information field
    //

    pIrp->IoStatus.Information   = 0;

    if(ulInLength < sizeof(IPINIP_SET_TUNNEL_INFO))
    {
        Trace(TUNN, ERROR,
              ("SetTunnelInfo: In Length %d too small\n",
               ulInLength));

        TraceLeave(TUNN, "SetTunnelInfo");

        return STATUS_BUFFER_TOO_SMALL;
    }

    //
    // Validate the parameters
    //

    if((pSet->dwLocalAddress is INVALID_IP_ADDRESS) or
       (pSet->dwRemoteAddress is INVALID_IP_ADDRESS) or
       ((DWORD)(pSet->dwLocalAddress & 0x000000E0) >= (DWORD)0x000000E0) or
       ((DWORD)(pSet->dwRemoteAddress & 0x000000E0) >= (DWORD)0x000000E0) or
       (pSet->byTtl is 0))
    {
        Trace(TUNN, ERROR,
              ("SetTunnelInfo: One of %d.%d.%d.%d %d.%d.%d.%d %d is invalid\n",
               PRINT_IPADDR(pSet->dwLocalAddress),
               PRINT_IPADDR(pSet->dwRemoteAddress),
               pSet->byTtl));

        TraceLeave(TUNN, "SetTunnelInfo");

        return STATUS_INVALID_PARAMETER;
    }

    InitializeListHead(&leTempList);

    EnterWriter(&g_rwlTunnelLock,
                &irql);
    
    pTunnel = FindTunnelGivenIndex(pSet->dwIfIndex);

    if(pTunnel is NULL)
    {
        ExitWriter(&g_rwlTunnelLock,
                   irql);
        
        //
        // Could not find the tunnel for the given index
        //
        
        Trace(TUNN, ERROR,
              ("SetTunnelInfo: Couldnt find tunnel for index %d\n",
               pSet->dwIfIndex));

        TraceLeave(TUNN, "SetTunnelInfo");
    
        return STATUS_OBJECT_NAME_NOT_FOUND;
    }

    if(IsTunnelMapped(pTunnel))
    {
        Trace(TUNN, TRACE,
              ("SetTunnelInfo: Tunnel already mapped\n"));

        //
        // if we are only changing the TTL, alles okay
        //

        if((pSet->dwRemoteAddress is pTunnel->REMADDR) and
           (pSet->dwLocalAddress is pTunnel->LOCALADDR))
        {
            Trace(TUNN, TRACE,
                  ("SetTunnelInfo: Only changing TTL on mapped tunnel\n"));

            pTunnel->byTtl = pSet->byTtl;

            RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));
        
            DereferenceTunnel(pTunnel);

            ExitWriter(&g_rwlTunnelLock,
                       irql);

            TraceLeave(TUNN, "SetTunnelInfo");

            return STATUS_SUCCESS;
        }

        //
        // So addresses are changing..
        //

        Trace(TUNN, TRACE,
              ("SetTunnelInfo: Changing address on mapped tunnel\n"));

        //
        // Remove it from the address blocks
        //

        RemoveEntryList(&(pTunnel->leAddressLink));

        //
        // This also marks it unmapped
        //

        pTunnel->dwAdminState = IF_ADMIN_STATUS_DOWN;
        pTunnel->dwOperState  = IF_OPER_STATUS_NON_OPERATIONAL;

        //
        // Copy out the queued packets to delete later
        //

        if(!IsListEmpty(&(pTunnel->lePacketQueueHead)))
        {
            //
            // Copy out Flink and Blink
            //

            leTempList = pTunnel->lePacketQueueHead;
    
            //
            // Set Flink's Blink
            //

            leTempList.Flink->Blink = &leTempList;

            //
            // Set Blink's Flink
            //

            leTempList.Blink->Flink = &leTempList;
        }
    }
    else
    {
        RtAssert(IsListEmpty(&(pTunnel->lePacketQueueHead)));
    }

    //
    // Set the state down
    //

    pTunnel->dwOperState  = IF_OPER_STATUS_NON_OPERATIONAL;
    pTunnel->dwAdminState = IF_ADMIN_STATUS_UP;

    //
    // See if we have the address block for this
    //

    pAddrBlock = GetAddressBlock(pSet->dwLocalAddress);

    if(pAddrBlock)
    {
        RtAssert(pAddrBlock->dwAddress is pSet->dwLocalAddress);

        if(pAddrBlock->bAddressPresent)
        {
            pTunnel->dwAdminState |= TS_ADDRESS_PRESENT;
        }
    }
    else
    {
        //
        // Create one
        //

        pAddrBlock = RtAllocate(NonPagedPool,
                                sizeof(ADDRESS_BLOCK),
                                TUNNEL_TAG);

        if(pAddrBlock is NULL)
        {
            RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));

            ExitWriter(&g_rwlTunnelLock,
                       irql);

            DereferenceTunnel(pTunnel);

            Trace(TDI, ERROR,
                  ("TdixAddressArrival: Unable to allocate address block\n"));

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        pAddrBlock->dwAddress       = pSet->dwLocalAddress;
        pAddrBlock->bAddressPresent = FALSE;

        InitializeListHead(&(pAddrBlock->leTunnelList));

        InsertHeadList(&g_leAddressList,
                       &(pAddrBlock->leAddressLink));
    }
       
    //
    // Link this onto the address
    //

    InsertHeadList(&(pAddrBlock->leTunnelList),
                   &(pTunnel->leAddressLink));
 
    pTunnel->REMADDR    = pSet->dwRemoteAddress;
    pTunnel->LOCALADDR  = pSet->dwLocalAddress;
    pTunnel->byTtl      = pSet->byTtl;

    MarkTunnelMapped(pTunnel);

    //
    // Initialize the TDI structure for this
    //

    pTdiIp = &(pTunnel->tiaIpAddr.Address[0].Address[0]);

    pTdiIp->sin_port = 0;
    pTdiIp->in_addr  = pTunnel->REMADDR;

    if(pTunnel->dwAdminState & TS_ADDRESS_PRESENT)
    {
        UpdateMtuAndReachability(pTunnel);
    }

    //
    // Return the current operational state to the user
    //

    pSet->dwOperationalState = pTunnel->dwOperState;

    RtReleaseSpinLockFromDpcLevel(&(pTunnel->rlLock));

    ExitWriter(&g_rwlTunnelLock,
               irql);


    //
    // Before dereferencing
    //

    while(!IsListEmpty(&leTempList))
    {
        PLIST_ENTRY pleNode;
        PQUEUE_NODE pQueueNode;

        pleNode = RemoveHeadList(&leTempList);

        pQueueNode = CONTAINING_RECORD(pleNode,
                                       QUEUE_NODE,
                                       leQueueItemLink);

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

            pnpPacket = pQueueNode->ppPacketArray[i];

            //
            // ok to access pvIpContext since we have a reference
            // and the tunnel is not going away
            //

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

        FreeQueueNode(pQueueNode);
    }
    
    DereferenceTunnel(pTunnel);

    TraceLeave(TUNN, "SetTunnelInfo");
   
    pIrp->IoStatus.Information = sizeof(IPINIP_SET_TUNNEL_INFO);

    return STATUS_SUCCESS;
}


NTSTATUS
GetTunnelTable(
    IN  PIRP   pIrp,
    IN  ULONG  ulInLength,
    IN  ULONG  ulOutLength
    )

/*++

Routine Description

    This is the handler for IOCTL_IPINIP_GET_TUNNEL.  We do the normal
    buffer length checks. 

Locks

   Takes the tunnel list lock as Reader

Arguments

    pIrp          IRP 
    ulInLength    The length of the Input Buffer
    ulOutLength   The length of the Output Buffer

Return Value

    STATUS_SUCCESS
    STATUS_BUFFER_TOO_SMALL      

--*/

{
    PVOID           pvIoBuffer;
    ULONG           i;
    NTSTATUS        nsStatus;
    KIRQL           irql;
    PLIST_ENTRY     pleNode;
    PTUNNEL         pTunnel;

    PIPINIP_TUNNEL_TABLE    pTunnelTable;

    
    TraceEnter(TUNN, "GetTunnels");
    
    //
    // Get the user buffer
    //

    pvIoBuffer   = pIrp->AssociatedIrp.SystemBuffer;

    pTunnelTable = (PIPINIP_TUNNEL_TABLE)pvIoBuffer;

    //
    // Always clean out the information field
    //

    pIrp->IoStatus.Information   = 0;

    if(ulOutLength < SIZEOF_BASIC_TUNNEL_TABLE)
    {
        Trace(TUNN, ERROR,
              ("GetTunnels: In Length %d too smaller than smallest table %d\n",
               ulInLength,
               SIZEOF_BASIC_TUNNEL_TABLE));

        TraceLeave(TUNN, "GetTunnels");

        return STATUS_BUFFER_TOO_SMALL;
    }

    EnterReader(&g_rwlTunnelLock,
                &irql);

    if(ulOutLength < SIZEOF_TUNNEL_TABLE(g_ulNumTunnels))
    {
        ExitReader(&g_rwlTunnelLock,
                   irql);

        Trace(TUNN, ERROR,
              ("GetTunnels: Len %d is less than required (%d) for %d i/f\n",
               ulOutLength,
               SIZEOF_TUNNEL_TABLE(g_ulNumTunnels),
               g_ulNumTunnels));

        pTunnelTable->ulNumTunnels = g_ulNumTunnels;

        pIrp->IoStatus.Information = SIZEOF_BASIC_TUNNEL_TABLE;

        TraceLeave(TUNN, "GetTunnels");
    
        return STATUS_SUCCESS;
    }

    pTunnelTable->ulNumTunnels = g_ulNumTunnels;
    
    //
    // So we have enough space to fill the tunnel
    //

    for(pleNode = g_leTunnelList.Flink, i = 0;
        pleNode isnot &g_leTunnelList;
        pleNode = pleNode->Flink, i++)
    {
        pTunnel = CONTAINING_RECORD(pleNode,
                                    TUNNEL,
                                    leTunnelLink);

        pTunnelTable->rgTable[i].dwIfIndex       = pTunnel->dwIfIndex;
        pTunnelTable->rgTable[i].dwRemoteAddress = pTunnel->REMADDR;
        pTunnelTable->rgTable[i].dwLocalAddress  = pTunnel->LOCALADDR;
        pTunnelTable->rgTable[i].fMapped         = IsTunnelMapped(pTunnel);
        pTunnelTable->rgTable[i].byTtl           = pTunnel->byTtl;
    }

    RtAssert(i is g_ulNumTunnels);

    ExitReader(&g_rwlTunnelLock,
               irql);

    pIrp->IoStatus.Information = SIZEOF_TUNNEL_TABLE(i);

    TraceLeave(TUNN, "GetTunnels");
    
    return STATUS_SUCCESS;
}

NTSTATUS
ProcessNotification(
    PIRP    pIrp,
    ULONG   ulInLength,
    ULONG   ulOutLength
    )

/*++

Routine Description:
      
    The handler for IOCTL_IPINIP_NOTIFICATION. We see if we have some info
    we wish to return to the caller and if we do, we return it. Otherwise,
    we pend the IRP and use it later when we need to report an event to
    the user mode
    
Locks: 

    Acquires the IoCancelSpinLock
    
Arguments:
      
    pIrp          IRP 
    ulInLength    The length of the Input Buffer
    ulOutLength   The length of the Output Buffer
    
Return Value:

    STATUS_PENDING
    STATUS_SUCCESS
    STATUS_BUFFER_TOO_SMALL
    
--*/

{
    KIRQL       kiIrql;
    PLIST_ENTRY pleNode;
    PVOID       pvIoBuffer;
    
    PPENDING_MESSAGE   pMessage;

    TraceEnter(GLOBAL, "ProcessNotification");
 
    pvIoBuffer   = pIrp->AssociatedIrp.SystemBuffer;
    
    pIrp->IoStatus.Information = 0;

    if((ulInLength < sizeof(IPINIP_NOTIFICATION)) or
       (ulOutLength < sizeof(IPINIP_NOTIFICATION)))
    {
        return STATUS_BUFFER_TOO_SMALL;
    }
        
    //
    // use cancel spin lock to prevent irp being cancelled during this call.
    //
    
    IoAcquireCancelSpinLock(&kiIrql);
    
    //
    // If we have a pending notification then complete it - else
    // queue the notification IRP
    //
    
    if(!IsListEmpty(&g_lePendingMessageList))
    {
        //
        // We have some old info
        //

        Trace(GLOBAL, TRACE,
              ("ProcNotification: Pending message being completed\n"));

        //
        // Remove it off the pending list
        //
        
        pleNode = RemoveHeadList(&g_lePendingMessageList);

        //
        // Get a pointer to the structure
        //
        
        pMessage = CONTAINING_RECORD(pleNode,
                                     PENDING_MESSAGE,
                                     leMessageLink);

        //
        // Copy out the event to the user mode buffer
        //
        
        RtlCopyMemory(pIrp->AssociatedIrp.SystemBuffer,
                      &pMessage->inMsg,
                      sizeof(IPINIP_NOTIFICATION));

        //
        // Mark the IRP as non pending (and hence non cancelable)
        //
        
        IoSetCancelRoutine(pIrp,
                           NULL);

        //
        // Fill the irp info
        //
        
        pIrp->IoStatus.Information = sizeof(IPINIP_NOTIFICATION);

        IoReleaseCancelSpinLock(kiIrql);
        
        //
        // Free the allocated message
        //
        
        FreeMessage(pMessage);
        
        return STATUS_SUCCESS;
    }


    Trace(GLOBAL, TRACE,
          ("ProcNotification: Notification being queued\n")); 


    //
    // Queue this IRP to use for later
    // First, mark the irp as pending
    //
    
    IoMarkIrpPending(pIrp);

    //
    // Queue up the irp at the end
    //
    
    InsertTailList(&g_lePendingIrpList,
                   &(pIrp->Tail.Overlay.ListEntry));

    //
    // Set the cancel routine
    //
    
    IoSetCancelRoutine(pIrp,
                       CancelNotificationIrp);
        
    IoReleaseCancelSpinLock(kiIrql);
        
    return STATUS_PENDING;
}

VOID
CancelNotificationIrp(
    PDEVICE_OBJECT  pDeviceObject,
    PIRP            pIrp
    )

/*++

Routine Description:

    Called to cancel a queued irp
  
Locks: 

    Called with the IoCancelSpinLock acquired
    
Arguments:
      
    pDeviceObject
    pIrp
    
Return Value:

    None
    
--*/

{
    TraceEnter(GLOBAL, "CancelNotificationIrp");

    //
    // Mark this Irp as cancelled
    //
    
    pIrp->IoStatus.Status        = STATUS_CANCELLED;
    pIrp->IoStatus.Information   = 0;

    //
    // Take off our own list
    //
    
    RemoveEntryList(&pIrp->Tail.Overlay.ListEntry);

    //
    // Release cancel spin lock which the IO system acquired
    //
    
    IoReleaseCancelSpinLock(pIrp->CancelIrql);

    IoCompleteRequest(pIrp,
                      IO_NETWORK_INCREMENT);
}


VOID
CompleteNotificationIrp(
    PPENDING_MESSAGE    pMessage
    )

/*++

Routine Description:

    Called to send a message to user mode
  
Locks: 

    Acquires the IoCancelSpinLock
    
Arguments:
      
    pEvent
    
Return Value:

    None
    
--*/

{
    KIRQL   kiIrql;
    
    TraceEnter(GLOBAL, "CompleteNotificationIrp");

    //
    // grab cancel spin lock
    //
    
    IoAcquireCancelSpinLock(&kiIrql);

    if(!IsListEmpty(&g_lePendingIrpList))
    {
        PLIST_ENTRY pleNode;
        PIRP        pIrp;

        //
        // We have a pending IRP. Use it to return info to router manager
        //
        
        pleNode = RemoveHeadList(&g_lePendingIrpList) ;

        pIrp = CONTAINING_RECORD(pleNode,
                                 IRP,
                                 Tail.Overlay.ListEntry);
        
        RtlCopyMemory(pIrp->AssociatedIrp.SystemBuffer,
                      &(pMessage->inMsg),
                      sizeof(IPINIP_NOTIFICATION));
        
        Trace(GLOBAL, TRACE,
              ("CompleteNotificationIrp: Returning Irp with event code of %d\n",
               ((PIPINIP_NOTIFICATION)pIrp->AssociatedIrp.SystemBuffer)->ieEvent));
        
        IoSetCancelRoutine(pIrp,
                           NULL);

        pIrp->IoStatus.Status       = STATUS_SUCCESS;
        pIrp->IoStatus.Information  = sizeof(IPINIP_NOTIFICATION);

        //
        // release lock
        //
        
        IoReleaseCancelSpinLock(kiIrql);

        IoCompleteRequest(pIrp,
                          IO_NETWORK_INCREMENT);

        //
        // Free the allocated Message
        //
        
        FreeMessage(pMessage);

    }
    else
    {
        Trace(GLOBAL, TRACE,
              ("CompleteNotificationIrp: Found no pending Irp so queuing message\n"));

        
        InsertTailList(&g_lePendingMessageList,
                       &(pMessage->leMessageLink));

        //
        // release lock
        //
        
        IoReleaseCancelSpinLock(kiIrql);
    }
}

PADDRESS_BLOCK
GetAddressBlock(
    DWORD   dwAddress
    )

/*++

Routine Description

    Looks up the address block for the given address

Locks

    Must be called with the g_rwlTunnelLock held    

Arguments

    dwAddress

Return Value

    Pointer to the address block
    NULL if not found
    
--*/

{
    PLIST_ENTRY pleNode;

    for(pleNode = g_leAddressList.Flink;
        pleNode isnot &g_leAddressList;
        pleNode = pleNode->Flink)
    {
        PADDRESS_BLOCK  pAddrBlock;

        pAddrBlock = CONTAINING_RECORD(pleNode,
                                       ADDRESS_BLOCK,
                                       leAddressLink);

        if(pAddrBlock->dwAddress is dwAddress)
        {
            return pAddrBlock;
        }
    }

    return NULL;
}

VOID
UpdateMtuAndReachability(
    PTUNNEL pTunnel
    )

/*++

Routine Description

    Updates the MTU and reachability info for a tunnel

Locks

    Must be called with the Tunnel locked and referenced

Arguments

    pTunnel

Return Value

    None

--*/

{
    DWORD           dwLocalNet;
    RouteCacheEntry *pDummyRce;
    BYTE            byType;
    USHORT          usMtu;
    IPOptInfo       OptInfo;
    BOOLEAN         bChange;
    ULONG           ulNewMtu;

    PPENDING_MESSAGE    pMessage;

    bChange = FALSE;

    RtAssert(pTunnel->dwAdminState & TS_ADDRESS_PRESENT);
    RtAssert(IsTunnelMapped(pTunnel));

    RtlZeroMemory(&OptInfo,
                  sizeof(OptInfo));

    //
    // See if the remote address is reachable and what the MTU is.
    //

    dwLocalNet = g_pfnOpenRce(pTunnel->REMADDR,
                              pTunnel->LOCALADDR,
                              &pDummyRce,
                              &byType,
                              &usMtu,
                              &OptInfo);

    if(dwLocalNet isnot NULL_IP_ADDR)
    {
        LLIPMTUChange       mtuChangeInfo;

        pTunnel->dwAdminState |= TS_ADDRESS_REACHABLE;

        //
        // Clear out any error bits
        //

        ClearErrorBits(pTunnel);

        //
        // Set the MTU if its changed
        //

        RtAssert(usMtu > MAX_IP_HEADER_LENGTH);

        ulNewMtu = usMtu - MAX_IP_HEADER_LENGTH;

        if(pTunnel->ulMtu isnot ulNewMtu)
        {
            bChange = TRUE;

            pTunnel->ulMtu = ulNewMtu;

            mtuChangeInfo.lmc_mtu = pTunnel->ulMtu;

            g_pfnIpStatus(pTunnel->pvIpContext,
                          LLIP_STATUS_MTU_CHANGE,
                          &mtuChangeInfo,
                          sizeof(LLIPMTUChange),
                          NULL);
        }

        if(pTunnel->dwOperState isnot IF_OPER_STATUS_OPERATIONAL)
        {
            bChange = TRUE;

            pTunnel->dwOperState = IF_OPER_STATUS_OPERATIONAL;
        }

        //
        // Close the RCE
        //

        g_pfnCloseRce(pDummyRce);
    }
    else
    {
        pTunnel->dwAdminState &= ~TS_ADDRESS_REACHABLE;
 
        if(pTunnel->dwOperState isnot IF_OPER_STATUS_NON_OPERATIONAL)
        {
            bChange = TRUE;

            pTunnel->dwOperState = IF_OPER_STATUS_NON_OPERATIONAL;
        }
    }

    if(bChange)
    {
        pMessage = AllocateMessage();

        if(pMessage isnot NULL)
        {
            if(pTunnel->dwOperState is IF_OPER_STATUS_OPERATIONAL)
            {
                pMessage->inMsg.ieEvent = IE_INTERFACE_UP;
            }
            else
            {
                pMessage->inMsg.ieEvent = IE_INTERFACE_DOWN;
            }

            pMessage->inMsg.iseSubEvent = 0xFFFFFFFF;
            pMessage->inMsg.dwIfIndex   = pTunnel->dwIfIndex;

            CompleteNotificationIrp(pMessage);
        }
    }

    KeQueryTickCount((PLARGE_INTEGER)&((pTunnel->ullLastChange)));
}