text: /*++
  
  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)));
  }
  