/*
*  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-2010 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
*/

//======================================================
// This driver is designed to work on Win 2000 or higher
// versions of Windows.
//
// It is SMP-safe and handles NDIS 5 power management.
//
// By default we operate as a "tap" virtual ethernet
// 802.3 interface, but we can emulate a "tun"
// interface (point-to-point IPv4) through the
// TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT or
// TAP_WIN_IOCTL_CONFIG_TUN ioctl.
//======================================================

#include "tap-windows.h"
#include "config.h"

#define NDIS_MINIPORT_DRIVER
#define BINARY_COMPATIBLE 0
#define NDIS50_MINIPORT 1
#define NDIS_WDM 0
#define NDIS50 1
#define NTSTRSAFE_LIB

// Debug info output
#define ALSO_DBGPRINT           1
#define DEBUGP_AT_DISPATCH      0

#if defined(DDKVER_MAJOR) && DDKVER_MAJOR < 5600
#include <ndis.h>
#include <ntstrsafe.h>
#include <ntddk.h>
#else
#include <ntifs.h>
#include <ndis.h>
#include <ntstrsafe.h>
#endif

#include "lock.h"
#include "constants.h"
#include "proto.h"
#include "error.h"
#include "endian.h"
#include "types.h"
#include "prototypes.h"

#include "mem.c"
#include "macinfo.c"
#include "error.c"
#include "instance.c"

#define IS_UP(ta) \
	((ta)->m_InterfaceIsRunning && (ta)->m_Extension.m_TapIsRunning)

#define INCREMENT_STAT(s) ++(s)

#define NAME_BUFFER_SIZE 80

//========================================================
//                            Globals
//========================================================

NDIS_HANDLE g_NdisWrapperHandle;

const UINT g_SupportedOIDList[] = {
	OID_GEN_HARDWARE_STATUS,
	OID_GEN_MEDIA_SUPPORTED,
	OID_GEN_MEDIA_IN_USE,
	OID_GEN_MAXIMUM_LOOKAHEAD,
	OID_GEN_MAC_OPTIONS,
	OID_GEN_LINK_SPEED,
	OID_GEN_TRANSMIT_BLOCK_SIZE,
	OID_GEN_RECEIVE_BLOCK_SIZE,
	OID_GEN_VENDOR_DESCRIPTION,
	OID_GEN_DRIVER_VERSION,
	OID_GEN_XMIT_OK,
	OID_GEN_RCV_OK,
	OID_GEN_XMIT_ERROR,
	OID_GEN_RCV_ERROR,
	OID_802_3_PERMANENT_ADDRESS,
	OID_802_3_CURRENT_ADDRESS,
	OID_GEN_RCV_NO_BUFFER,
	OID_802_3_RCV_ERROR_ALIGNMENT,
	OID_802_3_XMIT_ONE_COLLISION,
	OID_802_3_XMIT_MORE_COLLISIONS,
	OID_802_3_MULTICAST_LIST,
	OID_802_3_MAXIMUM_LIST_SIZE,
	OID_GEN_VENDOR_ID,
	OID_GEN_CURRENT_LOOKAHEAD,
	OID_GEN_CURRENT_PACKET_FILTER,
	OID_GEN_PROTOCOL_OPTIONS,
	OID_GEN_MAXIMUM_TOTAL_SIZE,
	OID_GEN_TRANSMIT_BUFFER_SPACE,
	OID_GEN_RECEIVE_BUFFER_SPACE,
	OID_GEN_MAXIMUM_FRAME_SIZE,
	OID_GEN_VENDOR_DRIVER_VERSION,
	OID_GEN_MAXIMUM_SEND_PACKETS,
	OID_GEN_MEDIA_CONNECT_STATUS,
	OID_GEN_SUPPORTED_LIST
};

//============================================================
//                         Driver Entry
//============================================================
#pragma NDIS_INIT_FUNCTION (DriverEntry)

DRIVER_INITIALIZE DriverEntry;
NTSTATUS
	DriverEntry (IN PDRIVER_OBJECT p_DriverObject,
	IN PUNICODE_STRING p_RegistryPath)
{
	NDIS_STATUS l_Status = NDIS_STATUS_FAILURE;
	NDIS_MINIPORT_CHARACTERISTICS *l_Properties = NULL;

	//========================================================
	// Notify NDIS that a new miniport driver is initializing.
	//========================================================

	NdisMInitializeWrapper (&g_NdisWrapperHandle,
		p_DriverObject,
		p_RegistryPath, NULL);

	//======================
	// Global initialization
	//======================

#if DBG
	MyDebugInit (10000); // Allocate debugging text space
#endif

	if (!InitInstanceList ())
	{
		DEBUGP (("[TAP] Allocation failed for adapter instance list\n"));
		goto cleanup;
	}

	//=======================================
	// Set and register miniport entry points
	//=======================================

	l_Properties = (NDIS_MINIPORT_CHARACTERISTICS *)MemAlloc (sizeof (NDIS_MINIPORT_CHARACTERISTICS), TRUE);

	if (l_Properties == NULL)
	{
		DEBUGP (("[TAP] Allocation failed for miniport entry points\n"));
		goto cleanup;
	}

	l_Properties->MajorNdisVersion = TAP_NDIS_MAJOR_VERSION;
	l_Properties->MinorNdisVersion = TAP_NDIS_MINOR_VERSION;
	l_Properties->InitializeHandler = AdapterCreate;
	l_Properties->HaltHandler = AdapterHalt;
	l_Properties->ResetHandler = AdapterReset;               /* DISPATCH_LEVEL */
	l_Properties->TransferDataHandler = AdapterReceive;      /* DISPATCH_LEVEL */
	l_Properties->SendHandler = AdapterTransmit;             /* DISPATCH_LEVEL */
	l_Properties->QueryInformationHandler = AdapterQuery;    /* DISPATCH_LEVEL */
	l_Properties->SetInformationHandler = AdapterModify;     /* DISPATCH_LEVEL */

	switch (l_Status =
		NdisMRegisterMiniport (g_NdisWrapperHandle, l_Properties,
		sizeof (NDIS_MINIPORT_CHARACTERISTICS)))
	{
	case NDIS_STATUS_SUCCESS:
		{
			DEBUGP (("[TAP] version [%d.%d] %s %s registered miniport successfully\n",
				TAP_DRIVER_MAJOR_VERSION,
				TAP_DRIVER_MINOR_VERSION,
				__DATE__,
				__TIME__));
			DEBUGP (("Registry Path: '%.*S'\n", p_RegistryPath->Length/2, p_RegistryPath->Buffer));
			break;
		}

	case NDIS_STATUS_BAD_CHARACTERISTICS:
		{
			DEBUGP (("[TAP] Miniport characteristics were badly defined\n"));
			NdisTerminateWrapper (g_NdisWrapperHandle, NULL);
			break;
		}

	case NDIS_STATUS_BAD_VERSION:
		{
			DEBUGP
				(("[TAP] NDIS Version is wrong for the given characteristics\n"));
			NdisTerminateWrapper (g_NdisWrapperHandle, NULL);
			break;
		}

	case NDIS_STATUS_RESOURCES:
		{
			DEBUGP (("[TAP] Insufficient resources\n"));
			NdisTerminateWrapper (g_NdisWrapperHandle, NULL);
			break;
		}

	default:
	case NDIS_STATUS_FAILURE:
		{
			DEBUGP (("[TAP] Unknown fatal registration error\n"));
			NdisTerminateWrapper (g_NdisWrapperHandle, NULL);
			break;
		}
	}

cleanup:
	if (l_Properties)
		MemFree (l_Properties, sizeof (NDIS_MINIPORT_CHARACTERISTICS));

	if (l_Status == NDIS_STATUS_SUCCESS)
		NdisMRegisterUnloadHandler (g_NdisWrapperHandle, TapDriverUnload);
	else
		TapDriverUnload (p_DriverObject);

	return l_Status;
}

//============================================================
//                         Driver Unload
//============================================================
DRIVER_UNLOAD TapDriverUnload;
VOID 
	TapDriverUnload (IN PDRIVER_OBJECT p_DriverObject)
{
	DEBUGP (("[TAP] version [%d.%d] %s %s unloaded, instances=%d, imbs=%d\n",
		TAP_DRIVER_MAJOR_VERSION,
		TAP_DRIVER_MINOR_VERSION,
		__DATE__,
		__TIME__,
		NInstances(),
		InstanceMaxBucketSize()));

	FreeInstanceList ();

	//==============================
	// Free debugging text space
	//==============================
#if DBG
	MyDebugFree ();
#endif
}

//==========================================================
//                            Adapter Initialization
//==========================================================
NDIS_STATUS AdapterCreate
	(OUT PNDIS_STATUS p_ErrorStatus,
	OUT PUINT p_MediaIndex,
	IN PNDIS_MEDIUM p_Media,
	IN UINT p_MediaCount,
	IN NDIS_HANDLE p_AdapterHandle,
	IN NDIS_HANDLE p_ConfigurationHandle)
{
	TapAdapterPointer l_Adapter = NULL;

	NDIS_MEDIUM l_PreferredMedium = NdisMedium802_3; // Ethernet
	BOOLEAN l_MacFromRegistry = FALSE;
	UINT l_Index;
	NDIS_STATUS status;

	DEBUGP (("[TAP] AdapterCreate called\n"));

	//====================================
	// Make sure adapter type is supported
	//====================================

	for (l_Index = 0;
		l_Index < p_MediaCount && p_Media[l_Index] != l_PreferredMedium;
		++l_Index);

	if (l_Index == p_MediaCount)
	{
		DEBUGP (("[TAP] Unsupported adapter type [wanted: %d]\n",
			l_PreferredMedium));
		return NDIS_STATUS_UNSUPPORTED_MEDIA;
	}

	*p_MediaIndex = l_Index;

	//=========================================
	// Allocate memory for TapAdapter structure
	//=========================================

	l_Adapter = MemAlloc (sizeof (TapAdapter), TRUE);

	if (l_Adapter == NULL)
	{
		DEBUGP (("[TAP] Couldn't allocate adapter memory\n"));
		return NDIS_STATUS_RESOURCES;
	}

	//==========================================
	// Inform the NDIS library about significant
	// features of our virtual NIC.
	//==========================================

	NdisMSetAttributesEx
		(p_AdapterHandle,
		(NDIS_HANDLE) l_Adapter,
		16,
		NDIS_ATTRIBUTE_DESERIALIZE
		| NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT
		| NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT
		| NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND,
		NdisInterfaceInternal);

	//=====================================
	// Initialize simple Adapter parameters
	//=====================================

	l_Adapter->m_Lookahead = DEFAULT_PACKET_LOOKAHEAD;
	l_Adapter->m_Medium = l_PreferredMedium;
	l_Adapter->m_DeviceState = '?';
	l_Adapter->m_MiniportAdapterHandle = p_AdapterHandle;

	//==================================
	// Allocate spinlock for controlling
	// access to multicast address list.
	//==================================
	NdisAllocateSpinLock (&l_Adapter->m_MCLock);
	l_Adapter->m_MCLockAllocated = TRUE;

	//====================================================
	// Register a shutdown handler which will be called
	// on system restart/shutdown to halt our virtual NIC.
	//====================================================

	NdisMRegisterAdapterShutdownHandler (p_AdapterHandle, l_Adapter,
		AdapterHalt);
	l_Adapter->m_RegisteredAdapterShutdownHandler = TRUE;

	//============================================
	// Get parameters from registry which were set
	// in the adapter advanced properties dialog.
	//============================================
	{
		NDIS_STATUS status;
		NDIS_HANDLE configHandle;
		NDIS_CONFIGURATION_PARAMETER *parm;

		// set defaults in case our registry query fails
		l_Adapter->m_MTU = ETHERNET_MTU;
		l_Adapter->m_MediaStateAlwaysConnected = FALSE;
		l_Adapter->m_MediaState = FALSE;

		NdisOpenConfiguration (&status, &configHandle, p_ConfigurationHandle);
		if (status != NDIS_STATUS_SUCCESS)
		{
			DEBUGP (("[TAP] Couldn't open adapter registry\n"));
			AdapterFreeResources (l_Adapter);
			return status;
		}

		//====================================
		// Allocate and construct adapter name
		//====================================
		{

			NDIS_STRING mkey = NDIS_STRING_CONST("MiniportName");
			NDIS_STRING vkey = NDIS_STRING_CONST("NdisVersion");
			NDIS_STATUS vstatus;
			NDIS_CONFIGURATION_PARAMETER *vparm;

			NdisReadConfiguration (&vstatus, &vparm, configHandle, &vkey, NdisParameterInteger);
			if (vstatus == NDIS_STATUS_SUCCESS)
				DEBUGP (("[TAP] NdisReadConfiguration NdisVersion=%X\n", vparm->ParameterData.IntegerData));

			NdisReadConfiguration (&status, &parm, configHandle, &mkey, NdisParameterString);
			if (status == NDIS_STATUS_SUCCESS)
			{
				if (parm->ParameterType == NdisParameterString)
				{
					DEBUGP (("[TAP] NdisReadConfiguration (MiniportName=%.*S)\n",
						parm->ParameterData.StringData.Length/2,
						parm->ParameterData.StringData.Buffer));

					if (RtlUnicodeStringToAnsiString (
						&l_Adapter->m_NameAnsi,
						&parm->ParameterData.StringData,
						TRUE) != STATUS_SUCCESS)
					{
						DEBUGP (("[TAP] MiniportName failed\n"));
						status = NDIS_STATUS_RESOURCES;
					}
				}
			}
			else
			{
				/* "MiniportName" is available only XP and above.  Not on Windows 2000. */
				if (vstatus == NDIS_STATUS_SUCCESS && vparm->ParameterData.IntegerData == 0x50000)
				{
					/* Fallback for Windows 2000 with NDIS version 5.00.00
					Don't use this on Vista, 'NDIS_MINIPORT_BLOCK' was changed! */
					if (RtlUnicodeStringToAnsiString (&l_Adapter->m_NameAnsi,
						&((struct WIN2K_NDIS_MINIPORT_BLOCK *) p_AdapterHandle)->MiniportName,
						TRUE) != STATUS_SUCCESS)
					{
						DEBUGP (("[TAP] MiniportName (W2K) failed\n"));
						status = NDIS_STATUS_RESOURCES;
					}
					else
					{
						DEBUGP (("[TAP] MiniportName (W2K) succeeded: %s\n", l_Adapter->m_NameAnsi.Buffer));
						status = NDIS_STATUS_SUCCESS;
					}
				}
			}
		}

		/* Can't continue without name (see macro 'NAME') */
		if (status != NDIS_STATUS_SUCCESS || !l_Adapter->m_NameAnsi.Buffer)
		{
			NdisCloseConfiguration (configHandle);
			AdapterFreeResources (l_Adapter);
			DEBUGP (("[TAP] failed to get miniport name\n"));
			return NDIS_STATUS_RESOURCES;
		}

		/* Read MTU setting from registry */
		{
			NDIS_STRING key = NDIS_STRING_CONST("MTU");
			NdisReadConfiguration (&status, &parm, configHandle,
				&key, NdisParameterInteger);
			if (status == NDIS_STATUS_SUCCESS)
			{
				if (parm->ParameterType == NdisParameterInteger)
				{
					int mtu = parm->ParameterData.IntegerData;
					if (mtu < MINIMUM_MTU)
						mtu = MINIMUM_MTU;
					if (mtu > MAXIMUM_MTU)
						mtu = MAXIMUM_MTU;
					l_Adapter->m_MTU = mtu;
				}
			}
		}

		/* Read Media Status setting from registry */
		{
			NDIS_STRING key = NDIS_STRING_CONST("MediaStatus");
			NdisReadConfiguration (&status, &parm, configHandle,
				&key, NdisParameterInteger);
			if (status == NDIS_STATUS_SUCCESS)
			{
				if (parm->ParameterType == NdisParameterInteger)
				{
					if (parm->ParameterData.IntegerData)
					{
						l_Adapter->m_MediaStateAlwaysConnected = TRUE;
						l_Adapter->m_MediaState = TRUE;
					}
				}
			}
		}

		/* Read optional MAC setting from registry */
		{
			NDIS_STRING key = NDIS_STRING_CONST("MAC");
			ANSI_STRING mac_string;
			NdisReadConfiguration (&status, &parm, configHandle,
				&key, NdisParameterString);
			if (status == NDIS_STATUS_SUCCESS)
			{
				if (parm->ParameterType == NdisParameterString)
				{
					if (RtlUnicodeStringToAnsiString (&mac_string, &parm->ParameterData.StringData, TRUE) == STATUS_SUCCESS)
					{
						l_MacFromRegistry = ParseMAC (l_Adapter->m_MAC, mac_string.Buffer);
						RtlFreeAnsiString (&mac_string);
					}
				}
			}
		}

		NdisCloseConfiguration (configHandle);

		DEBUGP (("[%s] MTU=%d\n", NAME (l_Adapter), l_Adapter->m_MTU));
	}

	//==================================
	// Store and update MAC address info
	//==================================

	if (!l_MacFromRegistry)
		GenerateRandomMac (l_Adapter->m_MAC, (const unsigned char *)NAME (l_Adapter));

	DEBUGP (("[%s] Using MAC %x:%x:%x:%x:%x:%x\n",
		NAME (l_Adapter),
		l_Adapter->m_MAC[0], l_Adapter->m_MAC[1], l_Adapter->m_MAC[2],
		l_Adapter->m_MAC[3], l_Adapter->m_MAC[4], l_Adapter->m_MAC[5]));

	//====================================
	// Initialize TAP device
	//====================================
	{
		NDIS_STATUS tap_status;
		tap_status = CreateTapDevice (&l_Adapter->m_Extension, NAME (l_Adapter));
		if (tap_status != NDIS_STATUS_SUCCESS)
		{
			AdapterFreeResources (l_Adapter);
			DEBUGP (("[TAP] CreateTapDevice failed\n"));
			return tap_status;
		}
	}

	if (!AddAdapterToInstanceList (l_Adapter))
	{
		NOTE_ERROR ();
		TapDeviceFreeResources (&l_Adapter->m_Extension);
		AdapterFreeResources (l_Adapter);
		DEBUGP (("[TAP] AddAdapterToInstanceList failed\n"));
		return NDIS_STATUS_RESOURCES;
	}

	l_Adapter->m_InterfaceIsRunning = TRUE;

	return NDIS_STATUS_SUCCESS;
}

VOID
	AdapterHalt (IN NDIS_HANDLE p_AdapterContext)
{
	BOOLEAN status;

	TapAdapterPointer l_Adapter = (TapAdapterPointer) p_AdapterContext;

	NOTE_ERROR ();

	l_Adapter->m_InterfaceIsRunning = FALSE;

	DEBUGP (("[%s] is being halted\n", NAME (l_Adapter)));

	DestroyTapDevice (&l_Adapter->m_Extension);

	// Free resources
	DEBUGP (("[%s] Freeing Resources\n", NAME (l_Adapter)));
	AdapterFreeResources (l_Adapter);

	status = RemoveAdapterFromInstanceList (l_Adapter);
	DEBUGP (("[TAP] RemoveAdapterFromInstanceList returned %d\n", (int) status));

	DEBUGP (("[TAP] version [%d.%d] %s %s AdapterHalt returning\n",
		TAP_DRIVER_MAJOR_VERSION,
		TAP_DRIVER_MINOR_VERSION,
		__DATE__,
		__TIME__));
}

VOID
	AdapterFreeResources (TapAdapterPointer p_Adapter)
{
	MYASSERT (!p_Adapter->m_CalledAdapterFreeResources);
	p_Adapter->m_CalledAdapterFreeResources = TRUE;

	if (p_Adapter->m_NameAnsi.Buffer)
		RtlFreeAnsiString (&p_Adapter->m_NameAnsi);

	if (p_Adapter->m_RegisteredAdapterShutdownHandler)
		NdisMDeregisterAdapterShutdownHandler (p_Adapter->m_MiniportAdapterHandle);

	if (p_Adapter->m_MCLockAllocated)
		NdisFreeSpinLock (&p_Adapter->m_MCLock);
}

VOID
	DestroyTapDevice (TapExtensionPointer p_Extension)
{
	DEBUGP (("[%s] Destroying tap device\n", p_Extension->m_TapName));

	//======================================
	// Let clients know we are shutting down
	//======================================
	p_Extension->m_TapIsRunning = FALSE;
	p_Extension->m_TapOpens = 0;
	p_Extension->m_Halt = TRUE;

	//=====================================
	// If we are concurrently executing in
	// TapDeviceHook or AdapterTransmit,
	// give those calls time to finish.
	// Note that we must be running at IRQL
	// < DISPATCH_LEVEL in order to call
	// NdisMSleep.
	//=====================================
	NdisMSleep (500000);

	//===========================================================
	// Exhaust IRP and packet queues.  Any pending IRPs will
	// be cancelled, causing user-space to get this error
	// on overlapped reads:
	//   The I/O operation has been aborted because of either a
	//   thread exit or an application request.   (code=995)
	// It's important that user-space close the device handle
	// when this code is returned, so that when we finally
	// do a NdisMDeregisterDevice, the device reference count
	// is 0.  Otherwise the driver will not unload even if the
	// the last adapter has been halted.
	//===========================================================
	FlushQueues (p_Extension);
	NdisMSleep (500000); // give user space time to respond to IRP cancel

	TapDeviceFreeResources (p_Extension);
}

VOID
	TapDeviceFreeResources (TapExtensionPointer p_Extension)
{
	MYASSERT (p_Extension);
	MYASSERT (!p_Extension->m_CalledTapDeviceFreeResources);
	p_Extension->m_CalledTapDeviceFreeResources = TRUE;

	if (p_Extension->m_PacketQueue)
		QueueFree (p_Extension->m_PacketQueue);
	if (p_Extension->m_IrpQueue)
		QueueFree (p_Extension->m_IrpQueue);

	if (p_Extension->m_CreatedUnicodeLinkName)
		RtlFreeUnicodeString (&p_Extension->m_UnicodeLinkName);

	//==========================================================
	// According to DDK docs, the device is not actually deleted
	// until its reference count falls to zero.  That means we
	// still need to gracefully fail TapDeviceHook requests
	// after this point, otherwise ugly things would happen if
	// the device was disabled (e.g. in the network connections
	// control panel) while a userspace app still held an open
	// file handle to it.
	//==========================================================

	if (p_Extension->m_TapDevice)
	{
		BOOLEAN status;
		status = (NdisMDeregisterDevice (p_Extension->m_TapDeviceHandle)
			== NDIS_STATUS_SUCCESS);
		DEBUGP (("[TAP] Deregistering TAP device, status=%d\n", (int)status));
	}

	if (p_Extension->m_TapName)
		MemFree (p_Extension->m_TapName, NAME_BUFFER_SIZE);

	if (p_Extension->m_AllocatedSpinlocks)
	{
		NdisFreeSpinLock (&p_Extension->m_QueueLock);
	}
}

//========================================================================
//                             Tap Device Initialization
//========================================================================

NDIS_STATUS
	CreateTapDevice (TapExtensionPointer p_Extension, const char *p_Name)
{
# define SIZEOF_DISPATCH (sizeof(PDRIVER_DISPATCH) * (IRP_MJ_MAXIMUM_FUNCTION + 1))
	PDRIVER_DISPATCH *l_Dispatch = NULL;
	ANSI_STRING l_TapString, l_LinkString;
	UNICODE_STRING l_TapUnicode;
	BOOLEAN l_FreeTapUnicode = FALSE;
	NTSTATUS l_Status, l_Return = NDIS_STATUS_SUCCESS;
	const char *l_UsableName;

	DEBUGP (("[TAP] version [%d.%d] creating tap device: %s\n",
		TAP_DRIVER_MAJOR_VERSION,
		TAP_DRIVER_MINOR_VERSION,
		p_Name));

	NdisZeroMemory (p_Extension, sizeof (TapExtension));

	INIT_MUTEX (&p_Extension->m_OpenCloseMutex);

	l_LinkString.Buffer = NULL;
	l_TapString.Buffer = NULL;

	l_TapString.MaximumLength = l_LinkString.MaximumLength = NAME_BUFFER_SIZE;

	//=======================================
	// Set TAP device entry points
	//=======================================

	if ((l_Dispatch = MemAlloc (SIZEOF_DISPATCH, TRUE)) == NULL)
	{
		DEBUGP (("[%s] couldn't alloc TAP dispatch table\n", p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}

	l_Dispatch[IRP_MJ_DEVICE_CONTROL] = TapDeviceHook;
	l_Dispatch[IRP_MJ_READ] = TapDeviceHook;
	l_Dispatch[IRP_MJ_WRITE] = TapDeviceHook;
	l_Dispatch[IRP_MJ_CREATE] = TapDeviceHook;
	l_Dispatch[IRP_MJ_CLOSE] = TapDeviceHook;

	//==================================
	// Find the beginning of the GUID
	//==================================
	l_UsableName = p_Name;
	while (*l_UsableName != '{')
	{
		if (*l_UsableName == '\0')
		{
			DEBUGP (("[%s] couldn't find leading '{' in name\n", p_Name));
			l_Return = NDIS_STATUS_RESOURCES;
			goto cleanup;
		}
		++l_UsableName;
	}

	//==================================
	// Allocate pool for TAP device name
	//==================================

	if ((p_Extension->m_TapName = l_TapString.Buffer =
		MemAlloc (NAME_BUFFER_SIZE, TRUE)) == NULL)
	{
		DEBUGP (("[%s] couldn't alloc TAP name buffer\n", p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}

	//================================================
	// Allocate pool for TAP symbolic link name buffer
	//================================================

	if ((l_LinkString.Buffer =
		(PCHAR)MemAlloc (NAME_BUFFER_SIZE, TRUE)) == NULL)
	{
		DEBUGP (("[%s] couldn't alloc TAP symbolic link name buffer\n",
			p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}

	//=======================================================
	// Set TAP device name
	//=======================================================

	l_Status = RtlStringCchPrintfExA
		(l_TapString.Buffer,
		l_TapString.MaximumLength,
		NULL,
		NULL,
		STRSAFE_FILL_BEHIND_NULL | STRSAFE_IGNORE_NULLS,
		"%s%s%s",
		SYSDEVICEDIR,
		l_UsableName,
		TAP_WIN_SUFFIX);

	if (l_Status != STATUS_SUCCESS)
	{
		DEBUGP (("[%s] couldn't format TAP device name\n",
			p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}
	l_TapString.Length = (USHORT) strlen (l_TapString.Buffer);

	DEBUGP (("TAP DEV NAME: '%s'\n", l_TapString.Buffer));

	//=======================================================
	// Set TAP link name
	//=======================================================

	l_Status = RtlStringCchPrintfExA
		(l_LinkString.Buffer,
		l_LinkString.MaximumLength,
		NULL,
		NULL,
		STRSAFE_FILL_BEHIND_NULL | STRSAFE_IGNORE_NULLS,
		"%s%s%s",
		USERDEVICEDIR,
		l_UsableName,
		TAP_WIN_SUFFIX);

	if (l_Status != STATUS_SUCCESS)
	{
		DEBUGP (("[%s] couldn't format TAP device symbolic link\n",
			p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}
	l_LinkString.Length = (USHORT) strlen (l_LinkString.Buffer);

	DEBUGP (("TAP LINK NAME: '%s'\n", l_LinkString.Buffer));

	//==================================================
	// Convert strings to unicode
	//==================================================
	if (RtlAnsiStringToUnicodeString (&l_TapUnicode, &l_TapString, TRUE) !=
		STATUS_SUCCESS)
	{
		DEBUGP (("[%s] couldn't alloc TAP unicode name buffer\n",
			p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}
	l_FreeTapUnicode = TRUE;

	if (RtlAnsiStringToUnicodeString
		(&p_Extension->m_UnicodeLinkName, &l_LinkString, TRUE)
		!= STATUS_SUCCESS)
	{
		DEBUGP
			(("[%s] Couldn't allocate unicode string for symbolic link name\n",
			p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}
	p_Extension->m_CreatedUnicodeLinkName = TRUE;

	//==================================================
	// Create new TAP device with symbolic
	// link and associate with adapter.
	//==================================================

	l_Status = NdisMRegisterDevice
		(g_NdisWrapperHandle,
		&l_TapUnicode,
		&p_Extension->m_UnicodeLinkName,
		l_Dispatch,
		&p_Extension->m_TapDevice,
		&p_Extension->m_TapDeviceHandle
		);

	if (l_Status != STATUS_SUCCESS)
	{
		DEBUGP (("[%s] couldn't be created\n", p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}

	/* Set TAP device flags */
	p_Extension->m_TapDevice->Flags |= DO_DIRECT_IO;

	//========================================================
	// Initialize Packet and IRP queues.
	//
	// The packet queue is used to buffer data which has been
	// "transmitted" by the virtual NIC, before user space
	// has had a chance to read it.
	//
	// The IRP queue is used to buffer pending I/O requests
	// from userspace, i.e. read requests on the TAP device
	// waiting for the system to "transmit" something through
	// the virtual NIC.
	//
	// Basically, packets in the packet queue are used
	// to satisfy IRP requests in the IRP queue.
	//
	// QueueLock is used to lock the packet queue used
	// for the TAP-Windows NIC -> User Space packet flow direction.
	//
	// All accesses to packet or IRP queues should be
	// bracketed by the QueueLock spinlock,
	// in order to be SMP-safe.
	//========================================================

	NdisAllocateSpinLock (&p_Extension->m_QueueLock);
	p_Extension->m_AllocatedSpinlocks = TRUE;

	p_Extension->m_PacketQueue = QueueInit (PACKET_QUEUE_SIZE);
	p_Extension->m_IrpQueue = QueueInit (IRP_QUEUE_SIZE);
	if (!p_Extension->m_PacketQueue
		|| !p_Extension->m_IrpQueue)
	{
		DEBUGP (("[%s] couldn't alloc TAP queues\n", p_Name));
		l_Return = NDIS_STATUS_RESOURCES;
		goto cleanup;
	}

	//========================
	// Finalize initialization
	//========================

	p_Extension->m_TapIsRunning = TRUE;

	DEBUGP (("[%s] successfully created TAP device [%s]\n", p_Name,
		p_Extension->m_TapName));

cleanup:
	if (l_FreeTapUnicode)
		RtlFreeUnicodeString (&l_TapUnicode);
	if (l_LinkString.Buffer)
		MemFree (l_LinkString.Buffer, NAME_BUFFER_SIZE);
	if (l_Dispatch)
		MemFree (l_Dispatch, SIZEOF_DISPATCH);

	if (l_Return != NDIS_STATUS_SUCCESS)
		TapDeviceFreeResources (p_Extension);

	return l_Return;
}
#undef SIZEOF_DISPATCH

//========================================================
//                      Adapter Control
//========================================================
NDIS_STATUS
	AdapterReset (OUT PBOOLEAN p_AddressingReset, IN NDIS_HANDLE p_AdapterContext)
{
	TapAdapterPointer l_Adapter = (TapAdapterPointer) p_AdapterContext;
	DEBUGP (("[%s] is resetting\n", NAME (l_Adapter)));
	return NDIS_STATUS_SUCCESS;
}

NDIS_STATUS AdapterReceive
	(OUT PNDIS_PACKET p_Packet,
	OUT PUINT p_Transferred,
	IN NDIS_HANDLE p_AdapterContext,
	IN NDIS_HANDLE p_ReceiveContext,
	IN UINT p_Offset,
	IN UINT p_ToTransfer)
{
	return NDIS_STATUS_SUCCESS;
}

//==============================================================
//                  Adapter Option Query/Modification
//==============================================================
NDIS_STATUS AdapterQuery
	(IN NDIS_HANDLE p_AdapterContext,
	IN NDIS_OID p_OID,
	IN PVOID p_Buffer,
	IN ULONG p_BufferLength,
	OUT PULONG p_BytesWritten, OUT PULONG p_BytesNeeded)
{
	TapAdapterPointer l_Adapter = (TapAdapterPointer) p_AdapterContext;
	TapAdapterQuery l_Query, *l_QueryPtr = &l_Query;
	NDIS_STATUS l_Status = NDIS_STATUS_SUCCESS;
	UINT l_QueryLength = 4;
	BOOLEAN lock_succeeded;

	NdisZeroMemory (&l_Query, sizeof (l_Query));

	switch (p_OID)
	{
		//===================================================================
		//                       Vendor & Driver version Info
		//===================================================================
	case OID_GEN_VENDOR_DESCRIPTION:
		l_QueryPtr = (TapAdapterQueryPointer) PRODUCT_TAP_WIN_DEVICE_DESCRIPTION;
		l_QueryLength = strlen (PRODUCT_TAP_WIN_DEVICE_DESCRIPTION) + 1;
		break;

	case OID_GEN_VENDOR_ID:
		l_Query.m_Long = 0xffffff;
		break;

	case OID_GEN_DRIVER_VERSION:
		l_Query.m_Short =
			(((USHORT) TAP_NDIS_MAJOR_VERSION) << 8 | (USHORT)
			TAP_NDIS_MINOR_VERSION);
		l_QueryLength = sizeof (unsigned short);
		break;

	case OID_GEN_VENDOR_DRIVER_VERSION:
		l_Query.m_Long =
			(((USHORT) PRODUCT_TAP_WIN_MAJOR) << 8 | (USHORT)
			PRODUCT_TAP_WIN_MINOR);
		break;

		//=================================================================
		//                             Statistics
		//=================================================================
	case OID_GEN_RCV_NO_BUFFER:
		l_Query.m_Long = 0;
		break;

	case OID_802_3_RCV_ERROR_ALIGNMENT:
		l_Query.m_Long = 0;
		break;

	case OID_802_3_XMIT_ONE_COLLISION:
		l_Query.m_Long = 0;
		break;

	case OID_802_3_XMIT_MORE_COLLISIONS:
		l_Query.m_Long = 0;
		break;

	case OID_GEN_XMIT_OK:
		l_Query.m_Long = l_Adapter->m_Tx;
		break;

	case OID_GEN_RCV_OK:
		l_Query.m_Long = l_Adapter->m_Rx;
		break;

	case OID_GEN_XMIT_ERROR:
		l_Query.m_Long = l_Adapter->m_TxErr;
		break;

	case OID_GEN_RCV_ERROR:
		l_Query.m_Long = l_Adapter->m_RxErr;
		break;

		//===================================================================
		//                       Device & Protocol Options
		//===================================================================
	case OID_GEN_SUPPORTED_LIST:
		l_QueryPtr = (TapAdapterQueryPointer) g_SupportedOIDList;
		l_QueryLength = sizeof (g_SupportedOIDList);
		break;

	case OID_GEN_MAC_OPTIONS:
		// This MUST be here !!!
		l_Query.m_Long = (NDIS_MAC_OPTION_RECEIVE_SERIALIZED
			| NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA
			| NDIS_MAC_OPTION_NO_LOOPBACK
			| NDIS_MAC_OPTION_TRANSFERS_NOT_PEND);

		break;

	case OID_GEN_CURRENT_PACKET_FILTER:
		l_Query.m_Long =
			(NDIS_PACKET_TYPE_ALL_LOCAL |
			NDIS_PACKET_TYPE_BROADCAST |
			NDIS_PACKET_TYPE_DIRECTED | NDIS_PACKET_TYPE_ALL_FUNCTIONAL);

		break;

	case OID_GEN_PROTOCOL_OPTIONS:
		l_Query.m_Long = 0;
		break;

		//==================================================================
		//                            Device Info
		//==================================================================
	case OID_GEN_MEDIA_CONNECT_STATUS:
		l_Query.m_Long = l_Adapter->m_MediaState
			? NdisMediaStateConnected : NdisMediaStateDisconnected;
		break;

	case OID_GEN_HARDWARE_STATUS:
		l_Query.m_HardwareStatus = NdisHardwareStatusReady;
		l_QueryLength = sizeof (NDIS_HARDWARE_STATUS);
		break;

	case OID_GEN_MEDIA_SUPPORTED:
	case OID_GEN_MEDIA_IN_USE:
		l_Query.m_Medium = l_Adapter->m_Medium;
		l_QueryLength = sizeof (NDIS_MEDIUM);
		break;

	case OID_GEN_PHYSICAL_MEDIUM:
		l_Query.m_PhysicalMedium = NdisPhysicalMediumUnspecified;
		l_QueryLength = sizeof (NDIS_PHYSICAL_MEDIUM);
		break;

	case OID_GEN_LINK_SPEED:
		l_Query.m_Long = 100000; // rate / 100 bps
		break;

	case OID_802_3_PERMANENT_ADDRESS:
	case OID_802_3_CURRENT_ADDRESS:
		COPY_MAC (l_Query.m_MacAddress, l_Adapter->m_MAC);
		l_QueryLength = sizeof (MACADDR);
		break;

		//==================================================================
		//                             Limits
		//==================================================================

	case OID_GEN_MAXIMUM_SEND_PACKETS:
		l_Query.m_Long = 1;
		break;

	case OID_802_3_MAXIMUM_LIST_SIZE:
		l_Query.m_Long = NIC_MAX_MCAST_LIST;
		break;

	case OID_GEN_CURRENT_LOOKAHEAD:
		l_Query.m_Long = l_Adapter->m_Lookahead;
		break;

	case OID_GEN_MAXIMUM_LOOKAHEAD:
	case OID_GEN_MAXIMUM_TOTAL_SIZE:
	case OID_GEN_RECEIVE_BUFFER_SPACE:
	case OID_GEN_RECEIVE_BLOCK_SIZE:
		l_Query.m_Long = DEFAULT_PACKET_LOOKAHEAD;
		break;

	case OID_GEN_MAXIMUM_FRAME_SIZE:
	case OID_GEN_TRANSMIT_BLOCK_SIZE:
	case OID_GEN_TRANSMIT_BUFFER_SPACE:
		l_Query.m_Long = l_Adapter->m_MTU;
		break;

	case OID_PNP_CAPABILITIES:
		do
		{
			PNDIS_PNP_CAPABILITIES pPNPCapabilities;
			PNDIS_PM_WAKE_UP_CAPABILITIES pPMstruct;

			if (p_BufferLength >= sizeof (NDIS_PNP_CAPABILITIES))
			{
				pPNPCapabilities = (PNDIS_PNP_CAPABILITIES) (p_Buffer);

				//
				// Setting up the buffer to be returned
				// to the Protocol above the Passthru miniport
				//
				pPMstruct = &pPNPCapabilities->WakeUpCapabilities;
				pPMstruct->MinMagicPacketWakeUp = NdisDeviceStateUnspecified;
				pPMstruct->MinPatternWakeUp = NdisDeviceStateUnspecified;
				pPMstruct->MinLinkChangeWakeUp = NdisDeviceStateUnspecified;
			}
			l_QueryLength = sizeof (NDIS_PNP_CAPABILITIES);
		}
		while (FALSE);
		break;
	case OID_PNP_QUERY_POWER:
		break;

		// Required OIDs that we don't support

	case OID_GEN_SUPPORTED_GUIDS:
	case OID_GEN_MEDIA_CAPABILITIES:
	case OID_TCP_TASK_OFFLOAD:
	case OID_FFP_SUPPORT:
		l_Status = NDIS_STATUS_INVALID_OID;
		break;

		// Optional stats OIDs

	case OID_GEN_DIRECTED_BYTES_XMIT:
	case OID_GEN_DIRECTED_FRAMES_XMIT:
	case OID_GEN_MULTICAST_BYTES_XMIT:
	case OID_GEN_MULTICAST_FRAMES_XMIT:
	case OID_GEN_BROADCAST_BYTES_XMIT:
	case OID_GEN_BROADCAST_FRAMES_XMIT:
	case OID_GEN_DIRECTED_BYTES_RCV:
	case OID_GEN_DIRECTED_FRAMES_RCV:
	case OID_GEN_MULTICAST_BYTES_RCV:
	case OID_GEN_MULTICAST_FRAMES_RCV:
	case OID_GEN_BROADCAST_BYTES_RCV:
	case OID_GEN_BROADCAST_FRAMES_RCV:
		l_Status = NDIS_STATUS_INVALID_OID;
		break;

		//===================================================================
		//                          Not Handled
		//===================================================================
	default:
		DEBUGP (("[%s] Unhandled OID %lx\n", NAME (l_Adapter), p_OID));
		l_Status = NDIS_STATUS_INVALID_OID;
		break;
	}

	if (l_Status != NDIS_STATUS_SUCCESS)
		;
	else if (l_QueryLength > p_BufferLength)
	{
		l_Status = NDIS_STATUS_INVALID_LENGTH;
		*p_BytesNeeded = l_QueryLength;
	}
	else
		NdisMoveMemory (p_Buffer, (PVOID) l_QueryPtr,
		(*p_BytesWritten = l_QueryLength));

	return l_Status;
}

NDIS_STATUS AdapterModify
	(IN NDIS_HANDLE p_AdapterContext,
	IN NDIS_OID p_OID,
	IN PVOID p_Buffer,
	IN ULONG p_BufferLength,
	OUT PULONG p_BytesRead,
	OUT PULONG p_BytesNeeded)
{
	TapAdapterQueryPointer l_Query = (TapAdapterQueryPointer) p_Buffer;
	TapAdapterPointer l_Adapter = (TapAdapterPointer) p_AdapterContext;
	NDIS_STATUS l_Status = NDIS_STATUS_INVALID_OID;
	ULONG l_Long;

	switch (p_OID)
	{
		//==================================================================
		//                            Device Info
		//==================================================================
	case OID_802_3_MULTICAST_LIST:
		DEBUGP (("[%s] Setting [OID_802_3_MULTICAST_LIST]\n",
			NAME (l_Adapter)));

		*p_BytesNeeded = sizeof (ETH_ADDR);
		*p_BytesRead = p_BufferLength;

		if (p_BufferLength % sizeof (ETH_ADDR))
			l_Status = NDIS_STATUS_INVALID_LENGTH;
		else if (p_BufferLength > sizeof (MC_LIST))
		{
			l_Status = NDIS_STATUS_MULTICAST_FULL;
			*p_BytesNeeded = sizeof (MC_LIST);
		}
		else
		{
			NdisAcquireSpinLock (&l_Adapter->m_MCLock);

			NdisZeroMemory(&l_Adapter->m_MCList, sizeof (MC_LIST));

			NdisMoveMemory(&l_Adapter->m_MCList,
				p_Buffer,
				p_BufferLength);

			l_Adapter->m_MCListSize = p_BufferLength / sizeof (ETH_ADDR);

			NdisReleaseSpinLock (&l_Adapter->m_MCLock);

			l_Status = NDIS_STATUS_SUCCESS;
		}
		break;

	case OID_GEN_CURRENT_PACKET_FILTER:
		l_Status = NDIS_STATUS_INVALID_LENGTH;
		*p_BytesNeeded = 4;

		if (p_BufferLength >= sizeof (ULONG))
		{
			DEBUGP
				(("[%s] Setting [OID_GEN_CURRENT_PACKET_FILTER] to [0x%02lx]\n",
				NAME (l_Adapter), l_Query->m_Long));
			l_Status = NDIS_STATUS_SUCCESS;
			*p_BytesRead = sizeof (ULONG);
		}
		break;

	case OID_GEN_CURRENT_LOOKAHEAD:
		if (p_BufferLength < sizeof (ULONG))
		{
			l_Status = NDIS_STATUS_INVALID_LENGTH;
			*p_BytesNeeded = 4;
		}
		else if (l_Query->m_Long > DEFAULT_PACKET_LOOKAHEAD
			|| l_Query->m_Long <= 0)
		{
			l_Status = NDIS_STATUS_INVALID_DATA;
		}
		else
		{
			DEBUGP (("[%s] Setting [OID_GEN_CURRENT_LOOKAHEAD] to [%d]\n",
				NAME (l_Adapter), l_Query->m_Long));
			l_Adapter->m_Lookahead = l_Query->m_Long;
			l_Status = NDIS_STATUS_SUCCESS;
			*p_BytesRead = sizeof (ULONG);
		}
		break;

	case OID_GEN_NETWORK_LAYER_ADDRESSES:
		l_Status = NDIS_STATUS_SUCCESS;
		*p_BytesRead = *p_BytesNeeded = 0;
		break;

	case OID_GEN_TRANSPORT_HEADER_OFFSET:
		l_Status = NDIS_STATUS_SUCCESS;
		*p_BytesRead = *p_BytesNeeded = 0;
		break;

	case OID_PNP_SET_POWER:
		do
		{
			NDIS_DEVICE_POWER_STATE NewDeviceState;

			NewDeviceState = (*(PNDIS_DEVICE_POWER_STATE) p_Buffer);

			switch (NewDeviceState)
			{
			case NdisDeviceStateD0:
				l_Adapter->m_DeviceState = '0';
				break;
			case NdisDeviceStateD1:
				l_Adapter->m_DeviceState = '1';
				break;
			case NdisDeviceStateD2:
				l_Adapter->m_DeviceState = '2';
				break;
			case NdisDeviceStateD3:
				l_Adapter->m_DeviceState = '3';
				break;
			default:
				l_Adapter->m_DeviceState = '?';
				break;
			}

			l_Status = NDIS_STATUS_FAILURE;

			//
			// Check for invalid length
			//
			if (p_BufferLength < sizeof (NDIS_DEVICE_POWER_STATE))
			{
				l_Status = NDIS_STATUS_INVALID_LENGTH;
				break;
			}

			if (NewDeviceState > NdisDeviceStateD0)
			{
				l_Adapter->m_InterfaceIsRunning = FALSE;
				DEBUGP (("[%s] Power management device state OFF\n",
					NAME (l_Adapter)));
			}
			else
			{
				l_Adapter->m_InterfaceIsRunning = TRUE;
				DEBUGP (("[%s] Power management device state ON\n",
					NAME (l_Adapter)));
			}

			l_Status = NDIS_STATUS_SUCCESS;
		}
		while (FALSE);

		if (l_Status == NDIS_STATUS_SUCCESS)
		{
			*p_BytesRead = sizeof (NDIS_DEVICE_POWER_STATE);
			*p_BytesNeeded = 0;
		}
		else
		{
			*p_BytesRead = 0;
			*p_BytesNeeded = sizeof (NDIS_DEVICE_POWER_STATE);
		}
		break;

	case OID_PNP_REMOVE_WAKE_UP_PATTERN:
	case OID_PNP_ADD_WAKE_UP_PATTERN:
		l_Status = NDIS_STATUS_SUCCESS;
		*p_BytesRead = *p_BytesNeeded = 0;
		break;

	default:
		DEBUGP (("[%s] Can't set value for OID %lx\n", NAME (l_Adapter),
			p_OID));
		l_Status = NDIS_STATUS_INVALID_OID;
		*p_BytesRead = *p_BytesNeeded = 0;
		break;
	}

	return l_Status;
}

//====================================================================
//                               Adapter Transmission
//====================================================================
NDIS_STATUS
	AdapterTransmit (IN NDIS_HANDLE p_AdapterContext,
	IN PNDIS_PACKET p_Packet,
	IN UINT p_Flags)
{
	TapAdapterPointer l_Adapter = (TapAdapterPointer) p_AdapterContext;
	ULONG l_Index = 0, l_PacketLength = 0;
	UINT l_BufferLength = 0;
	PIRP l_IRP;
	TapPacketPointer l_PacketBuffer;
	PNDIS_BUFFER l_NDIS_Buffer;
	PUCHAR l_Buffer;
	PVOID result;

	NdisQueryPacket (p_Packet, NULL, NULL, &l_NDIS_Buffer, &l_PacketLength);

	//====================================================
	// Here we abandon the transmission attempt if any of
	// the parameters is wrong or memory allocation fails
	// but we do not indicate failure. The packet is
	// silently dropped.
	//====================================================

	if (l_PacketLength < ETHERNET_HEADER_SIZE || l_PacketLength > 65535)
		goto exit_fail;
	else if (!l_Adapter->m_Extension.m_TapOpens || !l_Adapter->m_MediaState)
		goto exit_success;              // Nothing is bound to the TAP device

	if (NdisAllocateMemoryWithTag (&l_PacketBuffer,
		TAP_PACKET_SIZE (l_PacketLength),
		'5PAT') != NDIS_STATUS_SUCCESS)
		goto exit_no_resources;

	if (l_PacketBuffer == NULL)
		goto exit_no_resources;

	l_PacketBuffer->m_SizeFlags = (l_PacketLength & TP_SIZE_MASK);

	//===========================
	// Reassemble packet contents
	//===========================

	__try
	{
		l_Index = 0;
		while (l_NDIS_Buffer && l_Index < l_PacketLength)
		{
			ULONG newlen;
			NdisQueryBuffer (l_NDIS_Buffer, (PVOID *) & l_Buffer,
				&l_BufferLength);
			newlen = l_Index + l_BufferLength;
			if (newlen > l_PacketLength)
			{
				NOTE_ERROR ();
				goto no_queue; /* overflow */
			}
			NdisMoveMemory (l_PacketBuffer->m_Data + l_Index, l_Buffer,
				l_BufferLength);
			l_Index = newlen;
			NdisGetNextBuffer (l_NDIS_Buffer, &l_NDIS_Buffer);
		}
		if (l_Index != l_PacketLength)
		{
			NOTE_ERROR ();
			goto no_queue; /* underflow */
		}

		DUMP_PACKET ("AdapterTransmit", l_PacketBuffer->m_Data, l_PacketLength);

		//===============================================
		// Push packet onto queue to wait for read from
		// userspace.
		//===============================================

		NdisAcquireSpinLock (&l_Adapter->m_Extension.m_QueueLock);

		result = NULL;
		if (IS_UP (l_Adapter))
			result = QueuePush (l_Adapter->m_Extension.m_PacketQueue, l_PacketBuffer);

		NdisReleaseSpinLock (&l_Adapter->m_Extension.m_QueueLock);

		if ((TapPacketPointer) result != l_PacketBuffer)
		{
			// adapter receive overrun
			INCREMENT_STAT (l_Adapter->m_TxErr);
			goto no_queue;
		}
		else
		{
			INCREMENT_STAT (l_Adapter->m_Tx);
		}

		//============================================================
		// Cycle through IRPs and packets, try to satisfy each pending
		// IRP with a queued packet.
		//============================================================
		while (TRUE)
		{
			l_IRP = NULL;
			l_PacketBuffer = NULL;

			NdisAcquireSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			if (IS_UP (l_Adapter)
				&& QueueCount (l_Adapter->m_Extension.m_PacketQueue)
				&& QueueCount (l_Adapter->m_Extension.m_IrpQueue))
			{
				l_IRP = (PIRP) QueuePop (l_Adapter->m_Extension.m_IrpQueue);
				l_PacketBuffer = (TapPacketPointer)
					QueuePop (l_Adapter->m_Extension.m_PacketQueue);
			}

			NdisReleaseSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			MYASSERT ((l_IRP != NULL) + (l_PacketBuffer != NULL) != 1);

			if (l_IRP && l_PacketBuffer)
			{
				CompleteIRP (l_IRP,
					l_PacketBuffer, 
					IO_NETWORK_INCREMENT);
			}
			else
				break;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
	}

	return NDIS_STATUS_SUCCESS;

no_queue:
	NdisFreeMemory (l_PacketBuffer,
		TAP_PACKET_SIZE (l_PacketLength),
		0);

exit_success:
	return NDIS_STATUS_SUCCESS;

exit_fail:
	return NDIS_STATUS_FAILURE;

exit_no_resources:
	return NDIS_STATUS_RESOURCES;
}

//======================================================================
// Hooks for catching TAP device IRP's.
//======================================================================

DRIVER_DISPATCH TapDeviceHook;
NTSTATUS
	TapDeviceHook (IN PDEVICE_OBJECT p_DeviceObject, IN PIRP p_IRP)
{
	TapAdapterPointer l_Adapter = LookupAdapterInInstanceList (p_DeviceObject);
	PIO_STACK_LOCATION l_IrpSp;
	NTSTATUS l_Status = STATUS_SUCCESS;
	BOOLEAN accessible;
	ULONG i,j;

	l_IrpSp = IoGetCurrentIrpStackLocation (p_IRP);

	p_IRP->IoStatus.Status = STATUS_SUCCESS;
	p_IRP->IoStatus.Information = 0;

	if (!l_Adapter || l_Adapter->m_Extension.m_Halt)
	{
		DEBUGP (("TapDeviceHook called when TAP device is halted, MajorFunction=%d\n",
			(int)l_IrpSp->MajorFunction));

		if (l_IrpSp->MajorFunction == IRP_MJ_CLOSE)
		{
			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			return STATUS_SUCCESS;
		}
		else
		{
			p_IRP->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			return STATUS_NO_SUCH_DEVICE;
		}
	}

	switch (l_IrpSp->MajorFunction)
	{
		//===========================================================
		//                 Ioctl call handlers
		//===========================================================
	case IRP_MJ_DEVICE_CONTROL:
		{
			switch (l_IrpSp->Parameters.DeviceIoControl.IoControlCode)
			{
			case TAP_WIN_IOCTL_GET_MAC:
				{
					if (l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength
						>= sizeof (MACADDR))
					{
						COPY_MAC (p_IRP->AssociatedIrp.SystemBuffer,
							l_Adapter->m_MAC);
						p_IRP->IoStatus.Information = sizeof (MACADDR);
					}
					else
					{
						NOTE_ERROR ();
						p_IRP->IoStatus.Status = l_Status = STATUS_BUFFER_TOO_SMALL;
					}
					break;
				}
			case TAP_WIN_IOCTL_GET_VERSION:
				{
					const ULONG size = sizeof (ULONG) * 3;
					if (l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength
						>= size)
					{
						((PULONG) (p_IRP->AssociatedIrp.SystemBuffer))[0]
						= TAP_DRIVER_MAJOR_VERSION;
						((PULONG) (p_IRP->AssociatedIrp.SystemBuffer))[1]
						= TAP_DRIVER_MINOR_VERSION;
						((PULONG) (p_IRP->AssociatedIrp.SystemBuffer))[2]
#if DBG
						= 1;
#else
						= 0;
#endif
						p_IRP->IoStatus.Information = size;
					}
					else
					{
						NOTE_ERROR ();
						p_IRP->IoStatus.Status = l_Status = STATUS_BUFFER_TOO_SMALL;
					}

					break;
				}
			case TAP_WIN_IOCTL_GET_MTU:
				{
					const ULONG size = sizeof (ULONG) * 1;
					if (l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength
						>= size)
					{
						((PULONG) (p_IRP->AssociatedIrp.SystemBuffer))[0]
						= l_Adapter->m_MTU;
						p_IRP->IoStatus.Information = size;
					}
					else
					{
						NOTE_ERROR ();
						p_IRP->IoStatus.Status = l_Status = STATUS_BUFFER_TOO_SMALL;
					}

					break;
				}

#if DBG
			case TAP_WIN_IOCTL_GET_LOG_LINE:
				{
					if (GetDebugLine ((LPTSTR)p_IRP->AssociatedIrp.SystemBuffer,
						l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength))
						p_IRP->IoStatus.Status = l_Status = STATUS_SUCCESS;
					else
						p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;

					p_IRP->IoStatus.Information
						= l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

					break;
				}
#endif

				// Allow ZeroTier One to get multicast memberships at the L2 level in a
				// protocol-neutral manner.
			case TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS:
				{
					if (&l_Adapter->m_MCLockAllocated)
						NdisAcquireSpinLock (&l_Adapter->m_MCLock);
					if (l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength < TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE) {
						/* output buffer too small */
						NOTE_ERROR ();
						p_IRP->IoStatus.Status = l_Status = STATUS_INVALID_PARAMETER;
					} else {
						char *out = (char *)p_IRP->AssociatedIrp.SystemBuffer;
						char *end = out + TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE;
						for(i=0;i<l_Adapter->m_MCListSize;++i) {
							if (i >= TAP_MAX_MCAST_LIST)
								break;
							for(j=0;j<6;++j)
								*(out++) = l_Adapter->m_MCList.list[i].addr[j];
							if (out >= end)
								break;
						}
						while (out < end)
							*(out++) = (char)0;
						p_IRP->IoStatus.Information
							= l_IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
						p_IRP->IoStatus.Status = l_Status = STATUS_SUCCESS;
					}
					if (&l_Adapter->m_MCLockAllocated)
						NdisReleaseSpinLock (&l_Adapter->m_MCLock);

					break;
				}

			case TAP_WIN_IOCTL_SET_MEDIA_STATUS:
				{
					if (l_IrpSp->Parameters.DeviceIoControl.InputBufferLength >=
						(sizeof (ULONG) * 1))
					{
						ULONG parm = ((PULONG) (p_IRP->AssociatedIrp.SystemBuffer))[0];
						SetMediaStatus (l_Adapter, (BOOLEAN) parm);
						p_IRP->IoStatus.Information = 1;
					}
					else
					{
						NOTE_ERROR ();
						p_IRP->IoStatus.Status = l_Status = STATUS_INVALID_PARAMETER;
					}
					break;
				}

			default:
				{
					NOTE_ERROR ();
					p_IRP->IoStatus.Status = l_Status = STATUS_INVALID_PARAMETER;
					break;
				}
			}

			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}

		//===========================================================
		// User mode thread issued a read request on the tap device
		// If there are packets waiting to be read, then the request
		// will be satisfied here. If not, then the request will be
		// queued and satisfied by any packet that is not used to
		// satisfy requests ahead of it.
		//===========================================================
	case IRP_MJ_READ:
		{
			TapPacketPointer l_PacketBuffer;
			BOOLEAN pending = FALSE;

			// Save IRP-accessible copy of buffer length
			p_IRP->IoStatus.Information = l_IrpSp->Parameters.Read.Length;

			if (p_IRP->MdlAddress == NULL)
			{
				DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_READ\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_INVALID_PARAMETER;
				p_IRP->IoStatus.Information = 0;
				IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
				break;
			}
			else if ((p_IRP->AssociatedIrp.SystemBuffer =
				MmGetSystemAddressForMdlSafe
				(p_IRP->MdlAddress, NormalPagePriority)) == NULL)
			{
				DEBUGP (("[%s] Could not map address in IRP_MJ_READ\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_INSUFFICIENT_RESOURCES;
				p_IRP->IoStatus.Information = 0;
				IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
				break;
			}
			else if (!l_Adapter->m_InterfaceIsRunning)
			{
				DEBUGP (("[%s] Interface is down in IRP_MJ_READ\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
				p_IRP->IoStatus.Information = 0;
				IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
				break;
			}

			//==================================
			// Can we provide immediate service?
			//==================================

			l_PacketBuffer = NULL;

			NdisAcquireSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			if (IS_UP (l_Adapter)
				&& QueueCount (l_Adapter->m_Extension.m_PacketQueue)
				&& QueueCount (l_Adapter->m_Extension.m_IrpQueue) == 0)
			{
				l_PacketBuffer = (TapPacketPointer)
					QueuePop (l_Adapter->m_Extension.m_PacketQueue);
			}

			NdisReleaseSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			if (l_PacketBuffer)
			{
				l_Status = CompleteIRP (p_IRP,
					l_PacketBuffer,
					IO_NO_INCREMENT);
				break;
			}

			//=============================
			// Attempt to pend read request
			//=============================

			NdisAcquireSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			if (IS_UP (l_Adapter)
				&& QueuePush (l_Adapter->m_Extension.m_IrpQueue, p_IRP) == (PIRP) p_IRP)
			{
				IoSetCancelRoutine (p_IRP, CancelIRPCallback);
				l_Status = STATUS_PENDING;
				IoMarkIrpPending (p_IRP);
				pending = TRUE;
			}

			NdisReleaseSpinLock (&l_Adapter->m_Extension.m_QueueLock);

			if (pending)
				break;

			// Can't queue anymore IRP's
			DEBUGP (("[%s] TAP [%s] read IRP overrun\n",
				NAME (l_Adapter), l_Adapter->m_Extension.m_TapName));
			NOTE_ERROR ();
			p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
			p_IRP->IoStatus.Information = 0;
			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}

		//==============================================================
		// User mode issued a WriteFile request on the TAP file handle.
		// The request will always get satisfied here.  The call may
		// fail if there are too many pending packets (queue full).
		//==============================================================
	case IRP_MJ_WRITE:
		{
			if (p_IRP->MdlAddress == NULL)
			{
				DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_WRITE\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_INVALID_PARAMETER;
				p_IRP->IoStatus.Information = 0;
			}
			else if ((p_IRP->AssociatedIrp.SystemBuffer =
				MmGetSystemAddressForMdlSafe
				(p_IRP->MdlAddress, NormalPagePriority)) == NULL)
			{
				DEBUGP (("[%s] Could not map address in IRP_MJ_WRITE\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_INSUFFICIENT_RESOURCES;
				p_IRP->IoStatus.Information = 0;
			}
			else if (!l_Adapter->m_InterfaceIsRunning)
			{
				DEBUGP (("[%s] Interface is down in IRP_MJ_WRITE\n",
					NAME (l_Adapter)));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
				p_IRP->IoStatus.Information = 0;
			}
			else if ((l_IrpSp->Parameters.Write.Length) >= ETHERNET_HEADER_SIZE)
			{
				__try
				{
					p_IRP->IoStatus.Information = l_IrpSp->Parameters.Write.Length;

					DUMP_PACKET ("IRP_MJ_WRITE ETH",
						(unsigned char *) p_IRP->AssociatedIrp.SystemBuffer,
						l_IrpSp->Parameters.Write.Length);

					NdisMEthIndicateReceive
						(l_Adapter->m_MiniportAdapterHandle,
						(NDIS_HANDLE) l_Adapter,
						(PCHAR)p_IRP->AssociatedIrp.SystemBuffer,
						ETHERNET_HEADER_SIZE,
						(unsigned char *)p_IRP->AssociatedIrp.SystemBuffer + ETHERNET_HEADER_SIZE,
						l_IrpSp->Parameters.Write.Length - ETHERNET_HEADER_SIZE,
						l_IrpSp->Parameters.Write.Length - ETHERNET_HEADER_SIZE);

					NdisMEthIndicateReceiveComplete (l_Adapter->m_MiniportAdapterHandle);

					p_IRP->IoStatus.Status = l_Status = STATUS_SUCCESS;
				}
				__except (EXCEPTION_EXECUTE_HANDLER)
				{
					DEBUGP (("[%s] NdisMEthIndicateReceive failed in IRP_MJ_WRITE\n",
						NAME (l_Adapter)));
					NOTE_ERROR ();
					p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
					p_IRP->IoStatus.Information = 0;
				}
			}
			else
			{
				DEBUGP (("[%s] Bad buffer size in IRP_MJ_WRITE, len=%d\n",
					NAME (l_Adapter),
					l_IrpSp->Parameters.Write.Length));
				NOTE_ERROR ();
				p_IRP->IoStatus.Information = 0;	// ETHERNET_HEADER_SIZE;
				p_IRP->IoStatus.Status = l_Status = STATUS_BUFFER_TOO_SMALL;
			}

			if (l_Status == STATUS_SUCCESS)
				INCREMENT_STAT (l_Adapter->m_Rx);
			else
				INCREMENT_STAT (l_Adapter->m_RxErr);

			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}

		//--------------------------------------------------------------
		//   User mode thread has called CreateFile() on the tap device
		//--------------------------------------------------------------
	case IRP_MJ_CREATE:
		{
			BOOLEAN succeeded = FALSE;
			BOOLEAN mutex_succeeded;

			DEBUGP
				(("[%s] [TAP] release [%d.%d] open request (m_TapOpens=%d)\n",
				NAME (l_Adapter), TAP_DRIVER_MAJOR_VERSION,
				TAP_DRIVER_MINOR_VERSION, l_Adapter->m_Extension.m_TapOpens));

			ACQUIRE_MUTEX_ADAPTIVE (&l_Adapter->m_Extension.m_OpenCloseMutex, mutex_succeeded);
			if (mutex_succeeded)
			{
				if (l_Adapter->m_Extension.m_TapIsRunning && !l_Adapter->m_Extension.m_TapOpens)
				{
					l_Adapter->m_Extension.m_TapOpens = 1;
					succeeded = TRUE;
				}

				if (succeeded)
				{
					INCREMENT_STAT (l_Adapter->m_Extension.m_NumTapOpens);
					p_IRP->IoStatus.Status = l_Status = STATUS_SUCCESS;
					p_IRP->IoStatus.Information = 0;
				}
				else
				{
					DEBUGP (("[%s] TAP is presently unavailable (m_TapOpens=%d)\n",
						NAME (l_Adapter), l_Adapter->m_Extension.m_TapOpens));
					NOTE_ERROR ();
					p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
					p_IRP->IoStatus.Information = 0;
				}

				RELEASE_MUTEX (&l_Adapter->m_Extension.m_OpenCloseMutex);
			}
			else
			{
				DEBUGP (("[%s] TAP is presently locked (m_TapOpens=%d)\n",
					NAME (l_Adapter), l_Adapter->m_Extension.m_TapOpens));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
				p_IRP->IoStatus.Information = 0;
			}

			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}

		//-----------------------------------------------------------
		//  User mode thread called CloseHandle() on the tap device
		//-----------------------------------------------------------
	case IRP_MJ_CLOSE:
		{
			BOOLEAN mutex_succeeded;

			DEBUGP (("[%s] [TAP] release [%d.%d] close/cleanup request\n",
				NAME (l_Adapter), TAP_DRIVER_MAJOR_VERSION,
				TAP_DRIVER_MINOR_VERSION));

			ACQUIRE_MUTEX_ADAPTIVE (&l_Adapter->m_Extension.m_OpenCloseMutex, mutex_succeeded);
			if (mutex_succeeded)
			{
				l_Adapter->m_Extension.m_TapOpens = 0;
				FlushQueues (&l_Adapter->m_Extension);
				SetMediaStatus (l_Adapter, FALSE);
				RELEASE_MUTEX (&l_Adapter->m_Extension.m_OpenCloseMutex);
			}
			else
			{
				DEBUGP (("[%s] TAP is presently locked (m_TapOpens=%d)\n",
					NAME (l_Adapter), l_Adapter->m_Extension.m_TapOpens));
				NOTE_ERROR ();
				p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
				p_IRP->IoStatus.Information = 0;
			}

			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}

		//------------------
		// Strange Request
		//------------------
	default:
		{
			//NOTE_ERROR ();
			p_IRP->IoStatus.Status = l_Status = STATUS_UNSUCCESSFUL;
			IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
			break;
		}
	}

	return l_Status;
}

//=============================================================
// CompleteIRP is normally called with an adapter -> userspace
// network packet and an IRP (Pending I/O request) from userspace.
//
// The IRP will normally represent a queued overlapped read
// operation from userspace that is in a wait state.
//
// Use the ethernet packet to satisfy the IRP.
//=============================================================

NTSTATUS
	CompleteIRP (IN PIRP p_IRP,
	IN TapPacketPointer p_PacketBuffer,
	IN CCHAR PriorityBoost)
{
	NTSTATUS l_Status = STATUS_UNSUCCESSFUL;

	int offset;
	int len;

	MYASSERT (p_IRP);
	MYASSERT (p_PacketBuffer);

	IoSetCancelRoutine (p_IRP, NULL);  // Disable cancel routine

	//-------------------------------------------
	// While p_PacketBuffer always contains a
	// full ethernet packet, including the
	// ethernet header, in point-to-point mode,
	// we only want to return the IPv4
	// component.
	//-------------------------------------------

	if (p_PacketBuffer->m_SizeFlags & TP_TUN)
	{
		offset = ETHERNET_HEADER_SIZE;
		len = (int) (p_PacketBuffer->m_SizeFlags & TP_SIZE_MASK) - ETHERNET_HEADER_SIZE;
	}
	else
	{
		offset = 0;
		len = (p_PacketBuffer->m_SizeFlags & TP_SIZE_MASK);
	}

	if (len < 0 || (int) p_IRP->IoStatus.Information < len)
	{
		p_IRP->IoStatus.Information = 0;
		p_IRP->IoStatus.Status = STATUS_BUFFER_OVERFLOW;
		NOTE_ERROR ();
	}
	else
	{
		p_IRP->IoStatus.Information = len;
		p_IRP->IoStatus.Status = l_Status = STATUS_SUCCESS;

		__try
		{
			NdisMoveMemory (p_IRP->AssociatedIrp.SystemBuffer,
				p_PacketBuffer->m_Data + offset,
				len);
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			NOTE_ERROR ();
			p_IRP->IoStatus.Status = STATUS_UNSUCCESSFUL;
			p_IRP->IoStatus.Information = 0;
		}
	}

	__try
	{
		NdisFreeMemory (p_PacketBuffer,
			TAP_PACKET_SIZE (p_PacketBuffer->m_SizeFlags & TP_SIZE_MASK),
			0);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
	}

	if (l_Status == STATUS_SUCCESS)
	{
		IoCompleteRequest (p_IRP, PriorityBoost);
	}
	else
		IoCompleteRequest (p_IRP, IO_NO_INCREMENT);

	return l_Status;
}

//==============================================
// IRPs get cancelled for a number of reasons.
//
// The TAP device could be closed by userspace
// when there are still pending read operations.
//
// The user could disable the TAP adapter in the
// network connections control panel, while the
// device is still open by a process.
//==============================================
VOID
	CancelIRPCallback (IN PDEVICE_OBJECT p_DeviceObject,
	IN PIRP p_IRP)
{
	TapAdapterPointer l_Adapter = LookupAdapterInInstanceList (p_DeviceObject);
	CancelIRP (l_Adapter ? &l_Adapter->m_Extension : NULL, p_IRP, TRUE);
}

VOID
	CancelIRP (TapExtensionPointer p_Extension,
	IN PIRP p_IRP,
	BOOLEAN callback)
{
	BOOLEAN exists = FALSE;

	MYASSERT (p_IRP);

	if (p_Extension)
	{
		NdisAcquireSpinLock (&p_Extension->m_QueueLock);
		exists = (QueueExtract (p_Extension->m_IrpQueue, p_IRP) == p_IRP);
		NdisReleaseSpinLock (&p_Extension->m_QueueLock);
	}
	else
		exists = TRUE;

	if (exists)
	{
		IoSetCancelRoutine (p_IRP, NULL);
		p_IRP->IoStatus.Status = STATUS_CANCELLED;
		p_IRP->IoStatus.Information = 0;
	}

	if (callback)
		IoReleaseCancelSpinLock (p_IRP->CancelIrql);

	if (exists)
		IoCompleteRequest (p_IRP, IO_NO_INCREMENT);
}

//===========================================
// Exhaust packet, IRP, and injection queues.
//===========================================
VOID
	FlushQueues (TapExtensionPointer p_Extension)
{
	PIRP l_IRP;
	TapPacketPointer l_PacketBuffer;

	MYASSERT (p_Extension);
	MYASSERT (p_Extension->m_TapDevice);

	while (TRUE)
	{
		NdisAcquireSpinLock (&p_Extension->m_QueueLock);
		l_IRP = (PIRP)QueuePop (p_Extension->m_IrpQueue);
		NdisReleaseSpinLock (&p_Extension->m_QueueLock);
		if (l_IRP)
		{
			CancelIRP (NULL, l_IRP, FALSE);
		}
		else
			break;
	}

	while (TRUE)
	{
		NdisAcquireSpinLock (&p_Extension->m_QueueLock);
		l_PacketBuffer = (TapPacketPointer)QueuePop (p_Extension->m_PacketQueue);
		NdisReleaseSpinLock (&p_Extension->m_QueueLock);
		if (l_PacketBuffer)
		{
			MemFree (l_PacketBuffer, TAP_PACKET_SIZE (l_PacketBuffer->m_SizeFlags & TP_SIZE_MASK));
		}
		else
			break;
	}
}

//===================================================
// Tell Windows whether the TAP device should be
// considered "connected" or "disconnected".
//===================================================
VOID
	SetMediaStatus (TapAdapterPointer p_Adapter, BOOLEAN state)
{
	if (p_Adapter->m_MediaState != state && !p_Adapter->m_MediaStateAlwaysConnected)
	{
		if (state)
			NdisMIndicateStatus (p_Adapter->m_MiniportAdapterHandle,
			NDIS_STATUS_MEDIA_CONNECT, NULL, 0);
		else
			NdisMIndicateStatus (p_Adapter->m_MiniportAdapterHandle,
			NDIS_STATUS_MEDIA_DISCONNECT, NULL, 0);

		NdisMIndicateStatusComplete (p_Adapter->m_MiniportAdapterHandle);
		p_Adapter->m_MediaState = state;
	}
}