ZeroTierOne/windows/TapDriver6/rxpath.c

670 lines
22 KiB
C

/*
* TAP-Windows -- A kernel driver to provide virtual tap
* device functionality on Windows.
*
* This code was inspired by the CIPE-Win32 driver by Damion K. Wilson.
*
* This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc.,
* and is released under the GPL version 2 (see below).
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
//
// Include files.
//
#include "tap.h"
//======================================================================
// TAP Receive Path Support
//======================================================================
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, TapDeviceWrite)
#endif // ALLOC_PRAGMA
//===============================================================
// Used in cases where internally generated packets such as
// ARP or DHCP replies must be returned to the kernel, to be
// seen as an incoming packet "arriving" on the interface.
//===============================================================
VOID
IndicateReceivePacket(
__in PTAP_ADAPTER_CONTEXT Adapter,
__in PUCHAR packetData,
__in const unsigned int packetLength
)
{
PUCHAR injectBuffer;
//
// Handle miniport Pause
// ---------------------
// NDIS 6 miniports implement a temporary "Pause" state normally followed
// by the Restart. While in the Pause state it is forbidden for the miniport
// to indicate receive NBLs.
//
// That is: The device interface may be "up", but the NDIS miniport send/receive
// interface may be temporarily "down".
//
// BUGBUG!!! In the initial implementation of the NDIS 6 TapOas inject path
// the code below will simply ignore inject packets passed to the driver while
// the miniport is in the Paused state.
//
// The correct implementation is to go ahead and build the NBLs corresponding
// to the inject packet - but queue them. When Restart is entered the
// queued NBLs would be dequeued and indicated to the host.
//
if(tapAdapterSendAndReceiveReady(Adapter) != NDIS_STATUS_SUCCESS)
{
DEBUGP (("[%s] Lying send in IndicateReceivePacket while adapter paused\n",
MINIPORT_INSTANCE_ID (Adapter)));
return;
}
// Allocate flat buffer for packet data.
injectBuffer = (PUCHAR )NdisAllocateMemoryWithTagPriority(
Adapter->MiniportAdapterHandle,
packetLength,
TAP_RX_INJECT_BUFFER_TAG,
NormalPoolPriority
);
if( injectBuffer)
{
PMDL mdl;
// Copy packet data to flat buffer.
NdisMoveMemory (injectBuffer, packetData, packetLength);
// Allocate MDL for flat buffer.
mdl = NdisAllocateMdl(
Adapter->MiniportAdapterHandle,
injectBuffer,
packetLength
);
if( mdl )
{
PNET_BUFFER_LIST netBufferList;
mdl->Next = NULL; // No next MDL
// Allocate the NBL and NB. Link MDL chain to NB.
netBufferList = NdisAllocateNetBufferAndNetBufferList(
Adapter->ReceiveNblPool,
0, // ContextSize
0, // ContextBackFill
mdl, // MDL chain
0,
packetLength
);
if(netBufferList != NULL)
{
ULONG receiveFlags = 0;
LONG nblCount;
NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL
if(KeGetCurrentIrql() == DISPATCH_LEVEL)
{
receiveFlags |= NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL;
}
// Set flag indicating that this is an injected packet
TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList);
TAP_RX_NBL_FLAG_SET(netBufferList,TAP_RX_NBL_FLAGS_IS_INJECTED);
netBufferList->MiniportReserved[0] = NULL;
netBufferList->MiniportReserved[1] = NULL;
// Increment in-flight receive NBL count.
nblCount = NdisInterlockedIncrement(&Adapter->ReceiveNblInFlightCount);
ASSERT(nblCount > 0 );
netBufferList->SourceHandle = Adapter->MiniportAdapterHandle;
//
// Indicate the packet
// -------------------
// Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length
// contains the complete packet including Ethernet header and payload.
//
NdisMIndicateReceiveNetBufferLists(
Adapter->MiniportAdapterHandle,
netBufferList,
NDIS_DEFAULT_PORT_NUMBER,
1, // NumberOfNetBufferLists
receiveFlags
);
return;
}
else
{
DEBUGP (("[%s] NdisAllocateNetBufferAndNetBufferList failed in IndicateReceivePacket\n",
MINIPORT_INSTANCE_ID (Adapter)));
NOTE_ERROR ();
NdisFreeMdl(mdl);
NdisFreeMemory(injectBuffer,0,0);
}
}
else
{
DEBUGP (("[%s] NdisAllocateMdl failed in IndicateReceivePacket\n",
MINIPORT_INSTANCE_ID (Adapter)));
NOTE_ERROR ();
NdisFreeMemory(injectBuffer,0,0);
}
}
else
{
DEBUGP (("[%s] NdisAllocateMemoryWithTagPriority failed in IndicateReceivePacket\n",
MINIPORT_INSTANCE_ID (Adapter)));
NOTE_ERROR ();
}
}
VOID
tapCompleteIrpAndFreeReceiveNetBufferList(
__in PTAP_ADAPTER_CONTEXT Adapter,
__in PNET_BUFFER_LIST NetBufferList, // Only one NB here...
__in NTSTATUS IoCompletionStatus
)
{
PIRP irp;
ULONG frameType, netBufferCount, byteCount;
LONG nblCount;
// Fetch NB frame type.
frameType = tapGetNetBufferFrameType(NET_BUFFER_LIST_FIRST_NB(NetBufferList));
// Fetch statistics for all NBs linked to the NB.
netBufferCount = tapGetNetBufferCountsFromNetBufferList(
NetBufferList,
&byteCount
);
// Update statistics by frame type
if(IoCompletionStatus == STATUS_SUCCESS)
{
switch(frameType)
{
case NDIS_PACKET_TYPE_DIRECTED:
Adapter->FramesRxDirected += netBufferCount;
Adapter->BytesRxDirected += byteCount;
break;
case NDIS_PACKET_TYPE_BROADCAST:
Adapter->FramesRxBroadcast += netBufferCount;
Adapter->BytesRxBroadcast += byteCount;
break;
case NDIS_PACKET_TYPE_MULTICAST:
Adapter->FramesRxMulticast += netBufferCount;
Adapter->BytesRxMulticast += byteCount;
break;
default:
ASSERT(FALSE);
break;
}
}
//
// Handle P2P Packet
// -----------------
// Free MDL allocated for P2P Ethernet header.
//
if(TAP_RX_NBL_FLAG_TEST(NetBufferList,TAP_RX_NBL_FLAGS_IS_P2P))
{
PNET_BUFFER netBuffer;
PMDL mdl;
netBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);
mdl = NET_BUFFER_FIRST_MDL(netBuffer);
mdl->Next = NULL;
NdisFreeMdl(mdl);
}
//
// Handle Injected Packet
// -----------------------
// Free MDL and data buffer allocated for injected packet.
//
if(TAP_RX_NBL_FLAG_TEST(NetBufferList,TAP_RX_NBL_FLAGS_IS_INJECTED))
{
PNET_BUFFER netBuffer;
PMDL mdl;
PUCHAR injectBuffer;
netBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);
mdl = NET_BUFFER_FIRST_MDL(netBuffer);
injectBuffer = (PUCHAR )MmGetSystemAddressForMdlSafe(mdl,NormalPagePriority);
if(injectBuffer)
{
NdisFreeMemory(injectBuffer,0,0);
}
NdisFreeMdl(mdl);
}
//
// Complete the IRP
//
irp = (PIRP )NetBufferList->MiniportReserved[0];
if(irp)
{
irp->IoStatus.Status = IoCompletionStatus;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
// Decrement in-flight receive NBL count.
nblCount = NdisInterlockedDecrement(&Adapter->ReceiveNblInFlightCount);
ASSERT(nblCount >= 0 );
if (0 == nblCount)
{
NdisSetEvent(&Adapter->ReceiveNblInFlightCountZeroEvent);
}
// Free the NBL
NdisFreeNetBufferList(NetBufferList);
}
VOID
AdapterReturnNetBufferLists(
__in NDIS_HANDLE MiniportAdapterContext,
__in PNET_BUFFER_LIST NetBufferLists,
__in ULONG ReturnFlags
)
{
PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;
PNET_BUFFER_LIST currentNbl, nextNbl;
UNREFERENCED_PARAMETER(ReturnFlags);
//
// Process each NBL individually
//
currentNbl = NetBufferLists;
while (currentNbl)
{
PNET_BUFFER_LIST nextNbl;
nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl);
NET_BUFFER_LIST_NEXT_NBL(currentNbl) = NULL;
// Complete write IRP and free NBL and associated resources.
tapCompleteIrpAndFreeReceiveNetBufferList(
adapter,
currentNbl,
STATUS_SUCCESS
);
// Move to next NBL
currentNbl = nextNbl;
}
}
// IRP_MJ_WRITE callback.
NTSTATUS
TapDeviceWrite(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success
PIO_STACK_LOCATION irpSp;// Pointer to current stack location
PTAP_ADAPTER_CONTEXT adapter = NULL;
ULONG dataLength;
PAGED_CODE();
irpSp = IoGetCurrentIrpStackLocation( Irp );
//
// Fetch adapter context for this device.
// --------------------------------------
// Adapter pointer was stashed in FsContext when handle was opened.
//
adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext;
ASSERT(adapter);
//
// Sanity checks on state variables
//
if (!tapAdapterReadAndWriteReady(adapter))
{
//DEBUGP (("[%s] Interface is down in IRP_MJ_WRITE\n",
// MINIPORT_INSTANCE_ID (adapter)));
//NOTE_ERROR();
Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return ntStatus;
}
// Save IRP-accessible copy of buffer length
Irp->IoStatus.Information = irpSp->Parameters.Write.Length;
if (Irp->MdlAddress == NULL)
{
DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_WRITE\n",
MINIPORT_INSTANCE_ID (adapter)));
NOTE_ERROR();
Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return ntStatus;
}
//
// Try to get a virtual address for the MDL.
//
NdisQueryMdl(
Irp->MdlAddress,
&Irp->AssociatedIrp.SystemBuffer,
&dataLength,
NormalPagePriority
);
if (Irp->AssociatedIrp.SystemBuffer == NULL)
{
DEBUGP (("[%s] Could not map address in IRP_MJ_WRITE\n",
MINIPORT_INSTANCE_ID (adapter)));
NOTE_ERROR();
Irp->IoStatus.Status = ntStatus = STATUS_INSUFFICIENT_RESOURCES;
Irp->IoStatus.Information = 0;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return ntStatus;
}
ASSERT(dataLength == irpSp->Parameters.Write.Length);
Irp->IoStatus.Information = irpSp->Parameters.Write.Length;
//
// Handle miniport Pause
// ---------------------
// NDIS 6 miniports implement a temporary "Pause" state normally followed
// by the Restart. While in the Pause state it is forbidden for the miniport
// to indicate receive NBLs.
//
// That is: The device interface may be "up", but the NDIS miniport send/receive
// interface may be temporarily "down".
//
// BUGBUG!!! In the initial implementation of the NDIS 6 TapOas receive path
// the code below will perform a "lying send" for write IRPs passed to the
// driver while the miniport is in the Paused state.
//
// The correct implementation is to go ahead and build the NBLs corresponding
// to the user-mode write - but queue them. When Restart is entered the
// queued NBLs would be dequeued and indicated to the host.
//
if(tapAdapterSendAndReceiveReady(adapter) == NDIS_STATUS_SUCCESS)
{
if (/*!adapter->m_tun &&*/ ((irpSp->Parameters.Write.Length) >= ETHERNET_HEADER_SIZE))
{
PNET_BUFFER_LIST netBufferList;
DUMP_PACKET ("IRP_MJ_WRITE ETH",
(unsigned char *) Irp->AssociatedIrp.SystemBuffer,
irpSp->Parameters.Write.Length);
//=====================================================
// If IPv4 packet, check whether or not packet
// was truncated.
//=====================================================
#if PACKET_TRUNCATION_CHECK
IPv4PacketSizeVerify (
(unsigned char *) Irp->AssociatedIrp.SystemBuffer,
irpSp->Parameters.Write.Length,
FALSE,
"RX",
&adapter->m_RxTrunc
);
#endif
(Irp->MdlAddress)->Next = NULL; // No next MDL
// Allocate the NBL and NB. Link MDL chain to NB.
netBufferList = NdisAllocateNetBufferAndNetBufferList(
adapter->ReceiveNblPool,
0, // ContextSize
0, // ContextBackFill
Irp->MdlAddress, // MDL chain
0,
dataLength
);
if(netBufferList != NULL)
{
LONG nblCount;
NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL
// Stash IRP pointer in NBL MiniportReserved[0] field.
netBufferList->MiniportReserved[0] = Irp;
netBufferList->MiniportReserved[1] = NULL;
// This IRP is pended.
IoMarkIrpPending(Irp);
// This IRP cannot be cancelled while in-flight.
IoSetCancelRoutine(Irp,NULL);
TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList);
// Increment in-flight receive NBL count.
nblCount = NdisInterlockedIncrement(&adapter->ReceiveNblInFlightCount);
ASSERT(nblCount > 0 );
//
// Indicate the packet
// -------------------
// Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length
// contains the complete packet including Ethernet header and payload.
//
NdisMIndicateReceiveNetBufferLists(
adapter->MiniportAdapterHandle,
netBufferList,
NDIS_DEFAULT_PORT_NUMBER,
1, // NumberOfNetBufferLists
0 // ReceiveFlags
);
ntStatus = STATUS_PENDING;
}
else
{
DEBUGP (("[%s] NdisMIndicateReceiveNetBufferLists failed in IRP_MJ_WRITE\n",
MINIPORT_INSTANCE_ID (adapter)));
NOTE_ERROR ();
// Fail the IRP
Irp->IoStatus.Information = 0;
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
}
}
/*
else if (adapter->m_tun && ((irpSp->Parameters.Write.Length) >= IP_HEADER_SIZE))
{
PETH_HEADER p_UserToTap = &adapter->m_UserToTap;
PMDL mdl; // Head of MDL chain.
// For IPv6, need to use Ethernet header with IPv6 proto
if ( IPH_GET_VER( ((IPHDR*) Irp->AssociatedIrp.SystemBuffer)->version_len) == 6 )
{
p_UserToTap = &adapter->m_UserToTap_IPv6;
}
DUMP_PACKET2 ("IRP_MJ_WRITE P2P",
p_UserToTap,
(unsigned char *) Irp->AssociatedIrp.SystemBuffer,
irpSp->Parameters.Write.Length);
//=====================================================
// If IPv4 packet, check whether or not packet
// was truncated.
//=====================================================
#if PACKET_TRUNCATION_CHECK
IPv4PacketSizeVerify (
(unsigned char *) Irp->AssociatedIrp.SystemBuffer,
irpSp->Parameters.Write.Length,
TRUE,
"RX",
&adapter->m_RxTrunc
);
#endif
//
// Allocate MDL for Ethernet header
// --------------------------------
// Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length
// contains the only the Ethernet payload. Prepend the user-mode provided
// payload with the Ethernet header pointed to by p_UserToTap.
//
mdl = NdisAllocateMdl(
adapter->MiniportAdapterHandle,
p_UserToTap,
sizeof(ETH_HEADER)
);
if(mdl != NULL)
{
PNET_BUFFER_LIST netBufferList;
// Chain user's Ethernet payload behind Ethernet header.
mdl->Next = Irp->MdlAddress;
(Irp->MdlAddress)->Next = NULL; // No next MDL
// Allocate the NBL and NB. Link MDL chain to NB.
netBufferList = NdisAllocateNetBufferAndNetBufferList(
adapter->ReceiveNblPool,
0, // ContextSize
0, // ContextBackFill
mdl, // MDL chain
0,
sizeof(ETH_HEADER) + dataLength
);
if(netBufferList != NULL)
{
LONG nblCount;
NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL
// This IRP is pended.
IoMarkIrpPending(Irp);
// This IRP cannot be cancelled while in-flight.
IoSetCancelRoutine(Irp,NULL);
// Stash IRP pointer in NBL MiniportReserved[0] field.
netBufferList->MiniportReserved[0] = Irp;
netBufferList->MiniportReserved[1] = NULL;
// Set flag indicating that this is P2P packet
TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList);
TAP_RX_NBL_FLAG_SET(netBufferList,TAP_RX_NBL_FLAGS_IS_P2P);
// Increment in-flight receive NBL count.
nblCount = NdisInterlockedIncrement(&adapter->ReceiveNblInFlightCount);
ASSERT(nblCount > 0 );
//
// Indicate the packet
//
NdisMIndicateReceiveNetBufferLists(
adapter->MiniportAdapterHandle,
netBufferList,
NDIS_DEFAULT_PORT_NUMBER,
1, // NumberOfNetBufferLists
0 // ReceiveFlags
);
ntStatus = STATUS_PENDING;
}
else
{
mdl->Next = NULL;
NdisFreeMdl(mdl);
DEBUGP (("[%s] NdisMIndicateReceiveNetBufferLists failed in IRP_MJ_WRITE\n",
MINIPORT_INSTANCE_ID (adapter)));
NOTE_ERROR ();
// Fail the IRP
Irp->IoStatus.Information = 0;
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
}
}
else
{
DEBUGP (("[%s] NdisAllocateMdl failed in IRP_MJ_WRITE\n",
MINIPORT_INSTANCE_ID (adapter)));
NOTE_ERROR ();
// Fail the IRP
Irp->IoStatus.Information = 0;
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
}
}
*/
else
{
DEBUGP (("[%s] Bad buffer size in IRP_MJ_WRITE, len=%d\n",
MINIPORT_INSTANCE_ID (adapter),
irpSp->Parameters.Write.Length));
NOTE_ERROR ();
Irp->IoStatus.Information = 0; // ETHERNET_HEADER_SIZE;
Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL;
}
}
else
{
DEBUGP (("[%s] Lying send in IRP_MJ_WRITE while adapter paused\n",
MINIPORT_INSTANCE_ID (adapter)));
ntStatus = STATUS_SUCCESS;
}
if (ntStatus != STATUS_PENDING)
{
Irp->IoStatus.Status = ntStatus;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
return ntStatus;
}