Implementing a intermediate driver that works with RAS under NT

Unfortunately, because of the "open" architecture of Windows NT it is absolutely impossible to implement intermediate drivers using the information provided in DDK.  Many aspects of the problem are completely uncovered there; and thanks God there are several examples of intermediate drivers which you can use as a starting point for an intermediate driver.

Unfortunately again, those examples work only for network cards such as Ethernet, Fddi and Token-Ring; trying to use them for NdisWan (this is microsoft's acronym for RAS pseudo-"network cards") fails.  The main reason for this is that RAS uses a proprietary interface (aside of the "standard" NDIS interface). The following instructions are how to make an intermediate driver that will accomplish this proprietary interface; however the following was tested only with NetBIOS transport, but TCP/IP transport should work this way too.

Here are the instructions how to adapt a intermediate driver that works with network cards to make it work with RAS:
NTSTATUS NdisWanIoctl (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
  // If this is a request for \Device\NdisWan, process it
  if (DeviceObject != NdisWanHookDevice)
    return -1;

  PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
  PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);

  // copy the IRP into the stack location for next driver
  *nextIrpStack = *IrpStack;

  if (IrpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
  {
    // Get the pointer to the input/output buffer and it's length
    PVOID inputBuffer = Irp->AssociatedIrp.SystemBuffer;
    PVOID outputBuffer = Irp->UserBuffer;
    ULONG inputBufferLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
    ULONG outputBufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;

    ULONG IoCode = (IrpStack->Parameters.DeviceIoControl.IoControlCode & 0x3ffc) >> 2;
    ULONG IoId = IrpStack->Parameters.DeviceIoControl.IoControlCode >> 16;

    if (IoId == 0x30)
      switch (IoCode)
      {
        case 3:                                 // ActivateRoute
        {
          struct ActivateRoute
          {
            ULONG Unknown1;                     // 0x00000001
            USHORT Protocol;                    // 0x80d5
            USHORT DeviceNameLength;            // 0x0010
            K_CHAR DeviceName [100];            // "\DEVICE\NDISWAN4"
          } *Parm = (ActivateRoute *)inputBuffer;

          // If device name is specified, replace it by underlying MP
          if (Parm->DeviceNameLength != 0xffff)
          {
            K_STRING Name = { Parm->DeviceNameLength * 2, Parm->DeviceNameLength * 2, Parm->DeviceName };

            // Look through NdisWan intermediate drivers
            NdisAcquireSpinLock (&NdisWanAdapterChainLock);

            AdapterControlBlock *cur = NdisWanAdapterChain;
            while (cur) 
            {
              if (!RtlCompareUnicodeString (&Name, &cur->IMDeviceName, TRUE))
              {
                // Copy the underlaying MP device name into buffer
                RtlMoveMemory (&Parm->DeviceName, cur->MPDeviceName.Buffer,
                  cur->MPDeviceName.Length);
                Name.Length = Name.MaximumLength = cur->MPDeviceName.Length;
                Parm->DeviceNameLength = cur->MPDeviceName.Length / 2;
                RtlUpcaseUnicodeString (&Name, &Name, FALSE);
                break;
              }
              cur = cur->NextNdisWan;
            }

            NdisReleaseSpinLock (&NdisWanAdapterChainLock);
          }
          break;
        }
      }
  }

  return IoCallDriver (NdisWanDevice, Irp);
}

That's it.

First, RAS architecture is designed to use a separate "network card" for each protocol that is routed through the modem; and also a separate "network card" for receiving calls and for dialing out. For example, if you set-up RAS to "Receive calls only" and select the TCP/IP and NetBEUI protocols to be routed through RAS you will end up with two NdisWan pseudo-network cards (in fact, you will end up with FOUR network cards but two of them are created regardless of RAS setup - one is the AsyncMac adapter and other is called somewhat jerky "BloodHound adapter"). If you set up RAS to receive dial in calls and dial out using TCP/IP, NetBEUI and IPX protocols you will end up with SIX NdisWan adapters (aside from those "special" two I mentioned above).

To intercept a specific protocol you should insert your driver between a protocol and one of NdisWan drivers. The installation instructions follows:
{"IntermediateTransport ndisWanAdapterDialIn non non 100", +
 "IntermediateTransport ndisWanAdapterDialOut non non 100", +
 "IntermediateTransport ndisWanAdapterDialInIP non non 100", +
 "IntermediateTransport ndisWanAdapterDialOutIP non non 100"}
NdisWan adapters have a specific "service name" string. The RASMAN (the top-level RAS manager that works with adapters) checks for "NdisWan" string to be part of service name - otherwise the network card is not considered an RAS adapter. So, you should name your adapter XXXXNdisWanXXXX where XXX stands for any text.
RASMAN checks for NdisWanDialin and NdisWanDialout strings to be part of service name to determine which adapters are used for dialing out and which for receiving calls. So, more specifically, your adapters should have ServiceName equal to XXXXNdisWanDialinXXXX or XXXXNdisWanDialoutXXXX service names.
Your intermediate protocol should bind to the NdisWan "network cards". To do this, you should add the binding rule for your protocol that tells NCPA to bind it to the NdisWanAdapterXXXs; this can look like this:
Medium type. Some people tried to report NdisMediumEthernet instead of NdisMediumWan in their MiniportInitialize() function. However, this is WRONG. Doing so just temporarily resolves some of the problems that arise. If you do so you will later encounter other problems. So, if the underlying NIC reports NdisMediumWan
NdisWAN is a NDIS3 miniport. DON'T implement any NDIS4-specific features for your adapter. For example, the IMSAMPLE intermediate driver sample that can be downloaded from microsoft's Web site implements MiniportSendPackets() function instead of NDIS3 MiniportSend() - this leads to a LOCKUP during netowork initialization because NDIS.SYS assumes that if driver reports its medium as NdisMediumWan it IS a NDIS3 adapter; when a protocols tries to send some initial broadcasts through your adapter NDIS.SYS queues the packets, then it sees that MiniportSend function is NULL and finally it loops until the send queue becomes empty. Obviously this will never happen.
RASMAN opens the \Device\NdisWan device and talks with that device using a microsoft proprietary API. Of course I don't know that API but under a debugger can be well seen that during connection phase RASMAN sends an IRP down to NdisWan requiring a specific adapter to enable (and send an ProtocolIndicateStatus with LINEUP indication) a specific WAN adapter; this IRP contains the name of YOUR intermediate driver but NdisWAN doesn't know that adapter, so it indicates error. To overcome this, you should intercept all IRPs sent to NdisWAN (fortunately this is possible by using IoAttachDevice () kernel-mode call). After this you should do the following:

Questions/comments:
Andrew Zabolotny <bit@eltech.ru>