/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    ntos\tdi\isn\fwd\send.c

Abstract:
	Send routines

Author:

    Vadim Eydelman

Revision History:

--*/

#include    "precomp.h"


ULONG			SpoofingTimeout=DEF_SPOOFING_TIMEOUT;
LIST_ENTRY		SpoofingQueue;
KSPIN_LOCK		SpoofingQueueLock;
WORK_QUEUE_ITEM	SpoofingWorker;
BOOLEAN			SpoofingWorkerActive = FALSE;
ULONG			DontSuppressNonAgentSapAdvertisements = 0;

#define IsLocalSapNonAgentAdvertisement(hdr,data,ln,ifCB) (		\
	(DontSuppressNonAgentSapAdvertisements==0)					\
	&& (GETUSHORT(hdr+IPXH_DESTSOCK)==IPX_SAP_SOCKET)			\
	&& (GETUSHORT(hdr+IPXH_SRCSOCK)!=IPX_SAP_SOCKET)			\
	&& (ln>=IPXH_HDRSIZE+2)										\
	&& (GETUSHORT(data)==2)										\
	&& ((IPX_NODE_CMP(hdr+IPXH_DESTNODE,BROADCAST_NODE)==0)		\
        || (IPX_NODE_CMP(hdr+IPXH_DESTNODE,ifCB->ICB_RemoteNode)==0)) \
)

/*++
*******************************************************************
    D o S e n d

Routine Description:
	Prepares and sends packet.  Interface lock must be help while
	callin this routine
Arguments:
	dstIf	- over which interface to send
	pktTag	- packet to send
Return Value:
	result returned by IPX

*******************************************************************
--*/
NDIS_STATUS
DoSend (
	PINTERFACE_CB	dstIf,
	PPACKET_TAG		pktTag,
	KIRQL			oldIRQL
	) {
	NDIS_STATUS			status;
	PNDIS_PACKET		pktDscr;
	PNDIS_BUFFER		bufDscr, aDscr;
	UINT				dataLen;
	ULONG				dstNet = GETULONG (pktTag->PT_Data+IPXH_DESTNET);

	if (dstIf!=InternalInterface) {
        ADAPTER_CONTEXT_TO_LOCAL_TARGET (dstIf->ICB_AdapterContext,
										&pktTag->PT_Target);
    }
    else {
		CONSTANT_ADAPTER_CONTEXT_TO_LOCAL_TARGET (
                        VIRTUAL_NET_ADAPTER_CONTEXT,
										&pktTag->PT_Target);
    }

#if DBG
		// Keep track of packets being processed by IPX stack
	InsertTailList (&dstIf->ICB_InSendQueue, &pktTag->PT_QueueLink);
#endif
	KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
	
	if (pktTag->PT_Flags&PT_SOURCE_IF)
		ReleaseInterfaceReference (pktTag->PT_SourceIf);
	pktTag->SEND_RESERVED[0] = pktTag->SEND_RESERVED[1] = 0;
	pktDscr = CONTAINING_RECORD(pktTag, NDIS_PACKET, ProtocolReserved);
    NdisQueryPacket(pktDscr, NULL, NULL, &bufDscr, NULL);
#if DBG
	{		// Verify packet integrity
		PUCHAR	dataPtr;
		UINT	bufLen;
		ASSERT (NDIS_BUFFER_LINKAGE (bufDscr)==NULL);
		NdisQueryBuffer (bufDscr, &dataPtr, &bufLen);
		ASSERT (dataPtr==pktTag->PT_Data);
		ASSERT (bufLen==pktTag->PT_Segment->PS_SegmentList->SL_BlockSize);
	}
#endif
			// Prepare packet for IPX stack (mac header buffer goes in
			// front and packet length adjusted to reflect the size of the data
	dataLen = GETUSHORT(pktTag->PT_Data+IPXH_LENGTH);
    NdisAdjustBufferLength(bufDscr, dataLen);
	NdisChainBufferAtFront(pktDscr, pktTag->PT_MacHdrBufDscr);


	if (EnterForwarder ()) {// To make sure that we won't unload
							// until IPX driver has a chance to call us back
		status = IPXSendProc (&pktTag->PT_Target, pktDscr, dataLen, 0);

		if (status!=NDIS_STATUS_PENDING) {
			LeaveForwarder ();	// No callback

				// Restore original packet structure
			NdisUnchainBufferAtFront (pktDscr, &aDscr);
#if DBG
				// Make sure IPX stack did not mess our packet
			ASSERT (aDscr==pktTag->PT_MacHdrBufDscr);
		    NdisQueryPacket(pktDscr, NULL, NULL, &aDscr, NULL);
			ASSERT (aDscr==bufDscr);
			ASSERT (NDIS_BUFFER_LINKAGE (aDscr)==NULL);
#endif
				// Restore original packet size
			NdisAdjustBufferLength(bufDscr,
						pktTag->PT_Segment->PS_SegmentList->SL_BlockSize);
#if DBG
				// Remove packet from temp queue
			KeAcquireSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, &oldIRQL);
			RemoveEntryList (&pktTag->PT_QueueLink);
			KeReleaseSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, oldIRQL);
#endif
		}
	}
	else {
			// We are going down, restore the packet
		NdisUnchainBufferAtFront (pktDscr, &aDscr);
		NdisAdjustBufferLength(bufDscr,
						pktTag->PT_Segment->PS_SegmentList->SL_BlockSize);
		NdisRecalculatePacketCounts (pktDscr);
		status = STATUS_UNSUCCESSFUL;
#if DBG
		KeAcquireSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, &oldIRQL);
		RemoveEntryList (&pktTag->PT_QueueLink);
		KeReleaseSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, oldIRQL);
#endif
	}
	return status;
}


/*++
*******************************************************************
    P r o c e s s S e n t P a c k e t

Routine Description:
	Process completed sent packets
Arguments:
	dstIf	- interface over which packet was sent
	pktTag	- completed packet
	status	- result of send operation
Return Value:
	None

*******************************************************************
--*/
VOID
ProcessSentPacket (
	PINTERFACE_CB	dstIf,
	PPACKET_TAG		pktTag,
	NDIS_STATUS		status
	) {
	KIRQL			oldIRQL;

		// Packet processing is completed -> can take more packets
	InterlockedIncrement (&dstIf->ICB_PendingQuota);

	if (*(pktTag->PT_Data+IPXH_PKTTYPE) == IPX_NETBIOS_TYPE) {
            // Continue processing netbios packets
		if (status==NDIS_STATUS_SUCCESS) {
		    IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
			    ("IpxFwd: NB Packet %08lx sent.", pktTag));
    		InterlockedIncrement (&dstIf->ICB_Stats.OutDelivers);
			InterlockedIncrement (&dstIf->ICB_Stats.NetbiosSent);
        }
        else {
		    IpxFwdDbgPrint (DBG_NETBIOS, DBG_ERROR,
			    ("IpxFwd: NB Packet %08lx send failed with error: %08lx.\n",
			    pktTag, status));
        }
			// Queue nb packet for further processing (broadcast on all interfaces)
		QueueNetbiosPacket (pktTag);
	}
	else {
			// Destroy completed packet
	    if (status==NDIS_STATUS_SUCCESS) {
    		InterlockedIncrement (&dstIf->ICB_Stats.OutDelivers);
		    IpxFwdDbgPrint (DBG_SEND, DBG_INFORMATION,
			    ("IpxFwd: Packet %08lx sent.", pktTag));
	    }
	    else {
		    InterlockedIncrement (&dstIf->ICB_Stats.OutDiscards);
		    IpxFwdDbgPrint (DBG_SEND, DBG_ERROR,
			    ("IpxFwd: Packet %08lx send failed with error: %08lx.\n",
			    pktTag, status));
	    }
		ReleaseInterfaceReference (dstIf);
		if (MeasuringPerformance
			&& (pktTag->PT_PerfCounter!=0)) {
			LARGE_INTEGER	PerfCounter = KeQueryPerformanceCounter (NULL);
			PerfCounter.QuadPart -= pktTag->PT_PerfCounter;
			KeAcquireSpinLock (&PerfCounterLock, &oldIRQL);
			ASSERT (PerfCounter.QuadPart<ActivityTreshhold);
			PerfBlock.TotalPacketProcessingTime += PerfCounter.QuadPart;
			PerfBlock.PacketCounter += 1;
			if (PerfBlock.MaxPacketProcessingTime < PerfCounter.QuadPart)
				PerfBlock.MaxPacketProcessingTime = PerfCounter.QuadPart;
			KeReleaseSpinLock (&PerfCounterLock, oldIRQL);
		}
		FreePacket (pktTag);
	}
}

/*++
*******************************************************************
    S e n d P a c k e t

Routine Description:
	Enqueues packets to be sent by IPX stack
Arguments:
	dstIf	- over which interface to send
	pktTag	- packet to send
Return Value:
	None

*******************************************************************
--*/
VOID
SendPacket (
	PINTERFACE_CB		dstIf,
	PPACKET_TAG		    pktTag
	) {
    NDIS_STATUS			status;
	KIRQL				oldIRQL;


	ASSERT (dstIf->ICB_Index!=FWD_INTERNAL_INTERFACE_INDEX);
		// Make sure we have not exceded the quota of pending packets on the interface
	if (InterlockedDecrement (&dstIf->ICB_PendingQuota)>=0) {
		KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
			// Decide what to do with the packet based on the interface state
		switch (dstIf->ICB_Stats.OperationalState) {
		case FWD_OPER_STATE_UP:
			if (*(pktTag->PT_Data + IPXH_PKTTYPE) != IPX_NETBIOS_TYPE)
				NOTHING;
			else {
				PUTULONG (dstIf->ICB_Network, pktTag->PT_Data+IPXH_DESTNET);
			}
			status = DoSend (dstIf, pktTag, oldIRQL);
			IpxFwdDbgPrint (DBG_SEND, DBG_INFORMATION,
				("IpxFwd: Sent external packet %08lx on if %ld.\n",
				pktTag, dstIf->ICB_Index));
			break;
		case FWD_OPER_STATE_SLEEPING:
			if ((*(pktTag->PT_Data+IPXH_PKTTYPE)!=0)
					|| (GETUSHORT(pktTag->PT_Data+IPXH_LENGTH)!=IPXH_HDRSIZE+2)
					|| (*(pktTag->PT_Data+IPXH_HDRSIZE+1)!='?')) {
					// Queue this packet on the interface until it is connected
					// by Router Manager (DIM) if this is not a NCP keepalive
					// (watchdog)
				InsertTailList (&dstIf->ICB_ExternalQueue, &pktTag->PT_QueueLink);
				if (!IS_IF_CONNECTING (dstIf)) {
						// Ask for connection if interface is not in the connection
						// queue yet
					QueueConnectionRequest (dstIf,
                        CONTAINING_RECORD (pktTag,
                                            NDIS_PACKET,
                                            ProtocolReserved),
                        pktTag->PT_Data,
                        oldIRQL);
					IpxFwdDbgPrint (DBG_DIALREQS, DBG_WARNING,
						("IpxFwd: Queued dd request on if %ld (ifCB:%08lx)"
						" for packet to %02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%02x%02x"
						" from %02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%02x%02x\n",
						dstIf->ICB_Index, dstIf,
						*(pktTag->PT_Data+6),*(pktTag->PT_Data+7),
								*(pktTag->PT_Data+8),*(pktTag->PT_Data+9),
							*(pktTag->PT_Data+10),*(pktTag->PT_Data+11),
								*(pktTag->PT_Data+12),*(pktTag->PT_Data+13),
								*(pktTag->PT_Data+14),*(pktTag->PT_Data+15),
							*(pktTag->PT_Data+16),*(pktTag->PT_Data+17),
						*(pktTag->PT_Data+18),*(pktTag->PT_Data+19),
								*(pktTag->PT_Data+20),*(pktTag->PT_Data+21),
							*(pktTag->PT_Data+22),*(pktTag->PT_Data+23),
								*(pktTag->PT_Data+24),*(pktTag->PT_Data+25),
								*(pktTag->PT_Data+26),*(pktTag->PT_Data+27),
							*(pktTag->PT_Data+28),*(pktTag->PT_Data+29)));
				}
				else
					KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
				IpxFwdDbgPrint (DBG_SEND, DBG_INFORMATION,
					("IpxFwd: Queued external packet %08lx on if %ld.\n",
					pktTag, dstIf->ICB_Index));
				if (*(pktTag->PT_Data + IPXH_PKTTYPE) != IPX_NETBIOS_TYPE)
					NOTHING;
				else if (!(pktTag->PT_Flags&PT_NB_DESTROY)) {
						// If this nb packet is not to be destroyed after this
						// send, we have to make a copy of it to send on
						// other interfaces while the original is waiting
						// for connection
					PPACKET_TAG	newPktTag;
					DuplicatePacket (pktTag, newPktTag);
					if (newPktTag!=NULL) {
						UINT			bytesCopied;
						PNDIS_PACKET	packet = CONTAINING_RECORD (pktTag,
														NDIS_PACKET,
														ProtocolReserved);
						PNDIS_PACKET	newPacket = CONTAINING_RECORD (newPktTag,
														NDIS_PACKET,
														ProtocolReserved);
						NdisCopyFromPacketToPacket (newPacket, 0,
									GETUSHORT(pktTag->PT_Data+IPXH_LENGTH),
									packet, 0, &bytesCopied);

						ASSERT (bytesCopied==GETUSHORT(pktTag->PT_Data+IPXH_LENGTH));
						IpxFwdDbgPrint (DBG_NETBIOS,
							DBG_INFORMATION,
							("IpxFwd: Duplicated queued nb packet"
							" %08lx -> %08lx on if %ld.\n",
							pktTag, newPktTag, dstIf->ICB_Index));
						AcquireInterfaceReference (dstIf);
						newPktTag->PT_InterfaceReference = dstIf;
						newPktTag->PT_PerfCounter = pktTag->PT_PerfCounter;
						QueueNetbiosPacket (newPktTag);
							// The original copy will have to be
							// destroyed after it is sent on the
							// connected interface
						pktTag->PT_Flags |= PT_NB_DESTROY;
					}
				}
				status = NDIS_STATUS_PENDING;
				break;
			}
			else {	// Process keepalives
				LONGLONG	curTime;
				KeQuerySystemTime ((PLARGE_INTEGER)&curTime);
				if (((curTime-dstIf->ICB_DisconnectTime)/10000000) < SpoofingTimeout) {
					KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
					IpxFwdDbgPrint (DBG_SPOOFING, DBG_INFORMATION,
						("IpxFwd: Queueing reply to keepalive from server"
						" on if %ld (ifCB %lx)"
						" at %02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%02x%02x.\n",
						dstIf->ICB_Index, dstIf,
						*(pktTag->PT_Data+IPXH_SRCNET),*(pktTag->PT_Data+IPXH_SRCNET+1),
							*(pktTag->PT_Data+IPXH_SRCNET+2),*(pktTag->PT_Data+IPXH_SRCNET+3),
						*(pktTag->PT_Data+IPXH_SRCNODE),*(pktTag->PT_Data+IPXH_SRCNODE+1),
							*(pktTag->PT_Data+IPXH_SRCNODE+2),*(pktTag->PT_Data+IPXH_SRCNODE+3),
							*(pktTag->PT_Data+IPXH_SRCNODE+4),*(pktTag->PT_Data+IPXH_SRCNODE+5),
						*(pktTag->PT_Data+IPXH_SRCSOCK),*(pktTag->PT_Data+IPXH_SRCNODE+1)));
						// Spoof the packet if timeout has not been exceeded
					KeAcquireSpinLock (&SpoofingQueueLock, &oldIRQL);
					InsertTailList (&SpoofingQueue, &pktTag->PT_QueueLink);
					if (!SpoofingWorkerActive
							&& EnterForwarder()) {
						SpoofingWorkerActive = TRUE;
						ExQueueWorkItem (&SpoofingWorker, DelayedWorkQueue);
					}
					KeReleaseSpinLock (&SpoofingQueueLock, oldIRQL);
						// We will actually send this packet though
						// in other direction, so mark it as pending
						// to prevent ProcessSentPacket to be called
					status = NDIS_STATUS_PENDING;
					break;
				}
				// else don't spoof (fall through and fail the packet)
			}
		case FWD_OPER_STATE_DOWN:
			KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
			status = NDIS_STATUS_ADAPTER_NOT_READY;
			IpxFwdDbgPrint (DBG_SEND, DBG_WARNING,
				("IpxFwd: Failed external packet %08lx on if %ld(down?).\n",
				pktTag, dstIf->ICB_Index));
			break;
		default:
		    status = STATUS_UNSUCCESSFUL;
			ASSERTMSG ("Invalid operational state ", FALSE);
		}
	}
	else {
		IpxFwdDbgPrint (DBG_SEND, DBG_WARNING,
			("IpxFwd: Could not send packet %08lx on if %ld (quota exceeded).\n",
			pktTag, dstIf->ICB_Index));
		status = NDIS_STATUS_RESOURCES;
	}

	if (status!=NDIS_STATUS_PENDING)
		ProcessSentPacket (dstIf, pktTag, status);
}

/*++
*******************************************************************
    F w S e n d C o m p l e t e

Routine Description:
	Called by IPX stack when send completes asynchronously
Arguments:
	pktDscr	- descriptor of the completed packet
	status	- result of send operation
Return Value:
	None

*******************************************************************
--*/
VOID
IpxFwdSendComplete (
	PNDIS_PACKET	pktDscr,
	NDIS_STATUS		status
	) {
	PPACKET_TAG     pktTag;
	PNDIS_BUFFER	bufDscr;

	pktTag = (PPACKET_TAG)pktDscr->ProtocolReserved;

	NdisUnchainBufferAtFront (pktDscr, &bufDscr);
	ASSERT (bufDscr==pktTag->PT_MacHdrBufDscr);

	NdisQueryPacket(pktDscr,
            NULL,
            NULL,
            &bufDscr,
            NULL);
    NdisAdjustBufferLength(bufDscr,
		pktTag->PT_Segment->PS_SegmentList->SL_BlockSize);
	NdisRecalculatePacketCounts (pktDscr);
#if DBG
	{
	KIRQL			oldIRQL;
	KeAcquireSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, &oldIRQL);
	RemoveEntryList (&pktTag->PT_QueueLink);
	KeReleaseSpinLock (&pktTag->PT_InterfaceReference->ICB_Lock, oldIRQL);
	}
#endif
	ProcessSentPacket (pktTag->PT_InterfaceReference, pktTag, status);
	LeaveForwarder (); // Entered before calling IpxSendPacket
}


/*++
*******************************************************************
	
	F w I n t e r n a l S e n d

Routine Description:
	Filter and routes packets sent by IPX stack
Arguments:
   LocalTarget		- the NicId and next hop router MAC address
   Context			- preferred interface on which to send
   Packet			- packet to be sent
   ipxHdr			- pointer to ipx header inside the packet
   PacketLength		- length of the packet
   fIterate         - a flag to indicate if this is a packet for the
                        iteration of which the Fwd takes responsibility
                        - typically type 20 NetBIOS frames
Return Value:

   STATUS_SUCCESS - if the preferred NIC was OK and packet passed filtering
   STATUS_NETWORK_UNREACHABLE - if the preferred was not OK or packet failed filtering
   STATUS_PENDING - packet was queued until connection is established
*******************************************************************
--*/
NTSTATUS
IpxFwdInternalSend (
	IN OUT PIPX_LOCAL_TARGET	LocalTarget,
	IN ULONG_PTR				Context,
	IN PNDIS_PACKET				pktDscr,
	IN PUCHAR					ipxHdr,
	IN PUCHAR					data,
	IN ULONG					PacketLength,
    IN BOOLEAN                  fIterate
	) {
	PINTERFACE_CB				dstIf = NULL, // Initialized to indicate
                                // first path through the iteration
                                // as well as the fact the we do not
                                // know it initially
                                stDstIf = NULL;    // Static destination for
                                // NetBIOS names
	PFWD_ROUTE					fwRoute = NULL;
	ULONG						dstNet;
    USHORT                      dstSock;
	NTSTATUS					status;

	if (!EnterForwarder())
		return STATUS_NETWORK_UNREACHABLE;

	if (IS_IF_ENABLED(InternalInterface)
            && ((*(ipxHdr+IPXH_PKTTYPE) != IPX_NETBIOS_TYPE)
                || InternalInterface->ICB_NetbiosAccept)) {

        // Print out the fact that we're going to send and display the nic id's
	    IpxFwdDbgPrint (DBG_INT_SEND, DBG_INFORMATION,
		    ("IpxFwd: InternalSend entered: nicid= %d  if= %d  ifnic= %d  fIterate: %d",
		      LocalTarget->NicId,
		      ((Context!=INVALID_CONTEXT_VALUE) & (Context!=VIRTUAL_NET_FORWARDER_CONTEXT)) ? ((PINTERFACE_CB)Context)->ICB_Index : -1,
		      ((Context!=INVALID_CONTEXT_VALUE) & (Context!=VIRTUAL_NET_FORWARDER_CONTEXT)) ? ((PINTERFACE_CB)Context)->ICB_NicId : -1,
		      fIterate
		      ));

        do { // Big loop used to iterate over interfaces
            status = STATUS_SUCCESS;    // Assume success

            // fIterate is normally set to false and so the following codepath
            // is the most common.  The only time fIterate is set to true is when
            // this is a type 20 broadcast that needs to be sent over each interface.
		    if (!fIterate) {
    		    dstNet = GETULONG (ipxHdr+IPXH_DESTNET);

			    if (Context!=INVALID_CONTEXT_VALUE) {
                    if (Context!=VIRTUAL_NET_FORWARDER_CONTEXT) {
					    // IPX driver supplied interface context, just verify that it
					    // exists and can be used to reach the destination network
				        dstIf = InterfaceContextToReference ((PVOID)Context,
												    LocalTarget->NicId);
                    }
                    else {
                        dstIf = InternalInterface;
                        AcquireInterfaceReference (dstIf);
                    }
				    if (dstIf!=NULL) {
						    // It does exist
						    // First process direct connections
					    if ((dstNet==0)
							    || (dstNet==dstIf->ICB_Network)) {
						    NOTHING;
					    }
					    else { // Network is not connected directly
						    PINTERFACE_CB	dstIf2;
							    // Verify the route
						    dstIf2 = FindDestination (dstNet,
											    ipxHdr+IPXH_DESTNODE,
												    &fwRoute);
						    if (dstIf==dstIf2) {
								    // Route OK, release the extra interface reference
							    ReleaseInterfaceReference (dstIf2);
						    }
						    else {
								    // Route not OK, release interface/route references
							    InterlockedIncrement (&InternalInterface->ICB_Stats.InNoRoutes);
							    IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
								    ("IpxFwd: Failed direct internal send on"
								    " if %ld to %08lx:%02x%02x%02x%02x%02x%02x"
								    " (no route).\n",
								    dstIf->ICB_Index, dstNet,
								    LocalTarget->MacAddress[0],
									    LocalTarget->MacAddress[1],
									    LocalTarget->MacAddress[2],
									    LocalTarget->MacAddress[3],
									    LocalTarget->MacAddress[4],
									    LocalTarget->MacAddress[5]));
							    if (dstIf2!=NULL) {
								    ReleaseInterfaceReference (dstIf2);
							    }
							    status = STATUS_NETWORK_UNREACHABLE;
                                break;
						    }
					    }
				    }
				    else {
					    InterlockedIncrement (&InternalInterface->ICB_Stats.InDiscards);
					    IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
						    ("IpxFwd: Invalid interface context (%08lx)"
						    " from IPX driver on internal send to"
						    " %08lx:%02x%02x%02x%02x%02x%02x.\n",
						    Context,  dstNet,
						    LocalTarget->MacAddress[0],
							    LocalTarget->MacAddress[1],
							    LocalTarget->MacAddress[2],
							    LocalTarget->MacAddress[3],
							    LocalTarget->MacAddress[4],
							    LocalTarget->MacAddress[5]));
                        status = STATUS_NO_SUCH_DEVICE;
                        break;
				    }
			    }
			    else {// No interface context supplied by IPX driver, have to find the route
				    dstIf = FindDestination (dstNet, ipxHdr+IPXH_DESTNODE,
										    &fwRoute);
				    if (dstIf!=NULL)
					    NOTHING;
				    else {
					    InterlockedIncrement (&InternalInterface->ICB_Stats.InNoRoutes);
					    IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
						    ("IpxFwd: Failed internal send because no route to"
						    " %08lx:%02x%02x%02x%02x%02x%02x exists.\n",
						    LocalTarget->MacAddress[0],
							    LocalTarget->MacAddress[1],
							    LocalTarget->MacAddress[2],
							    LocalTarget->MacAddress[3],
							    LocalTarget->MacAddress[4],
							    LocalTarget->MacAddress[5]));
					    status = STATUS_NETWORK_UNREACHABLE;
                        break;
				    }
			    }
    		    InterlockedIncrement (&InternalInterface->ICB_Stats.InDelivers);
		    }

            // fIterate was set to true.
		    // In this case, the stack is calling the forwarder with fIterate set
            // to true until the fwd returns STATUS_NETWORK_UNREACHABLE.  It is
            // the fwd's job to return the NEXT nicid over which to send each time
            // it is called.  This allows the fwd to not enumerate interfaces which
            // have been disabled for netbios delivery.
		    else {
		        dstNet = 0;	// Don't care, it must be a local send

		        // See if it's a type 20 broadcast
			    if (*(ipxHdr+IPXH_PKTTYPE) == IPX_NETBIOS_TYPE) {
			
			        // dstIf is initialized to null.  The only way it
			        // would be non-null is if this is not our first time through
			        // the big do-while loop in this function and if on the last
			        // time through this big loop, we found an interface that we
			        // can't send the packet over so we're looking for the next
			        // one now.
                    if (dstIf==NULL) { // First time through internal loop
   			            dstSock = GETUSHORT (ipxHdr+IPXH_DESTSOCK);

					        // See if we can get a static route for this packet
				        if (dstSock==IPX_NETBIOS_SOCKET)
					        stDstIf = FindNBDestination (data+(NB_NAME-IPXH_HDRSIZE));
				        else if (dstSock==IPX_SMB_NAME_SOCKET)
					        stDstIf = FindNBDestination (data+(SMB_NAME-IPXH_HDRSIZE));
                        else
                            stDstIf = NULL;
                    }

                    // The first time the stack calls us with fIterate==TRUE, it will
                    // give us an INVALID_CONTEXT_VALUE so we can tell it which is the
                    // first nic id in the iteration as per our interface table.
                    if ((Context==INVALID_CONTEXT_VALUE) && (dstIf==NULL)) {
                        // First time through the loop, increment counters
                    	InterlockedIncrement (&InternalInterface->ICB_Stats.InDelivers);
                    	InterlockedIncrement (&InternalInterface->ICB_Stats.NetbiosSent);

                        // stDstIf is the interface to use if there is a static route
                        // to the given network.
                        if (stDstIf!=NULL) {
                            dstIf = stDstIf;
				            IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					            ("IpxFwd: Allowed internal NB broadcast (1st iteration) on if %d (%lx)"
					            " to static name %.16s.\n",
                                dstIf->ICB_Index, dstIf,
					            (dstSock==IPX_NETBIOS_SOCKET)
						            ? data+(NB_NAME-IPXH_HDRSIZE)
						            : ((dstSock==IPX_SMB_NAME_SOCKET)
							            ? data+(SMB_NAME-IPXH_HDRSIZE)
							            : "Not a name frame")
					            ));
                        }

                        // There is no static route.  Tell the stack to use the
                        // next interface in this enumeration.
                        else {
                            dstIf = GetNextInterfaceReference (NULL);
                            if (dstIf!=NULL)
				                IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					                ("IpxFwd: Allowed internal nb broadcast (1st iteration) on if %d (%lx),"
					                " to name %.16s.\n",
                                    dstIf->ICB_Index, dstIf,
					                (dstSock==IPX_NETBIOS_SOCKET)
						                ? data+(NB_NAME-IPXH_HDRSIZE)
						                : ((dstSock==IPX_SMB_NAME_SOCKET)
							                ? data+(SMB_NAME-IPXH_HDRSIZE)
							                : "Not a name frame")
					                ));
                            else {
				                IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					                ("IpxFwd: Nb broadcast no destinations"
                                    " to name %.16s.\n",
					                (dstSock==IPX_NETBIOS_SOCKET)
						                ? data+(NB_NAME-IPXH_HDRSIZE)
						                : ((dstSock==IPX_SMB_NAME_SOCKET)
							                ? data+(SMB_NAME-IPXH_HDRSIZE)
							                : "Not a name frame")
					                ));
                                status = STATUS_NETWORK_UNREACHABLE;
                                break;
                            }
                        }
                    }

                    // The following path is taken if the stack provided a
                    // valid context and set fIterate to true.  Our job here
                    // is to return the next nic id according to our interface
                    // table over which to send the pack.
                    else {

                        // This path is taken if there is no static netbios route
                        if (stDstIf==NULL) {
                            // dstIf will be null if this is the first time through the
                            // big do-while loop in this function.
                            if (dstIf==NULL)
                                dstIf = InterfaceContextToReference ((PVOID)Context,
												            LocalTarget->NicId);
                            dstIf = GetNextInterfaceReference (dstIf);

                            // If we find a next interface over which to send we'll
                            // put the nic id of that interface into the local target
                            // after exiting the big do-while loop.
                            if (dstIf!=NULL) {
				                IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					                ("IpxFwd: Allowed internal NB broadcast (1+ iteration)"
                                    " on if %d (%lx, ctx: %08lx, nic: %d)"
                                    " to name %.16s.\n",
                                    dstIf->ICB_Index, dstIf,
                                    Context, LocalTarget->NicId,
					                (dstSock==IPX_NETBIOS_SOCKET)
						                ? data+(NB_NAME-IPXH_HDRSIZE)
						                : ((dstSock==IPX_SMB_NAME_SOCKET)
							                ? data+(SMB_NAME-IPXH_HDRSIZE)
							                : "Not a name frame")
					                ));
                            }

                            // Otherwise, we'll break out here and return
                            // STATUS_NETWORK_UNREACHABLE which will signal to the
                            // stack that we have finished the iteration.
                            else {
				                IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					                ("IpxFwd: NB broadcast no more iterations"
                                    " for ctx: %08lx, nic: %d"
                                    " to name %.16s.\n",
                                    Context, LocalTarget->NicId,
					                (dstSock==IPX_NETBIOS_SOCKET)
						                ? data+(NB_NAME-IPXH_HDRSIZE)
						                : ((dstSock==IPX_SMB_NAME_SOCKET)
							                ? data+(SMB_NAME-IPXH_HDRSIZE)
							                : "Not a name frame")
					                ));
                                status = STATUS_NETWORK_UNREACHABLE;
                                break;
                            }
                        }

                        // This path is taken if there is a static netbios route.  In this
                        // case, we don't need to iterate over all interfaces so we break
                        // and tell the stack that we finished our iteration.
                        else {
				            IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					            ("IpxFwd: Static NB broadcast (1+ iteration)"
                                " on if %d (%lx, ctx: %08lx, nic: %d)"
                                " to name %.16s.\n",
                                stDstIf->ICB_Index, stDstIf,
                                Context, LocalTarget->NicId,
					            (dstSock==IPX_NETBIOS_SOCKET)
						            ? data+(NB_NAME-IPXH_HDRSIZE)
						            : ((dstSock==IPX_SMB_NAME_SOCKET)
							            ? data+(SMB_NAME-IPXH_HDRSIZE)
							            : "Not a name frame")
					            ));
                            ReleaseInterfaceReference (stDstIf);
                            status = STATUS_NETWORK_UNREACHABLE;
                            break;
                        }
                    }
                }

                // This path is taken if fIterate was set to true but this
                // is not a type 20 broadcast.  I doubt that this path is
                // ever even taken since for general broadcasts, the stack
                // handles the iteration.
                else {
                    if ((dstIf==NULL)
                            && (Context!=INVALID_CONTEXT_VALUE))
                        dstIf = InterfaceContextToReference ((PVOID)Context,
												        LocalTarget->NicId);
                    dstIf = GetNextInterfaceReference (dstIf);
                    if (dstIf!=NULL) {
				        IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					        ("IpxFwd: Allowed internal iterative send"
                            " on if %d (%lx, ctx: %08lx, nic: %d)"
						    " to %02x%02x%02x%02x%02x%02x.\n",
                            dstIf->ICB_Index, dstIf,
                            Context, LocalTarget->NicId,
						    LocalTarget->MacAddress[0],
							    LocalTarget->MacAddress[1],
							    LocalTarget->MacAddress[2],
							    LocalTarget->MacAddress[3],
							    LocalTarget->MacAddress[4],
							    LocalTarget->MacAddress[5]));

                    }
                    else {
				        IpxFwdDbgPrint (DBG_NETBIOS, DBG_INFORMATION,
					        ("IpxFwd: No destinations to internal iterative send"
                            " for ctx: %08lx, nic: %d"
						    " to %02x%02x%02x%02x%02x%02x.\n",
                            Context, LocalTarget->NicId,
						    LocalTarget->MacAddress[0],
							    LocalTarget->MacAddress[1],
							    LocalTarget->MacAddress[2],
							    LocalTarget->MacAddress[3],
							    LocalTarget->MacAddress[4],
							    LocalTarget->MacAddress[5]));
                        status = STATUS_NETWORK_UNREACHABLE;
                        break;
                    }
                }

	        }	// End iterative send processing

		    // We were able to find a destination interface
		    if (IS_IF_ENABLED (dstIf)
                    && ((*(ipxHdr+IPXH_PKTTYPE) != IPX_NETBIOS_TYPE)
                        || (dstIf->ICB_NetbiosDeliver==FWD_NB_DELIVER_ALL)
                        || ((dstIf->ICB_NetbiosDeliver==FWD_NB_DELIVER_IF_UP)
				            && (dstIf->ICB_Stats.OperationalState==FWD_OPER_STATE_UP))
                        || ((stDstIf!=NULL)
                            && (dstIf->ICB_NetbiosDeliver==FWD_NB_DELIVER_STATIC)))) {
            	KIRQL			oldIRQL;
                FILTER_ACTION   action;

                // In/Out filter check and statistics update

                action = FltFilter (ipxHdr, IPXH_HDRSIZE,
						    InternalInterface->ICB_FilterInContext,
						    dstIf->ICB_FilterOutContext);
			    if (action==FILTER_PERMIT) {
                    NOTHING;
			    }
			    else {
                    InterlockedIncrement (&dstIf->ICB_Stats.OutFiltered);
				    status = STATUS_NETWORK_UNREACHABLE;
                    break;
			    }

        		KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
				// All set, try to send now
	    		switch (dstIf->ICB_Stats.OperationalState) {
		    	case FWD_OPER_STATE_UP:
					    // Interface is up, let it go right away
					    // Set NIC ID
                    if (dstIf!=InternalInterface) {
				        ADAPTER_CONTEXT_TO_LOCAL_TARGET (
								        dstIf->ICB_AdapterContext,
								        LocalTarget);
                    }
                    else {
				        CONSTANT_ADAPTER_CONTEXT_TO_LOCAL_TARGET (
								        VIRTUAL_NET_ADAPTER_CONTEXT,
								        LocalTarget);
                    }
					    // Set destination node
				    if (IsLocalSapNonAgentAdvertisement (ipxHdr,data,PacketLength,dstIf)) {
						    // Loop back sap ads from non-sap socket
					    IPX_NODE_CPY (&LocalTarget->MacAddress,
                                        dstIf->ICB_LocalNode);
                    }
				    else if ((dstNet==0) || (dstNet==dstIf->ICB_Network)) {
						    // Direct connection: send to destination specified
						    // in the header
					    IPX_NODE_CPY (LocalTarget->MacAddress,
									    ipxHdr+IPXH_DESTNODE);
				    }
				    else {	// Indirect connection: send to next hop router
					    if (dstIf->ICB_InterfaceType==FWD_IF_PERMANENT) {
						    ASSERT (fwRoute!=NULL);
						    IPX_NODE_CPY (LocalTarget->MacAddress,
									    fwRoute->FR_NextHopAddress);
					    }
					    else {
							    // Only one peer on the other side
						    IPX_NODE_CPY (LocalTarget->MacAddress,
									    dstIf->ICB_RemoteNode);
					    }
				    }
				    KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
					    // Update statistics
				    InterlockedIncrement (
					    &dstIf->ICB_Stats.OutDelivers);
				    if (*(ipxHdr+IPXH_PKTTYPE)==IPX_NETBIOS_TYPE)
					    InterlockedIncrement (
						    &dstIf->ICB_Stats.NetbiosSent);

				    IpxFwdDbgPrint (DBG_INT_SEND, DBG_INFORMATION,
					    ("IpxFwd: Allowed internal send:"
					    " %ld-%08lx:%02x%02x%02x%02x%02x%02x.\n",
					    dstIf->ICB_Index, dstNet,
					    LocalTarget->MacAddress[0],
						    LocalTarget->MacAddress[1],
						    LocalTarget->MacAddress[2],
						    LocalTarget->MacAddress[3],
						    LocalTarget->MacAddress[4],
						    LocalTarget->MacAddress[5]));
				    // status = STATUS_SUCCESS;	// Let it go
				    break;
			    case FWD_OPER_STATE_SLEEPING:
					    // Interface is disconnected, queue the packet and try to connecte
				    if ((*(ipxHdr+IPXH_PKTTYPE)!=0)
						    || (*(ipxHdr+IPXH_LENGTH)!=IPXH_HDRSIZE+2)
						    || (*(data+1)!='?')) {
						    // Not a keep-alive packet,
					    if (((*(ipxHdr+IPXH_PKTTYPE)!=IPX_NETBIOS_TYPE))
							    || (dstIf->ICB_NetbiosDeliver!=FWD_NB_DELIVER_IF_UP)) {
							    // Not a netbios broadcast or we are allowed to connect
							    // the interface to deliver netbios broadcasts
						    if (InterlockedDecrement (&dstIf->ICB_PendingQuota)>=0) {
							    PINTERNAL_PACKET_TAG	pktTag;
								    // Create a queue element to enqueue the packet
							    pktTag = (PINTERNAL_PACKET_TAG)ExAllocatePoolWithTag (
														    NonPagedPool,
														    sizeof (INTERNAL_PACKET_TAG),
														    FWD_POOL_TAG);
							    if (pktTag!=NULL) {
								    pktTag->IPT_Packet = pktDscr;
								    pktTag->IPT_Length = PacketLength;
								    pktTag->IPT_DataPtr = ipxHdr;
									    // Save next hop address if after connection is
									    // established we determine that destination net
									    // is not connected directly
								    if (fwRoute!=NULL)
									    IPX_NODE_CPY (pktTag->IPT_Target.MacAddress,
													    fwRoute->FR_NextHopAddress);
								    AcquireInterfaceReference (dstIf);	// To make sure interface
												    // block won't go away until we are done with
												    // the packet
								    pktTag->IPT_InterfaceReference = dstIf;
								    InsertTailList (&dstIf->ICB_InternalQueue,
														    &pktTag->IPT_QueueLink);
								    if (!IS_IF_CONNECTING (dstIf)) {
									    QueueConnectionRequest (dstIf, pktDscr, ipxHdr, oldIRQL);
									    IpxFwdDbgPrint (DBG_DIALREQS, DBG_WARNING,
										    ("IpxFwd: Queued dd request on if %ld (ifCB:%08lx)"
										    " for internal packet"
										    " to %02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%02x%02x"
										    " from socket:%02x%02x\n",
										    dstIf->ICB_Index, dstIf,
										    *(ipxHdr+6),*(ipxHdr+7),
												    *(ipxHdr+8),*(ipxHdr+9),
											    *(ipxHdr+10),*(ipxHdr+11),
												    *(ipxHdr+12),*(ipxHdr+13),
												    *(ipxHdr+14),*(ipxHdr+15),
											    *(ipxHdr+16),*(ipxHdr+17),
										    *(ipxHdr+28),*(ipxHdr+29)));
								    }
								    else
									    KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
								    IpxFwdDbgPrint (DBG_INT_SEND, DBG_INFORMATION,
									    ("IpxFwd: Queueing internal send packet %08lx on if %ld.\n",
									    pktTag, dstIf->ICB_Index));
								    status = STATUS_PENDING;
								    break;
							    }
							    else {
								    IpxFwdDbgPrint (DBG_INT_SEND, DBG_ERROR,
									    ("IpxFwd: Could not allocate"
									    " internal packet tag.\n"));
							    }
						    }
						    InterlockedIncrement (&dstIf->ICB_PendingQuota);
					    }
					    else {
						    IpxFwdDbgPrint (DBG_NETBIOS, DBG_WARNING,
							    ("IpxFwd: Droped internal NB packet"
							    " because FWD_NB_DELIVER_IF_UP.\n"));
					    }
				    }
				    else { // Process keep-alives
					    LONGLONG	curTime;
					    KeQuerySystemTime ((PLARGE_INTEGER)&curTime);
					    if (((curTime-dstIf->ICB_DisconnectTime)/10000000) < SpoofingTimeout) {
						    PPACKET_TAG pktTag;
							    // Spoofing timeout has not been exceeded,
							    // Create a reply packet
						    AllocatePacket (WanPacketListId,
								    WanPacketListId,
								    pktTag);
						    if (pktTag!=NULL) {
							    KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
							    PUTUSHORT (0xFFFF, pktTag->PT_Data+IPXH_CHECKSUM);
							    PUTUSHORT ((IPXH_HDRSIZE+2), pktTag->PT_Data+IPXH_LENGTH);
							    *(pktTag->PT_Data+IPXH_XPORTCTL) = 0;
							    *(pktTag->PT_Data+IPXH_PKTTYPE) = 0;
							    memcpy (pktTag->PT_Data+IPXH_DESTADDR,
									    ipxHdr+IPXH_SRCADDR,
									    12);
							    memcpy (pktTag->PT_Data+IPXH_SRCADDR,
									    ipxHdr+IPXH_DESTADDR,
									    12);
							    *(pktTag->PT_Data+IPXH_HDRSIZE) = *data;
							    *(pktTag->PT_Data+IPXH_HDRSIZE+1) = 'Y';
								    // Destination for this packet will have to
								    // be the first active LAN adapter in the system
								    // SHOULD BE REMOVED WHEN LOOPBACK SUPPORT US ADDED BY IPX

							    pktTag->PT_InterfaceReference = NULL;
							    IpxFwdDbgPrint (DBG_SPOOFING, DBG_INFORMATION,
								    ("IpxFwd: Queueing reply to keepalive from internal server"
								    " at %02x%02x.\n",*(ipxHdr+IPXH_DESTSOCK),*(ipxHdr+IPXH_DESTSOCK+1)));
								    // Enqueue to spoofing queue to be sent back
								    // to the server
							    KeAcquireSpinLock (&SpoofingQueueLock, &oldIRQL);
							    InsertTailList (&SpoofingQueue, &pktTag->PT_QueueLink);
								    // Start worker if not running already
							    if (!SpoofingWorkerActive
									    && EnterForwarder()) {
								    SpoofingWorkerActive = TRUE;
								    ExQueueWorkItem (&SpoofingWorker, DelayedWorkQueue);
							    }
							    KeReleaseSpinLock (&SpoofingQueueLock, oldIRQL);
							    status = STATUS_DROP_SILENTLY;
							    break;
						    }
						    else {
							    IpxFwdDbgPrint (DBG_SPOOFING, DBG_ERROR,
								    ("IpxFwd: Could not allocate"
								    " packet tag for spoofing.\n"));
						    }
					    }
					    else {
						    IpxFwdDbgPrint (DBG_SPOOFING, DBG_WARNING,
							    ("IpxFwd: Internal spoofing"
							    " timeout exceded.\n"));
					    }
				    }
			    case FWD_OPER_STATE_DOWN:
					    // Interface down or send failed
				    KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
				    if (*(ipxHdr+IPXH_PKTTYPE)!=IPX_NETBIOS_TYPE)
    				    InterlockedIncrement (
						    &dstIf->ICB_Stats.OutDiscards);
				    IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
					    ("IpxFwd: Internal send not allowed"
					    " on if %ld (down?).\n", dstIf->ICB_Index));
				    status = STATUS_NETWORK_UNREACHABLE;
				    break;
			    default:
				    ASSERTMSG ("Invalid operational state ", FALSE);
			    }
		    }
		    else {// Interface is disabled
    		    if (*(ipxHdr+IPXH_PKTTYPE)!=IPX_NETBIOS_TYPE)
	    		    InterlockedIncrement (&dstIf->ICB_Stats.OutDiscards);
			    IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
				    ("IpxFwd: Internal send not allowed"
				    " on because dst if (or Netbios deliver on it) %ld (ifCB: %08lx) is disabled.\n",
				    dstIf->ICB_Index, dstIf));
                status = STATUS_NETWORK_UNREACHABLE;
		    }

		
	    }
        while (fIterate && (status!=STATUS_SUCCESS) && (status!=STATUS_PENDING));

        if (dstIf!=NULL)
		    ReleaseInterfaceReference (dstIf);
		if (fwRoute!=NULL)
			ReleaseRouteReference (fwRoute);
    }
	else {	// Internal interface is disabled
		IpxFwdDbgPrint (DBG_INT_SEND, DBG_WARNING,
			("IpxFwd: Internal send not allowed"
			" because internal if (or Netbios accept on it) is disabled.\n"));
		InterlockedIncrement (
				&InternalInterface->ICB_Stats.InDiscards);
		status = STATUS_NETWORK_UNREACHABLE;
	}

	LeaveForwarder ();
	return status;
}


/*++
*******************************************************************

	P r o c e s s I n t e r n a l Q u e u e

Routine Description:
	Processes packets in the interface internal queue.
	Called when connection request completes
Arguments:
	dstIf - interface to process
Return Value:
	None
*******************************************************************
--*/
VOID
ProcessInternalQueue (
	PINTERFACE_CB	dstIf
	) {
	KIRQL						oldIRQL;
	LIST_ENTRY					tempQueue;

	KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
	InsertHeadList (&dstIf->ICB_InternalQueue, &tempQueue);
	RemoveEntryList (&dstIf->ICB_InternalQueue);
	InitializeListHead (&dstIf->ICB_InternalQueue);
	KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);

	while (!IsListEmpty (&tempQueue)) {
		PINTERNAL_PACKET_TAG		pktTag;
		PLIST_ENTRY					cur;
		NTSTATUS					status;

		cur = RemoveHeadList (&tempQueue);
		pktTag = CONTAINING_RECORD (cur, INTERNAL_PACKET_TAG, IPT_QueueLink);
		InterlockedIncrement (&dstIf->ICB_PendingQuota);

		KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
		if (IS_IF_ENABLED(dstIf)
				&& (dstIf->ICB_Stats.OperationalState==FWD_OPER_STATE_UP)) {
			IPX_NODE_CPY (pktTag->IPT_Target.MacAddress,
								dstIf->ICB_RemoteNode);
			ADAPTER_CONTEXT_TO_LOCAL_TARGET (
									dstIf->ICB_AdapterContext,
									&pktTag->IPT_Target);
			KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
			InterlockedIncrement (&dstIf->ICB_Stats.OutDelivers);
			if (*(pktTag->IPT_DataPtr + IPXH_PKTTYPE) == IPX_NETBIOS_TYPE) {
				InterlockedIncrement (&dstIf->ICB_Stats.NetbiosSent);
			}
			status = STATUS_SUCCESS;
		}
		else {
			KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
			InterlockedIncrement (&dstIf->ICB_Stats.OutDiscards);
			status = STATUS_NETWORK_UNREACHABLE;
		}

		IPXInternalSendCompletProc (&pktTag->IPT_Target,
							pktTag->IPT_Packet,
							pktTag->IPT_Length,
							status);
		IpxFwdDbgPrint (DBG_INT_SEND,
				NT_SUCCESS (status) ? DBG_INFORMATION : DBG_WARNING,
				("IpxFwd: Returned internal packet %08lx"
				" for send on if %ld with status %08lx.\n",
				pktTag, dstIf->ICB_Index, status));
		ReleaseInterfaceReference (pktTag->IPT_InterfaceReference);
		ExFreePool (pktTag);
	}
}
			
		

/*++
*******************************************************************

	P r o c e s s E x t e r n a l Q u e u e

Routine Description:
	Processes packets in the interface external queue.
	Called when connection request completes
Arguments:
	dstIf - interface to process
Return Value:
	None
*******************************************************************
--*/
VOID
ProcessExternalQueue (
	PINTERFACE_CB	dstIf
	) {
	KIRQL						oldIRQL;
	LIST_ENTRY					tempQueue;

	KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
	InsertHeadList (&dstIf->ICB_ExternalQueue, &tempQueue);
	RemoveEntryList (&dstIf->ICB_ExternalQueue);
	InitializeListHead (&dstIf->ICB_ExternalQueue);
	KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);

	while (!IsListEmpty (&tempQueue)) {
		PPACKET_TAG					pktTag;
		PLIST_ENTRY					cur;
		NDIS_STATUS					status;

		cur = RemoveHeadList (&tempQueue);
		pktTag = CONTAINING_RECORD (cur, PACKET_TAG, PT_QueueLink);

		KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
		if (IS_IF_ENABLED(dstIf)
				&& (dstIf->ICB_Stats.OperationalState==FWD_OPER_STATE_UP)) {
			IPX_NODE_CPY (pktTag->PT_Target.MacAddress,
								dstIf->ICB_RemoteNode);
			if (*(pktTag->PT_Data + IPXH_PKTTYPE) == IPX_NETBIOS_TYPE) {
				PUTULONG (dstIf->ICB_Network, pktTag->PT_Data+IPXH_DESTNET);
			}
			status = DoSend (dstIf, pktTag, oldIRQL);
			IpxFwdDbgPrint (DBG_SEND, DBG_INFORMATION,
				("IpxFwd: Sent queued external packet %08lx if %ld.\n",
				pktTag, dstIf->ICB_Index));
		}
		else {
			KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
			IpxFwdDbgPrint (DBG_SEND, DBG_WARNING,
				("IpxFwd: Dropped queued external packet %08lx on dead if %ld.\n",
				pktTag, dstIf->ICB_Index));
			status = STATUS_UNSUCCESSFUL;
		}

		if (status!=STATUS_PENDING)
			ProcessSentPacket (dstIf, pktTag, status);
	}
}
			
		
/*++
*******************************************************************

	S p o o f e r

Routine Description:
	Processes packets in spoofing queue
Arguments:
	None
Return Value:
	None
*******************************************************************
--*/
VOID
Spoofer (
	PVOID	Context
	) {
	KIRQL		oldIRQL;
	NTSTATUS	status;
	UNREFERENCED_PARAMETER (Context);

	KeAcquireSpinLock (&SpoofingQueueLock, &oldIRQL);
		// Keep going till queue is empty
	while (!IsListEmpty (&SpoofingQueue)) {
		PINTERFACE_CB dstIf;
		PPACKET_TAG pktTag = CONTAINING_RECORD (SpoofingQueue.Flink,
										PACKET_TAG,
										PT_QueueLink);
		RemoveEntryList (&pktTag->PT_QueueLink);
		KeReleaseSpinLock (&SpoofingQueueLock, oldIRQL);
		dstIf = pktTag->PT_InterfaceReference;
		if (dstIf==NULL) {
				// Replies for internal server require first active LAN adapter
				// SHOULD BE REMOVED WHEN LOOPBACK SUPPORT US ADDED BY IPX
			while ((dstIf=GetNextInterfaceReference (dstIf))!=NULL) {
				KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
				if (IS_IF_ENABLED (dstIf)
						&& (dstIf->ICB_Stats.OperationalState==FWD_OPER_STATE_UP)
						&& (dstIf->ICB_InterfaceType==FWD_IF_PERMANENT)) {
					pktTag->PT_InterfaceReference = dstIf;
					IPX_NODE_CPY (&pktTag->PT_Target.MacAddress, dstIf->ICB_LocalNode);
					status = DoSend (dstIf, pktTag, oldIRQL);	// releases spin lock
					if (status!=STATUS_PENDING)
						ProcessSentPacket (dstIf, pktTag, status);
					break;
				}
				else
					KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
			}
			if (dstIf==NULL) {
				FreePacket (pktTag);
			}

		}
		else {	// Reply for external server, interface is already known
			UCHAR	addr[12];
            FILTER_ACTION   action;
			pktTag->PT_Flags &= (~PT_SOURCE_IF);
			
				// Switch source and destination
			memcpy (addr, pktTag->PT_Data+IPXH_DESTADDR, 12);
			memcpy (pktTag->PT_Data+IPXH_DESTADDR,
				pktTag->PT_Data+IPXH_SRCADDR, 12);
			memcpy (pktTag->PT_Data+IPXH_SRCADDR, addr, 12);
				// Say yes in reply
			*(pktTag->PT_Data+IPXH_HDRSIZE+1) = 'Y';

            action = FltFilter (pktTag->PT_Data,
					GETUSHORT (pktTag->PT_Data+IPXH_LENGTH),
					dstIf->ICB_FilterInContext,
					pktTag->PT_SourceIf->ICB_FilterOutContext);
			if (action==FILTER_PERMIT) {

					// Release destination if and use source as destination
				ReleaseInterfaceReference (dstIf);
				dstIf = pktTag->PT_InterfaceReference = pktTag->PT_SourceIf;
				// Send the packet if we can
				KeAcquireSpinLock (&dstIf->ICB_Lock, &oldIRQL);
				if (IS_IF_ENABLED (dstIf)
					&& (dstIf->ICB_Stats.OperationalState==FWD_OPER_STATE_UP)) {
					status = DoSend (dstIf, pktTag, oldIRQL);
					if (status!=STATUS_PENDING)
						ProcessSentPacket (dstIf, pktTag, status);
				}
				else {
					KeReleaseSpinLock (&dstIf->ICB_Lock, oldIRQL);
					InterlockedIncrement (&dstIf->ICB_Stats.OutDiscards);
					ReleaseInterfaceReference (dstIf);
					FreePacket (pktTag);
				}
			}
			else {
                if (action==FILTER_DENY_OUT)
				    InterlockedIncrement (&pktTag->PT_SourceIf->ICB_Stats.OutFiltered);
                else {
                    ASSERT (action==FILTER_DENY_IN);
				    InterlockedIncrement (&dstIf->ICB_Stats.InFiltered);
                }
				ReleaseInterfaceReference (dstIf);
				ReleaseInterfaceReference (pktTag->PT_SourceIf);
				FreePacket (pktTag);
			}
		}
		KeAcquireSpinLock (&SpoofingQueueLock, &oldIRQL);
	} // end while
	SpoofingWorkerActive = FALSE;
	KeReleaseSpinLock (&SpoofingQueueLock, oldIRQL);
	LeaveForwarder ();
}