Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

12959 lines
560 KiB

/*++
Copyright(c) 1998,99 Microsoft Corporation
Module Name:
main.c
Abstract:
Windows Load Balancing Service (WLBS)
Driver - packet handling
Author:
kyrilf
shouse
--*/
#include <ndis.h>
#include "main.h"
#include "prot.h"
#include "nic.h"
#include "univ.h"
#include "tcpip.h"
#include "wlbsip.h"
#include "util.h"
#include "load.h"
#include "wlbsparm.h"
#include "params.h"
#include "log.h"
#include "trace.h"
#include "nlbwmi.h"
#include "main.tmh"
#if defined (NLB_TCP_NOTIFICATION)
/* For retrieving the IP address table to map an NLB instance
to an IP interface index; required for TCP notification. */
#include <tcpinfo.h>
#include <tdiinfo.h>
#endif
/* For querying TCP about the state of a TCP connection. */
#include "ntddtcp.h"
#include "ntddip.h"
NTSYSAPI
NTSTATUS
NTAPI
ZwDeviceIoControlFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength
);
EXPORT
VOID
NdisIMCopySendPerPacketInfo(
IN PNDIS_PACKET DstPacket,
IN PNDIS_PACKET SrcPacket
);
EXPORT
VOID
NdisIMCopySendCompletePerPacketInfo(
IN PNDIS_PACKET DstPacket,
PNDIS_PACKET SrcPacket
);
/* GLOBALS */
static ULONG log_module_id = LOG_MODULE_MAIN;
/* The global array of all NLB adapters. */
MAIN_ADAPTER univ_adapters [CVY_MAX_ADAPTERS];
/* The total number of NLB instances. */
ULONG univ_adapters_count = 0;
/* The head of the BDA team list. */
PBDA_TEAM univ_bda_teaming_list = NULL;
#if defined (NLB_HOOK_ENABLE)
/* The global NLB hook table. */
HOOK_TABLE univ_hooks;
#endif
/* PROCEDURES */
#if defined (NLB_HOOK_ENABLE)
/*
* Function: Main_hook_interface_init
* Description: This function initializes a hook interface by marking it unregistered.
* Parameters: pInterface - a pointer to the hook interface.
* Returns: Nothing.
* Author: shouse, 12.14.01
* Notes:
*/
VOID Main_hook_interface_init (PHOOK_INTERFACE pInterface)
{
pInterface->Registered = FALSE; /* Mark the interface as unregistered. */
pInterface->References = 0; /* Initialize the reference count to zero. */
pInterface->Owner = 0; /* Mark the owner as unknown. */
pInterface->Deregister = NULL; /* Zero out the de-register callback function pointer. */
}
/*
* Function: Main_hook_init
* Description: This function initializes a hook by marking it unused and unreferenced.
* Parameters: pHook - a pointer to the hook.
* Returns: Nothing.
* Author: shouse, 12.14.01
* Notes:
*/
VOID Main_hook_init (PHOOK pHook)
{
pHook->Registered = FALSE; /* Mark this hook as unregistered. */
/* Zero out the union of hook function pointers. */
NdisZeroMemory(&pHook->Hook, sizeof(HOOK_FUNCTION));
}
/*
* Function: Main_recv_hook
* Description: This function invokes the receive packet filter hook if one has been
* configured, and returns the response from the hook back to the caller.
* Parameters: ctxtp - a pointer to the NLB adapter context.
* pPacket - a pointer to the NDIS packet being received.
* pPacketInfo - a pointer to the information previously parsed from the NDIS packet.
* Returns: NLB_FILTER_HOOK_DIRECTIVE - the feedback from the hook invocation.
* Author: shouse, 04.19.02
* Notes: If no hook has been registered, this function returns NLB_FILTER_HOOK_PROCEED_WITH_HASH,
* which tells NLB to proceed as if the hook did not even exist.
*/
__inline NLB_FILTER_HOOK_DIRECTIVE Main_recv_hook (
PMAIN_CTXT ctxtp,
PNDIS_PACKET pPacket,
PMAIN_PACKET_INFO pPacketInfo)
{
/* By default, we proceed as per usual. */
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
/* If a receive packet filter hook has been registered, then we MAY need to
call the hook to inspect the packet. Note that this is not certainty yet,
as we check this boolean flag WITHOUT first grabbing the lock in order to
optimize the common execution path in which no hook is registered. */
if (univ_hooks.FilterHook.ReceiveHook.Registered)
{
/* If there is a chance this hook is registered, we need to grab the
hook lock to make sure. */
NdisDprAcquireSpinLock(&univ_hooks.FilterHook.Lock);
/* If the registered flag is set and we are holding the hook lock, then
we are certain that the hook is set and that we need to execute it.
To prevent this hook information from disappearing while we're using
it (mostly to prevent the registering component from disappering
before we call it), we need to increment the reference count on the
hook with the lock held. This will ensure that the hook cannot be
de-registered before we're done with it. */
if (univ_hooks.FilterHook.ReceiveHook.Registered)
{
ULONG dwFlags = 0;
if (!ctxtp->convoy_enabled)
dwFlags |= NLB_FILTER_HOOK_FLAGS_STOPPED;
else if (ctxtp->draining)
dwFlags |= NLB_FILTER_HOOK_FLAGS_DRAINING;
NdisInterlockedIncrement(&univ_hooks.FilterHook.Interface.References);
/* Release the spin lock protecting the hook. */
NdisDprReleaseSpinLock(&univ_hooks.FilterHook.Lock);
UNIV_ASSERT(univ_hooks.FilterHook.ReceiveHook.Hook.ReceiveHookFunction);
TRACE_FILTER("%!FUNC! Invoking the packet receive filter hook");
/* Invoke the hook and save the response. */
filter = (*univ_hooks.FilterHook.ReceiveHook.Hook.ReceiveHookFunction)(
univ_adapters[ctxtp->adapter_id].device_name,
pPacket,
(PUCHAR)pPacketInfo->Ethernet.pHeader,
pPacketInfo->Ethernet.Length,
(PUCHAR)pPacketInfo->IP.pHeader,
pPacketInfo->IP.Length,
dwFlags);
/* Decrement the reference count on the hook now that we're done with it. */
NdisInterlockedDecrement(&univ_hooks.FilterHook.Interface.References);
}
else
{
/* Release the spin lock protecting the hook. */
NdisDprReleaseSpinLock(&univ_hooks.FilterHook.Lock);
}
}
return filter;
}
/*
* Function: Main_query_hook
* Description: This function invokes the query packet filter hook if one has been
* configured, and returns the response from the hook back to the caller.
* Parameters:
* Returns: NLB_FILTER_HOOK_DIRECTIVE - the feedback from the hook invocation.
* Author: shouse, 04.19.02
* Notes: If no hook has been registered, this function returns NLB_FILTER_HOOK_PROCEED_WITH_HASH,
* which tells NLB to proceed as if the hook did not even exist.
*/
__inline NLB_FILTER_HOOK_DIRECTIVE Main_query_hook (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol)
{
/* By default, we proceed as per usual. */
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
/* If a query packet filter hook has been registered, then we MAY need to
call the hook to inspect the packet. Note that this is not certainty yet,
as we check this boolean flag WITHOUT first grabbing the lock in order to
optimize the common execution path in which no hook is registered. */
if (univ_hooks.FilterHook.QueryHook.Registered)
{
/* If there is a chance this hook is registered, we need to grab the
hook lock to make sure. */
NdisAcquireSpinLock(&univ_hooks.FilterHook.Lock);
/* If the registered flag is set and we are holding the hook lock, then
we are certain that the hook is set and that we need to execute it.
To prevent this hook information from disappearing while we're using
it (mostly to prevent the registering component from disappering
before we call it), we need to increment the reference count on the
hook with the lock held. This will ensure that the hook cannot be
de-registered before we're done with it. */
if (univ_hooks.FilterHook.QueryHook.Registered)
{
ULONG dwFlags = 0;
if (!ctxtp->convoy_enabled)
dwFlags |= NLB_FILTER_HOOK_FLAGS_STOPPED;
else if (ctxtp->draining)
dwFlags |= NLB_FILTER_HOOK_FLAGS_DRAINING;
NdisInterlockedIncrement(&univ_hooks.FilterHook.Interface.References);
/* Release the spin lock protecting the hook. */
NdisReleaseSpinLock(&univ_hooks.FilterHook.Lock);
UNIV_ASSERT(univ_hooks.FilterHook.QueryHook.Hook.QueryHookFunction);
TRACE_FILTER("%!FUNC! Invoking the packet query filter hook");
/* Invoke the hook and save the response. */
filter = (*univ_hooks.FilterHook.QueryHook.Hook.QueryHookFunction)(
univ_adapters[ctxtp->adapter_id].device_name,
svr_addr,
(USHORT)svr_port,
clt_addr,
(USHORT)clt_port,
(UCHAR)protocol,
TRUE, /* For now, all queries are in a receive context. */
dwFlags);
/* Decrement the reference count on the hook now that we're done with it. */
NdisInterlockedDecrement(&univ_hooks.FilterHook.Interface.References);
}
else
{
/* Release the spin lock protecting the hook. */
NdisReleaseSpinLock(&univ_hooks.FilterHook.Lock);
}
}
return filter;
}
/*
* Function: Main_send_hook
* Description: This function invokes the send packet filter hook if one has been
* configured, and returns the response from the hook back to the caller.
* Parameters: ctxtp - a pointer to the NLB adapter context.
* pPacket - a pointer to the NDIS packet being sent.
* pPacketInfo - a pointer to the information previously parsed from the NDIS packet.
* Returns: NLB_FILTER_HOOK_DIRECTIVE - the feedback from the hook invocation.
* Author: shouse, 04.19.02
* Notes: If no hook has been registered, this function returns NLB_FILTER_HOOK_PROCEED_WITH_HASH,
* which tells NLB to proceed as if the hook did not even exist.
*/
__inline NLB_FILTER_HOOK_DIRECTIVE Main_send_hook (
PMAIN_CTXT ctxtp,
PNDIS_PACKET pPacket,
PMAIN_PACKET_INFO pPacketInfo)
{
/* By default, we proceed as per usual. */
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
/* If a send packet filter hook has been registered, then we MAY need to
call the hook to inspect the packet. Note that this is not certainty yet,
as we check this boolean flag WITHOUT first grabbing the lock in order to
optimize the common execution path in which no hook is registered. */
if (univ_hooks.FilterHook.SendHook.Registered)
{
/* If there is a chance this hook is registered, we need to grab the
hook lock to make sure. */
NdisAcquireSpinLock(&univ_hooks.FilterHook.Lock);
/* If the registered flag is set and we are holding the hook lock, then
we are certain that the hook is set and that we need to execute it.
To prevent this hook information from disappearing while we're using
it (mostly to prevent the registering component from disappering
before we call it), we need to increment the reference count on the
hook with the lock held. This will ensure that the hook cannot be
de-registered before we're done with it. */
if (univ_hooks.FilterHook.SendHook.Registered)
{
ULONG dwFlags = 0;
if (!ctxtp->convoy_enabled)
dwFlags |= NLB_FILTER_HOOK_FLAGS_STOPPED;
else if (ctxtp->draining)
dwFlags |= NLB_FILTER_HOOK_FLAGS_DRAINING;
NdisInterlockedIncrement(&univ_hooks.FilterHook.Interface.References);
/* Release the spin lock protecting the hook. */
NdisReleaseSpinLock(&univ_hooks.FilterHook.Lock);
UNIV_ASSERT(univ_hooks.FilterHook.SendHook.Hook.SendHookFunction);
TRACE_FILTER("%!FUNC! Invoking the packet send filter hook");
/* Invoke the hook and save the response. */
filter = (*univ_hooks.FilterHook.SendHook.Hook.SendHookFunction)(
univ_adapters[ctxtp->adapter_id].device_name,
pPacket,
(PUCHAR)pPacketInfo->Ethernet.pHeader,
pPacketInfo->Ethernet.Length,
(PUCHAR)pPacketInfo->IP.pHeader,
pPacketInfo->IP.Length,
dwFlags);
/* Decrement the reference count on the hook now that we're done with it. */
NdisInterlockedDecrement(&univ_hooks.FilterHook.Interface.References);
}
else
{
/* Release the spin lock protecting the hook. */
NdisReleaseSpinLock(&univ_hooks.FilterHook.Lock);
}
}
return filter;
}
#endif
/*
* Function: Main_external_ioctl
* Description: This function performs the given IOCTL on the specified device.
* Parameters: DriverName - the unicode name of the device, e.g. \\Device\WLBS.
* Ioctl - the IOCTL code to invoke.
* pvInArg - a pointer to the input buffer.
* dwInSize - the size of the input buffer.
* pvOutArg - a pointer to the output buffer.
* dwOutSize - the size of the output buffer.
* Returns: NTSTATUS - the status of the operation.
* Author: shouse, 4.15.02
* Notes:
*/
NTSTATUS Main_external_ioctl (
IN PWCHAR DriverName,
IN ULONG Ioctl,
IN PVOID pvInArg,
IN ULONG dwInSize,
IN PVOID pvOutArg,
IN ULONG dwOutSize)
{
NTSTATUS Status;
UNICODE_STRING Driver;
OBJECT_ATTRIBUTES Attrib;
IO_STATUS_BLOCK IOStatusBlock;
HANDLE Handle;
/* Initialize the device driver device string. */
RtlInitUnicodeString(&Driver, DriverName);
InitializeObjectAttributes(&Attrib, &Driver, OBJ_CASE_INSENSITIVE, NULL, NULL);
/* Open a handle to the device. */
Status = ZwCreateFile(&Handle, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, &Attrib, &IOStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0);
if (!NT_SUCCESS(Status))
return STATUS_UNSUCCESSFUL;
/* Send an IOCTL to the driver. */
Status = ZwDeviceIoControlFile(Handle, NULL, NULL, NULL, &IOStatusBlock, Ioctl, pvInArg, dwInSize, pvOutArg, dwOutSize);
ZwClose(Handle);
return Status;
}
/*
* Function: Main_schedule_work_item
* Description: This function schedules the given procedure to be invoked as part of a
* deferred NDIS work item, which will be scheduled to run at PASSIVE_LEVEL.
* This function will refernce the adapter context to keep it from being
* destroyed before the work item executed. The given procedure is required
* to both free the memory in the work item pointer passed to it and to
* dereference the adapter context before returning.
* Parameters: ctxtp - the adapter context.
* funcp - the procedure to invoke when the work item fires.
* Returns: NTSTATUS - the status of the operation; STATUS_SUCCESS if successful.
* Author: shouse, 4.15.02
* Notes:
*/
NTSTATUS Main_schedule_work_item (PMAIN_CTXT ctxtp, NDIS_PROC funcp)
{
PNDIS_WORK_ITEM pWorkItem = NULL;
PMAIN_ADAPTER pAdapter;
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Extract the MAIN_ADAPTER structure given the MAIN_CTXT. */
pAdapter = &(univ_adapters[ctxtp->adapter_id]);
NdisAcquireSpinLock(&univ_bind_lock);
/* If the adapter is not initialized yet (or anymore), then we don't want to schedule a work item. */
if (pAdapter->inited)
{
/* Allocate a work item - this will be freed by the callback function. */
status = NdisAllocateMemoryWithTag(&pWorkItem, sizeof(NDIS_WORK_ITEM), UNIV_POOL_TAG);
/* If we can't allocate a work item, then just bail out - there's not much else we can do. */
if (status == NDIS_STATUS_SUCCESS)
{
/* This should definately be non-NULL if we get this far. */
ASSERT(pWorkItem);
/* Add a reference to the context to keep it from disappearing before this work item is serviced. */
Main_add_reference(ctxtp);
/* Set the callback function in the work item and set the context pointer as our callback context. */
NdisInitializeWorkItem(pWorkItem, funcp, ctxtp);
/* Add the work item to the queue. */
status = NdisScheduleWorkItem(pWorkItem);
/* If we fail to schedule the work item, we have to do the cleanup that the callback
function is supposed to do ourselves. */
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_schedule_work_item: Failed to schedule work item, status=0x%08x", status));
TRACE_CRIT("%!FUNC! Failed to schedule work item, status=0x%08x", status);
/* Release the reference on the adapter context. */
Main_release_reference(ctxtp);
/* Free the work item we allocated. */
NdisFreeMemory(pWorkItem, sizeof(NDIS_WORK_ITEM), 0);
}
}
}
NdisReleaseSpinLock(&univ_bind_lock);
return status;
}
#if defined (NLB_TCP_NOTIFICATION)
/*
* Function: Main_get_context
* Description: This function takes an IP interface index and translates it to an NLB
* adapter context pointer if a match is found; NULL otherwise.
* Parameters: if_index - the IP interface index.
* Returns: PMAIN_CTXT - a pointer to the corresponding NLB instance, if found; NULL otherwise.
* Author: shouse, 4.15.02
* Notes:
*/
PMAIN_CTXT Main_get_context (ULONG if_index)
{
ULONG i;
ULONG count = 0;
/* Loop through all of our adapters looking for a match to the given if_index and
bail out when we find one. Note that we assume that the same if_index cannot
exist for two adapters; if it does, we may mistakenly return the wrong adapter.
However, since the if_index is based on which adapter the primary cluster IP
address is configured and we don't allow multiple NLB instances with the same
primary cluster IP address, this shouldn't happen unless the administrator has
misconfigured TCP/IP. Note also that while this is a loop, in the most common
cases, we will find what we're looking for in the first one or two indexes. */
for (i = 0; i < CVY_MAX_ADAPTERS; i++)
{
if (univ_adapters[i].used && univ_adapters[i].bound && univ_adapters[i].inited)
{
if (univ_adapters[i].if_index == if_index)
{
/* Return the adapter context pointer. */
return univ_adapters[i].ctxtp;
}
/* Increment the number of NLB instances we've checked so far. */
count++;
/* If we've checked them all, we can leave now, rather than continuing to
loop through all CVY_MAX_ADAPTERS array entries. */
if (count >= univ_adapters_count) break;
}
}
/* Failed to find the corresponding NLB instance. */
return NULL;
}
/* Assert that our definitions of the interesting TCP connection states
matches those that TCP will be sending us in their notifications. */
C_ASSERT(NLB_TCP_CLOSED == TCP_CONN_CLOSED);
C_ASSERT(NLB_TCP_SYN_SENT == TCP_CONN_SYN_SENT);
C_ASSERT(NLB_TCP_SYN_RCVD == TCP_CONN_SYN_RCVD);
C_ASSERT(NLB_TCP_ESTAB == TCP_CONN_ESTAB);
/* Assert that both our address info buffer and TCP's address info buffer
are, at the very least, the same size. */
C_ASSERT(sizeof(NLBTCPAddressInfo) == sizeof(TCPAddrInfo));
/*
* Function: Main_tcp_callback_handle
* Description: This function is invoked by TCP/IP as the state of TCP connections change.
* We register for this callback when the first NLB instance goes up and de-
* register when the last instance of NLB goes away. When SYNs are received
* and TCP creates state, they use this callback to notify NLB so that it can
* create state to track the connection. Likewise, when the connection is
* closed, TCP notifies NLB so that it may destroy the associated state for
* that connection.
* Parameters: pAddressInfo - pointer to an NLBTCPAddressInfo block.
* IPInterface - the IP interface, if provided, that the connection is active on.
* PreviousState - the previous state for this connection.
* CurrentState - the new state that the connection has just transitioned to.
* Returns: Nothing.
* Author: shouse, 4.15.02
* Notes:
*/
VOID Main_tcp_callback_handle (NLBTCPAddressInfo * pAddressInfo, ULONG IPInterface, ULONG PreviousState, ULONG CurrentState)
{
UNIV_ASSERT(pAddressInfo != NULL);
/* Check the TCP address information buffer. */
if (!pAddressInfo) return;
switch (CurrentState)
{
case NLB_TCP_CLOSED:
{
TRACE_FILTER("%!FUNC! CLOSED notification received on interface %u", IPInterface);
UNIV_ASSERT(IPInterface == 0);
/* Notify the load module that this TCP connection has gone down. Send a RST to the load module instead of
a FIN so that it will immediately relinquish state for this connection - no need to timeout the state.
Note: perhaps it would be better to send a CVY_CONN_DOWN, but change the default TCP timeout in the
registry to zero. That way, it would be possible to timeout TCP if necessary, but not by default. */
(VOID)Main_conn_down(pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP,
CVY_CONN_RESET);
break;
}
case NLB_TCP_SYN_SENT:
{
TRACE_FILTER("%!FUNC! SYN_SENT notification received on interface %u", IPInterface);
UNIV_ASSERT(IPInterface == 0);
/* Notify the load module that an outgoing connection has been intiated. At this time, we do not know on
which interface the connection will be established, so the load module will create global state to track
the connection and when SYN+ACKs arrive, that state will be used to determine whether or not to accept
the packet. When the connection is finally established, we'll get an ESTAB notification, at which time
we can move the descriptor to the appropriate load module, or remove it if applicable. */
(VOID)Main_conn_pending(pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP);
break;
}
case NLB_TCP_SYN_RCVD:
{
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
#endif
PMAIN_CTXT ctxtp = NULL;
TRACE_FILTER("%!FUNC! SYN_RCVD notification received on interface %u", IPInterface);
UNIV_ASSERT(IPInterface != 0);
NdisAcquireSpinLock(&univ_bind_lock);
/* Translate the interface index to an adapter context. */
ctxtp = Main_get_context(IPInterface);
/* If we did not find an NLB instance corresponding to this interface index,
either it is a notification for a non-NLB adapter, or the cluster is mis-
configured such that NLB has not successfully associated an instance with
this particular interface index as yet. */
if (ctxtp == NULL)
{
TRACE_FILTER(" This notification is for a non-NLB adapter");
NdisReleaseSpinLock(&univ_bind_lock);
break;
}
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Packets directed to the dedicated IP address are always passed through, as are subnet broadcast
packets. Since these packets do not require tracking information, we can return without creating
any state in the load module. */
if ((pAddressInfo->LocalIPAddress == ctxtp->ded_ip_addr) ||
(pAddressInfo->LocalIPAddress == ctxtp->ded_bcast_addr) ||
(pAddressInfo->LocalIPAddress == ctxtp->cl_bcast_addr) ||
(ctxtp->cl_ip_addr == 0))
{
TRACE_FILTER("%!FUNC! Packet directed to the DIP or subnet broadcast");
NdisReleaseSpinLock(&univ_bind_lock);
break;
}
/* Reference the adapter context to keep it from disappearing while we're processing the callback. */
Main_add_reference(ctxtp);
NdisReleaseSpinLock(&univ_bind_lock);
#if defined (NLB_HOOK_ENABLE)
/* Invoke the packet query hook, if one has been registered. */
filter = Main_query_hook(ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* If the hook asked us to reject this packet, then break out and don't create state. */
TRACE_FILTER("%!FUNC! Packet receive filter hook: REJECT packet");
/* Release our reference on the adapter context. */
Main_release_reference(ctxtp);
break;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* If the hook asked us to accept this packet, then break out and don't create state. */
TRACE_FILTER("%!FUNC! Packet receive filter hook: ACCEPT packet");
/* Release our reference on the adapter context. */
Main_release_reference(ctxtp);
break;
}
/* Notify the load module that a new incoming connection has gone up on this NLB interface. */
(VOID)Main_conn_up(ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP,
filter);
#else
/* Notify the load module that a new incoming connection has gone up on this NLB interface. */
(VOID)Main_conn_up(ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP);
#endif
/* Release our reference on the adapter context. */
Main_release_reference(ctxtp);
break;
}
case NLB_TCP_ESTAB:
/* NLB currently only needs to process ESTAB notifications whose previous state was SYN_SENT.
We need to find out on what interface the connection ended up getting established and create
state to track it if necessary. If the previous state was SYN_RCVD, then we already know
what interface the connection was established on and we already have to state to track the
connection if necessary. In that case, bail out now. */
if (PreviousState == NLB_TCP_SYN_RCVD)
break;
{
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
#endif
PMAIN_CTXT ctxtp = NULL;
BOOLEAN bFlush = FALSE;
TRACE_FILTER("%!FUNC! ESTAB notification received on interface %u", IPInterface);
UNIV_ASSERT(IPInterface != 0);
NdisAcquireSpinLock(&univ_bind_lock);
/* Translate the interface index to an adapter context. */
ctxtp = Main_get_context(IPInterface);
/* If we did not find an NLB instance corresponding to this interface index,
either it is a notification for a non-NLB adapter, or the cluster is mis-
configured such that NLB has not successfully associated an instance with
this particular interface index as yet. However, if the context is NULL,
we may still have work to do, so we don't bail out just yet. If the context
is non-NULL, add a reference to it to keep it from being destroyed while
we're using it. */
if (ctxtp != NULL)
{
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Packets directed to the dedicated IP address are always passed through, as are subnet broadcast
packets. Since these packets do not require tracking information, we can return without creating
any state in the load module. */
if ((pAddressInfo->LocalIPAddress == ctxtp->ded_ip_addr) ||
(pAddressInfo->LocalIPAddress == ctxtp->ded_bcast_addr) ||
(pAddressInfo->LocalIPAddress == ctxtp->cl_bcast_addr) ||
(ctxtp->cl_ip_addr == 0))
{
TRACE_FILTER("%!FUNC! Packet directed to the DIP or subnet broadcast");
/* We still need to flush out any SYN_SENT information lurking in the load module. */
bFlush = TRUE;
}
/* Reference the adapter context to keep it from disappearing while we're processing the callback. */
Main_add_reference(ctxtp);
}
else
{
/* It ctxtp is NULL, that means that the connection was ultimately established
on a non-NLB NIC. However, we still need to flush out the temporary state
that was created when the SYN_SENT notification was processed. */
bFlush = TRUE;
}
NdisReleaseSpinLock(&univ_bind_lock);
#if defined (NLB_HOOK_ENABLE)
/* If the context is NULL, then we're not going to be establishing any connection
state for this notification. There is no need to query the hook for anything. */
if (ctxtp != NULL)
{
/* Invoke the packet query hook, if one has been registered. */
filter = Main_query_hook(ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* If the hook asked us to reject this packet, then we can do so here. */
TRACE_FILTER("%!FUNC! Packet receive filter hook: REJECT packet");
/* If the hook told us to unconditionally reject this notification, we still
need to flush out the temporary state that was created when the SYN_SENT
notification was processed. */
bFlush = TRUE;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* If the hook asked us to accept this packet, then break out and don't create state. */
TRACE_FILTER("%!FUNC! Packet receive filter hook: ACCEPT packet");
/* If the hook told us to unconditionally accept this notification, we still
need to flush out the temporary state that was created when the SYN_SENT
notification was processed. */
bFlush = TRUE;
}
}
/* Notify the load module that a new outgoing connection has been established. Note: ctxtp CAN BE NULL, which means
that the connection was established on a non-NLB interface or that the hook told us not to accept this connection;
in this case, we still need to call into the load module to allow it to cleanup the state it created when the
SYN_SENT notification was processed. */
(VOID)Main_conn_establish(bFlush ? NULL : ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP,
filter);
#else
/* Notify the load module that a new outgoing connection has been established. Note: ctxtp CAN BE NULL, which means
that the connection was established on a non-NLB interface; in this case, we still need to call into the load
module to allow it to cleanup the state it created when the SYN_SENT notification was processed. */
(VOID)Main_conn_establish(bFlush ? NULL : ctxtp,
pAddressInfo->LocalIPAddress,
NTOHS(pAddressInfo->LocalPort),
pAddressInfo->RemoteIPAddress,
NTOHS(pAddressInfo->RemotePort),
TCPIP_PROTOCOL_TCP);
#endif
if (ctxtp != NULL)
{
/* Release our reference on the adapter context. */
Main_release_reference(ctxtp);
}
break;
}
default:
TRACE_CRIT("%!FUNC! Unknown notification received on interface %u", IPInterface);
break;
}
}
/*
* Function: Main_tcp_callback
* Description: This function is invoked by TCP/IP as the state of TCP connections change.
* We register for this callback when the first NLB instance goes up and de-
* register when the last instance of NLB goes away. When SYNs are received
* and TCP creates state, they use this callback to notify NLB so that it can
* create state to track the connection. Likewise, when the connection is
* closed, TCP notifies NLB so that it may destroy the associated state for
* that connection.
* Parameters: Context - NULL, unused.
* Argument1 - pointer to a TCPCcbInfo structure (See net\published\inc\tcpinfo.w).
* Argument2 - NULL, unused.
* Returns: Nothing.
* Author: shouse, 4.15.02
* Notes:
*/
VOID Main_tcp_callback (PVOID Context, PVOID Argument1, PVOID Argument2)
{
TCPCcbInfo * pTCPConnInfo = (TCPCcbInfo *)Argument1;
/* If TCP notifications are not on, we should not be here - return. */
if (!NLB_TCP_NOTIFICATION_ON()) return;
UNIV_ASSERT(pTCPConnInfo);
/* Check the input buffer from TCP. */
if (!pTCPConnInfo) return;
/* Handle the TCP connection notification. Note that we are passing in an TCPAddrInfo
pointer, while this function expects an NLBTCPAddressInfo buffer. Therefore, it is
important to ensure that these two structures are in fact one in the same. */
Main_tcp_callback_handle((NLBTCPAddressInfo *)pTCPConnInfo->tci_connaddr,
pTCPConnInfo->tci_incomingif,
pTCPConnInfo->tci_prevstate,
pTCPConnInfo->tci_currstate);
}
/*
* Function: Main_alternate_callback
* Description: This function is invoked by external components as the state of connections
* change. We register for this callback when the first NLB instance goes up
* and de-register when the last instance of NLB goes away. When connections
* are created and a protocol creates state, they use this callback to notify
* NLB so that it can create state to track the connection. Likewise, when the
* connection is closed, the protocol notifies NLB so that it may destroy the
* associated state for that connection.
* Parameters: Context - NULL, unused.
* Argument1 - pointer to a NLBConnectionInfo structure (See net\published\inc\ntddnlb.w).
* Argument2 - NULL, unused.
* Returns: Nothing.
* Author: shouse, 8.1.02
* Notes:
*/
VOID Main_alternate_callback (PVOID Context, PVOID Argument1, PVOID Argument2)
{
NLBConnectionInfo * pConnInfo = (NLBConnectionInfo *)Argument1;
/* If NLB public notifications are not on, we should not be here - return. */
if (!NLB_ALTERNATE_NOTIFICATION_ON()) return;
UNIV_ASSERT(pConnInfo);
/* Check the input buffer. */
if (!pConnInfo) return;
/* Only TCP notifications are currently supported. */
UNIV_ASSERT(pConnInfo->Protocol == NLB_TCPIP_PROTOCOL_TCP);
switch (pConnInfo->Protocol)
{
case NLB_TCPIP_PROTOCOL_TCP:
UNIV_ASSERT(pConnInfo->pTCPInfo);
/* Check the TCP connection input buffer. */
if (!pConnInfo->pTCPInfo) return;
/* Handle the TCP connection notification. */
Main_tcp_callback_handle(&pConnInfo->pTCPInfo->Address,
pConnInfo->pTCPInfo->IPInterface,
pConnInfo->pTCPInfo->PreviousState,
pConnInfo->pTCPInfo->CurrentState);
break;
default:
/* Notifications for this protocol are not supported. */
break;
}
}
/*
* Function: Main_set_interface_index
* Description: This function is called as a result of either the IP address table being
* modified (triggers a OID_GEN_NETWORK_LAYER_ADDRESSES NDIS request), or
* when the NLB instance is reloaded (IOCTL_CVY_RELOAD). This function
* retrieves the IP address table from IP and searches for its primary
* cluster IP address in the table. If it finds it, it notes the IP interface
* index on which the primary cluster IP address is configured; this infor-
* mation is required in order to process TCP connection notifcation callbacks.
* If NLB cannot find its primary cluster IP address in the IP table, or
* if the cluster is misconfigured (primary cluster IP address configured on
* the wrong NIC, perhaps), NLB will be unable to properly handle notifications.
* Because this function performs IOCTLs on other drivers, it MUST run at
* PASSIVE_LEVEL, in which case NDIS work items might be required to invoke it.
* Parameters: pWorkItem - the work item pointer, which must be freed if non-NULL.
* nlbctxt - the adapter context.
* Returns: Nothing.
* Author: shouse, 4.15.02
* Notes: Note that the code that sets up this work item MUST increment the reference
* count on the adapter context BEFORE adding the work item to the queue. This
* ensures that when this callback is executed, the context will stiil be valid,
* even if an unbind operation is pending. This function must free the work
* item memory and decrement the reference count - both, whether this function
* can successfully complete its task OR NOT.
*/
VOID Main_set_interface_index (PNDIS_WORK_ITEM pWorkItem, PVOID nlbctxt) {
TCP_REQUEST_QUERY_INFORMATION_EX TrqiInBuf;
IPSNMPInfo IPSnmpInfo;
PUCHAR Context;
IPAddrEntry * pIpAddrTbl;
TDIObjectID * ID;
ULONG IpAddrCount;
ULONG Status;
ULONG dwInBufLen;
ULONG dwOutBufLen;
KIRQL irql;
ULONG k;
PMAIN_ADAPTER pAdapter;
PMAIN_CTXT ctxtp = (PMAIN_CTXT)nlbctxt;
/* Do some sanity checking on the context - make sure that the MAIN_CTXT code
is correct and that its properly attached to an adapter, etc. */
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* From the context, find the adapter structure, which is where we store
the IP interface index. */
pAdapter = &(univ_adapters[ctxtp->adapter_id]);
UNIV_ASSERT(pAdapter->code == MAIN_ADAPTER_CODE);
UNIV_ASSERT(pAdapter->ctxtp == ctxtp);
/* Might as well free the work item now - we don't need it. */
if (pWorkItem)
NdisFreeMemory(pWorkItem, sizeof(NDIS_WORK_ITEM), 0);
/* Grab the bind lock. */
NdisAcquireSpinLock(&univ_bind_lock);
/* Make sure that another if_index update is not in progress. */
while (pAdapter->if_index_operation != IF_INDEX_OPERATION_NONE) {
/* Release the bind lock. */
NdisReleaseSpinLock(&univ_bind_lock);
/* Sleep while some other operation is in progress. */
Nic_sleep(10);
/* Grab the bind lock. */
NdisAcquireSpinLock(&univ_bind_lock);
}
/* Update the operation flag to reflect the update in progress. */
pAdapter->if_index_operation = IF_INDEX_OPERATION_UPDATE;
/* Release the bind lock. */
NdisReleaseSpinLock(&univ_bind_lock);
/* This shouldn't happen, but protect against it anyway - we cannot manipulate
the registry if we are at an IRQL > PASSIVE_LEVEL, so bail out. */
if ((irql = KeGetCurrentIrql()) > PASSIVE_LEVEL) {
UNIV_PRINT_CRIT(("Main_set_interface_index: IRQL(%u) > PASSIVE_LEVEL(%u), exiting...", irql, PASSIVE_LEVEL));
TRACE_CRIT("%!FUNC! IRQL(%u) > PASSIVE_LEVEL(%u), exiting...", irql, PASSIVE_LEVEL);
goto failure;
}
/* If the cluster IP address has not been set, don't even bother to look for
it in the IP address tables; we'd just be wasting cycles. */
if (ctxtp->cl_ip_addr == 0) {
UNIV_PRINT_CRIT(("Main_set_interface_index: Primary cluster IP address is not set"));
TRACE_CRIT("%!FUNC! Primary cluster IP address is not set");
goto failure;
}
/* Grab a few pointers to pieces of the request. */
ID = &(TrqiInBuf.ID);
Context = (PUCHAR)&(TrqiInBuf.Context[0]);
/* Calculate the input and output buffer lengths for the IOCTL. */
dwInBufLen = sizeof(TCP_REQUEST_QUERY_INFORMATION_EX);
dwOutBufLen = sizeof(IPSNMPInfo);
/* Fill in the IP stats request. This is used only to get the
size of the IP address table from TCP/IP. */
ID->toi_entity.tei_entity = CL_NL_ENTITY;
ID->toi_entity.tei_instance = 0;
ID->toi_class = INFO_CLASS_PROTOCOL;
ID->toi_type = INFO_TYPE_PROVIDER;
ID->toi_id = IP_MIB_STATS_ID;
NdisZeroMemory(Context, CONTEXT_SIZE);
/* Send the request to TCP/IP via an IOCTL. */
Status = Main_external_ioctl(DD_TCP_DEVICE_NAME, IOCTL_TCP_QUERY_INFORMATION_EX, (PVOID)&TrqiInBuf, sizeof(TCP_REQUEST_QUERY_INFORMATION_EX), (PVOID)&IPSnmpInfo, dwOutBufLen);
if(NT_SUCCESS(Status))
{
/* Calculate the size of the buffer necessary to hold the entire IP address table. */
IpAddrCount = IPSnmpInfo.ipsi_numaddr + 10;
dwOutBufLen = IpAddrCount * sizeof(IPAddrEntry);
/* Allocate a buffer for the IP address table. */
Status = NdisAllocateMemoryWithTag(&pIpAddrTbl, dwOutBufLen, UNIV_POOL_TAG);
if(!pIpAddrTbl) {
UNIV_PRINT_CRIT(("Main_set_interface_index: Could not allocate memory for %d IP addresses", IPSnmpInfo.ipsi_numaddr + 10));
TRACE_CRIT("%!FUNC! Could not allocate memory for %d IP addresses", IPSnmpInfo.ipsi_numaddr + 10);
goto failure;
}
NdisZeroMemory(pIpAddrTbl, dwOutBufLen);
/* Fill in the IP address table request. This is used to retrieve
the entire IP address table from TCP/IP. */
ID->toi_type = INFO_TYPE_PROVIDER;
ID->toi_id = IP_MIB_ADDRTABLE_ENTRY_ID;
NdisZeroMemory(Context, CONTEXT_SIZE);
/* Send the request to TCP/IP via an IOCTL. */
Status = Main_external_ioctl(DD_TCP_DEVICE_NAME, IOCTL_TCP_QUERY_INFORMATION_EX, (PVOID)&TrqiInBuf, sizeof(TCP_REQUEST_QUERY_INFORMATION_EX), (PVOID)pIpAddrTbl, dwOutBufLen);
if(!NT_SUCCESS(Status))
{
/* Free the IP address table buffer. */
NdisFreeMemory(pIpAddrTbl, dwOutBufLen, 0);
UNIV_PRINT_CRIT(("Main_set_interface_index: IP_MIB_ADDRTABLE_ENTRY_ID failed with status=0x%08x", Status));
TRACE_CRIT("%!FUNC! IP_MIB_ADDRTABLE_ENTRY_ID failed with status=0x%08x", Status);
goto failure;
}
}
else
{
UNIV_PRINT_CRIT(("Main_set_interface_index: IP_MIB_STATS_ID failed with status=0x%08x", Status));
TRACE_CRIT("%!FUNC! IP_MIB_STATS_ID failed with status=0x%08x", Status);
goto failure;
}
/* Loop through the IP address table looking for our primary cluster IP address.
If we don't find it, this is a misconfigure, as the user has not yet added the
cluster IP address to TCP/IP. No matter; when they do, we should trap the
NDIS request to set the NETwORK_ADDRESS table and we'll fire off another
attempt at this. */
for (k = 0; k < IpAddrCount; k++)
{
if (pIpAddrTbl[k].iae_addr == ctxtp->cl_ip_addr)
break;
}
/* If we couldn't find our cluster IP address in the list, set the interface
index to an invalid value. */
if (k >= IpAddrCount)
{
/* Free the IP address table buffer. */
NdisFreeMemory(pIpAddrTbl, dwOutBufLen, 0);
UNIV_PRINT_CRIT(("Main_set_interface_index: Did not find the primary cluster IP address (%u.%u.%u.%u) in the IP address table",
IP_GET_OCTET(ctxtp->cl_ip_addr, 0), IP_GET_OCTET(ctxtp->cl_ip_addr, 1), IP_GET_OCTET(ctxtp->cl_ip_addr, 2), IP_GET_OCTET(ctxtp->cl_ip_addr, 3)));
TRACE_CRIT("%!FUNC! Did not find the primary cluster IP address (%u.%u.%u.%u) in the IP address table",
IP_GET_OCTET(ctxtp->cl_ip_addr, 0), IP_GET_OCTET(ctxtp->cl_ip_addr, 1), IP_GET_OCTET(ctxtp->cl_ip_addr, 2), IP_GET_OCTET(ctxtp->cl_ip_addr, 3));
goto failure;
}
/* If we found our cluster IP address in the list, note the interface index. */
NdisAcquireSpinLock(&univ_bind_lock);
pAdapter->if_index = pIpAddrTbl[k].iae_index;
NdisReleaseSpinLock(&univ_bind_lock);
/* Free the IP address table buffer. */
NdisFreeMemory(pIpAddrTbl, dwOutBufLen, 0);
UNIV_PRINT_INFO(("Main_set_interface_index: NLB cluster %u.%u.%u.%u maps to IP interface %u",
IP_GET_OCTET(ctxtp->cl_ip_addr, 0), IP_GET_OCTET(ctxtp->cl_ip_addr, 1), IP_GET_OCTET(ctxtp->cl_ip_addr, 2), IP_GET_OCTET(ctxtp->cl_ip_addr, 3), pAdapter->if_index));
TRACE_INFO("%!FUNC! NLB cluster %u.%u.%u.%u maps to IP interface %u",
IP_GET_OCTET(ctxtp->cl_ip_addr, 0), IP_GET_OCTET(ctxtp->cl_ip_addr, 1), IP_GET_OCTET(ctxtp->cl_ip_addr, 2), IP_GET_OCTET(ctxtp->cl_ip_addr, 3), pAdapter->if_index);
goto exit;
failure:
NdisAcquireSpinLock(&univ_bind_lock);
pAdapter->if_index = 0;
NdisReleaseSpinLock(&univ_bind_lock);
exit:
/* If the work item pointer is non-NULL, then we were called as the result of
scheduling an NDIS work item. In that case, the reference count on the
context was incremented to ensure that the context did not disappear before
this work item was completed; therefore, we need to decrement the reference
count here. If the work item pointer is NULL, then this function was called
internally directly. In that case, the reference count was not incremented
and therefore there is no need to decrement it here.
Note that if the function is called internally, but without setting the work
item parameter to NULL, the reference count will be skewed and may cause
either invalid memory references later or block unbinds from completing. */
if (pWorkItem)
Main_release_reference(ctxtp);
/* Update the operation flag to reflect the completion of the update. */
pAdapter->if_index_operation = IF_INDEX_OPERATION_NONE;
return;
}
#endif
/*
* Function: Main_add_reference
* Description: This function adds a reference to the context of a given adapter.
* Parameters: ctxtp - a pointer to the context to reference.
* Returns: ULONG - The incremented value.
* Author: shouse, 3.29.01
* Notes:
*/
ULONG Main_add_reference (IN PMAIN_CTXT ctxtp) {
/* Assert that the context pointer actually points to something. */
UNIV_ASSERT(ctxtp);
/* Increment the reference count. */
return NdisInterlockedIncrement(&ctxtp->ref_count);
}
/*
* Function: Main_release_reference
* Description: This function releases a reference on the context of a given adapter.
* Parameters: ctxtp - a pointer to the context to dereference.
* Returns: ULONG - The decremented value.
* Author: shouse, 3.29.01
* Notes:
*/
ULONG Main_release_reference (IN PMAIN_CTXT ctxtp) {
/* Assert that the context pointer actually points to something. */
UNIV_ASSERT(ctxtp);
/* Decrement the reference count. */
return NdisInterlockedDecrement(&ctxtp->ref_count);
}
/*
* Function: Main_get_reference_count
* Description: This function returns the current context reference count on a given adapter.
* Parameters: ctxtp - a pointer to the context to check.
* Returns: ULONG - The current reference count.
* Author: shouse, 3.29.01
* Notes:
*/
ULONG Main_get_reference_count (IN PMAIN_CTXT ctxtp) {
/* Assert that the context pointer actually points to something. */
UNIV_ASSERT(ctxtp);
/* Return the reference count. */
return ctxtp->ref_count;
}
/*
* Function: Main_apply_without_restart
* Description: This function checks the new set of NLB parameters against the old
* set to determine whether or not the changes can be made without
* stopping and starting the load module, which destroys all connection
* state on the adapter (this is bad). If the only changes were to
* parameters that can be changed without resetting the load module,
* such as port rule load weights, then the changes will be made here
* and the load module will not be stopped and re-started.
* Parameters: ctxtp - a pointer to the context structure for the adapter being reloaded.
* pOldParams - a pointer to the "old" NLB parameters.
* pCurParams - a pointer to the "new" NLB parameters.
* Returns: BOOLEAN - if TRUE, the changes were made here and there is no need to reset
* the load module.
* Author: fengsun, 9.15.00
* Notes:
*/
BOOLEAN Main_apply_without_restart (PMAIN_CTXT ctxtp, PCVY_PARAMS pOldParams, PCVY_PARAMS pCurParams)
{
CVY_RULE OldRules[CVY_MAX_RULES - 1];
ULONG NewWeight;
ULONG i;
BOOLEAN bAttemptToFireReloadEvent;
if (pOldParams->num_rules != pCurParams->num_rules)
{
return FALSE;
}
UNIV_ASSERT(sizeof(OldRules) == sizeof(pOldParams->port_rules));
/* Copy the Netmon heartbeat message flag from the new params to the old params. */
pOldParams->netmon_alive = pCurParams->netmon_alive;
/* Save the old port rules for later comparison. */
RtlCopyMemory(OldRules, pOldParams->port_rules, sizeof(OldRules));
/* Copy the new rule weight over the old settings. */
for (i = 0; i < pCurParams->num_rules; i++) {
if (pCurParams->port_rules[i].mode == CVY_MULTI) {
pOldParams->port_rules[i].mode_data.multi.equal_load = pCurParams->port_rules[i].mode_data.multi.equal_load;
pOldParams->port_rules[i].mode_data.multi.load = pCurParams->port_rules[i].mode_data.multi.load;
}
}
if (RtlCompareMemory(pOldParams, pCurParams, sizeof(CVY_PARAMS)) != sizeof(CVY_PARAMS))
{
return FALSE;
}
bAttemptToFireReloadEvent = FALSE;
for (i = 0; i < pCurParams->num_rules; i++) {
switch (OldRules[i].mode) {
case CVY_MULTI:
if (OldRules[i].mode_data.multi.equal_load && pCurParams->port_rules[i].mode_data.multi.equal_load)
continue;
if (!OldRules[i].mode_data.multi.equal_load && !pCurParams->port_rules[i].mode_data.multi.equal_load &&
(OldRules[i].mode_data.multi.load == pCurParams->port_rules[i].mode_data.multi.load))
continue;
if (pCurParams->port_rules[i].mode_data.multi.equal_load)
NewWeight = CVY_EQUAL_LOAD;
else
NewWeight = pCurParams->port_rules[i].mode_data.multi.load;
UNIV_PRINT_VERB(("Main_apply_without_restart: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", OldRules[i].virtual_ip_addr, OldRules[i].start_port, NewWeight));
TRACE_VERB("%!FUNC! Main_apply_without_restart: Calling Load_port_change -> VIP=0x%08x, port=%d, load=%d", OldRules[i].virtual_ip_addr, OldRules[i].start_port, NewWeight);
// If enabled, fire wmi event indicating reloading of nlb on this node
// This is to be done only once, hence the bAttemptToFireReloadEvent flag.
// This is the only place in the code where the event is fired BEFORE the action
// takes place. We could not fire this event after the action took place, i.e.
// from within Load_port_change 'cos from within Load_port_change, we could not
// distinguish between the first & the subsequent calls (for each port rule) to it.
// We need to distinguish 'cos this event is to be fired only for the first call
if (bAttemptToFireReloadEvent == FALSE)
{
bAttemptToFireReloadEvent = TRUE;
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_RELOADED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_RELOADED 'cos NodeControlEvent generation disabled");
}
}
Load_port_change(&ctxtp->load, OldRules[i].virtual_ip_addr, OldRules[i].start_port, IOCTL_CVY_PORT_SET, NewWeight);
break;
case CVY_SINGLE:
case CVY_NEVER:
default:
break;
}
}
LOG_MSG(MSG_INFO_CONFIGURE_CHANGE_CONVERGING, MSG_NONE);
return TRUE;
}
/*
* Function: Main_alloc_team
* Description: This function allocates and initializes a BDA_TEAM structure.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter
* Returns: PBDA_TEAM - a pointer to a new BDA_TEAM structure if successful, NULL if not.
* Author: shouse, 3.29.01
* Notes:
*/
PBDA_TEAM Main_alloc_team (IN PMAIN_CTXT ctxtp, IN PWSTR team_id) {
PUCHAR ptr;
PBDA_TEAM team;
NDIS_STATUS status;
/* Allocate a BDA_TEAM structure. */
status = NdisAllocateMemoryWithTag(&ptr, sizeof(BDA_TEAM), UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_alloc_team: Unable to allocate a team. Exiting..."));
TRACE_CRIT("%!FUNC! Unable to allocate a team");
LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, sizeof(BDA_TEAM), status);
return NULL;
}
/* Make sure that ptr actually points to something. */
UNIV_ASSERT(ptr);
/* Zero the memory out. */
NdisZeroMemory(ptr, sizeof(BDA_TEAM));
/* Cast the new memory to a team pointer. */
team = (PBDA_TEAM)ptr;
/* Set the default field values. This is redundant (since
we just called NdisZeroMemory), but whatever. */
team->prev = NULL;
team->next = NULL;
team->load = NULL;
team->load_lock = NULL;
team->active = FALSE;
team->membership_count = 0;
team->membership_fingerprint = 0;
team->membership_map = 0;
team->consistency_map = 0;
/* Copy the team ID into the team structure. Note that the Team ID will be
NULL-terminated implicitly by the NdisZeroMemory call above. */
NdisMoveMemory(team->team_id, team_id, CVY_MAX_BDA_TEAM_ID * sizeof(WCHAR));
return team;
}
/*
* Function: Main_free_team
* Description: This function frees the memory used by a BDA_TEAM.
* Parameters: team - a pointer to the team to be freed.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes:
*/
VOID Main_free_team (IN PBDA_TEAM team) {
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Free the memory that the team structure is using. */
NdisFreeMemory((PUCHAR)team, sizeof(BDA_TEAM), 0);
}
/*
* Function: Main_find_team
* Description: This function searches the linked list of teams looking for
* a given team ID. If team with the same ID is found, a pointer
* to that team is returned, otherwise NULL is returned to indicate
* that no such team exists.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this team.
* team_id - a unicode string containing the team ID, which must be a GUID.
* Returns: PBDA_TEAM - a pointer to the team if found, NULL otherwise.
* Author: shouse, 3.29.01
* Notes: This function should be called with the global teaming lock already acquired!!!
*/
PBDA_TEAM Main_find_team (IN PMAIN_CTXT ctxtp, IN PWSTR team_id) {
PBDA_TEAM team;
/* We should be at PASSIVE_LEVEL - %ls is OK. However, be extra paranoid, just in case. If
we're at DISPATCH_LEVEL, then just log an unknown adapter print - don't specify the GUID. */
if (KeGetCurrentIrql() <= PASSIVE_LEVEL) {
UNIV_PRINT_VERB(("Main_find_team: Looking for %ls. Entering...", team_id));
} else {
UNIV_PRINT_VERB(("Main_find_team: Looking for (IRQ level too high to print hook GUID). Entering...", team_id));
}
/* Loop through all teams in the linked list. If we find a matching
team ID, return a pointer to the team; otherwise, return NULL. */
for (team = univ_bda_teaming_list; team; team = team->next) {
/* If we have a match, return a pointer to the team. */
if (Univ_equal_unicode_string(team->team_id, team_id, wcslen(team->team_id))) {
UNIV_PRINT_VERB(("Main_find_team: Team found. Exiting..."));
return team;
}
}
UNIV_PRINT_VERB(("Main_find_team: Team not found. Exiting..."));
return NULL;
}
/*
* Function: Main_teaming_get_member_id
* Description: This function assigns a team member a unique, zero-indexed integer ID,
* which is used by the member as in index in a consistency bit map. Each
* member sets their bit in the consistecy bit map, based on heartbeat
* observations, that is used by the master of the team to determine whether
* or not the team should be in an active state.
* Parameters: team - a pointer to the team which the adapter is joining.
* id - out parameter to hold the new ID.
* Returns: BOOLEAN - always TRUE now, but there may be a need to return failure in the future.
* Author: shouse, 3.29.01
* Notes: This function should be called with the team lock already acquired!!!
*/
BOOLEAN Main_teaming_get_member_id (IN PBDA_TEAM team, OUT PULONG id) {
ULONG index;
ULONG map;
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Make sure that ID actually points to something. */
UNIV_ASSERT(id);
/* Loop through the membership map looking for the first reset bit. Because members
can come and go, this bit will not always be in the (num_membes)th position. For
example, it is perfectly plausible for the membership_map to look like (binary)
0000 0000 0000 0000 0000 0100 1110 0111, in which case the ID returned by this
function would be three. */
for (index = 0, map = team->membership_map;
index <= CVY_BDA_MAXIMUM_MEMBER_ID, map;
index++, map >>= 1)
if (!(map & 0x00000001)) break;
/* We assert that the index must be less than the maximum number of adapters
(CVY_BDA_MAXIMUM_MEMBER_ID = CVY_MAX_ADAPTERS - 1). */
UNIV_ASSERT(index <= CVY_BDA_MAXIMUM_MEMBER_ID);
/* Set the member ID. */
*id = index;
/* Set our bit in the membership map. */
team->membership_map |= (1 << *id);
/* Set our bit in the consistency map. By default, we assume that this member
is consistent and heartbeats on this adapter can deternmine otherwise. */
team->consistency_map |= (1 << *id);
/* We may have reason to fail this call in the future, but for now, we always succeed. */
return TRUE;
}
/*
* Function: Main_teaming_put_member_id
* Description: This function is called when a member leaves its team, at which
* time its ID is put back into the ID pool.
* Parameters: team - a pointer to the team which this adapter is leaving.
* id - the ID, which this function will reset before returning.
* Returns: BOOLEAN - always TRUE now, but there may be a need to return failure in the future.
* Author: shouse, 3.29.01
* Notes: This function should be called with the team lock already acquired!!!
*/
BOOLEAN Main_teaming_put_member_id (IN PBDA_TEAM team, IN OUT PULONG id) {
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Make sure that ID actually points to something. */
UNIV_ASSERT(id);
/* Reet our bit in the membership map. This effectively prevents
us from influencing the active state of the team. */
team->membership_map &= ~(1 << *id);
/* Reet our bit in the consistency map. */
team->consistency_map &= ~(1 << *id);
/* Set the member ID back to an invalid value. */
*id = CVY_BDA_INVALID_MEMBER_ID;
/* We may have reason to fail this call in the future, but for now, we always succeed. */
return TRUE;
}
/*
* Function: Main_queue_team
* Description: This fuction queues a team onto the global doubly-linked list of BDA teams
* (univ_bda_teaming_list). Insertions always occur at the front of the list.
* Parameters: team - a pointer to the team to queue onto the list.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function should be called with the global teaming lock already acquired!!!
*/
VOID Main_queue_team (IN PBDA_TEAM team) {
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Insert at the head of the list by setting next to the current
head and pointing the global head pointer to the new team. */
team->prev = NULL;
team->next = univ_bda_teaming_list;
univ_bda_teaming_list = team;
/* If we are not the only team in the list, then we have to
set my successor's previous pointer to point to me. */
if (team->next) team->next->prev = team;
}
/*
* Function: Main_dequeue_team
* Description: This function removes a given team from the global doubly-linked
* list of teams (univ_bda_teaming_list).
* Parameters: team - a pointer to the team to remove from the list.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function should be called with the global teaming lock already acquired!!!
*/
VOID Main_dequeue_team (IN PBDA_TEAM team) {
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Special case when we are the first team in the list, in which case, we
have no previous pointer and the head of the list needs to be reset. */
if (!team->prev) {
/* Point the global head of the list to the next team in the list,
which CAN be NULL, meaning that the list is now empty. */
univ_bda_teaming_list = team->next;
/* If there was a team after me in the list, who is now the new
head of the list, set its previous pointer to NULL. */
if (team->next) team->next->prev = NULL;
} else {
/* Point the previous node's next pointer to my successor in the
list, which CAN be NULL if I was the last team in the list. */
team->prev->next = team->next;
/* If there is a team after me in the list, point its previous
pointer to my predecessor. */
if (team->next) team->next->prev = team->prev;
}
}
/*
* Function: Main_get_team
* Description: This function returns a team for the given team ID. If the team already
* exists in the global teaming list, it is returned. Otherwise, a new team
* is allocated, initialized and returned. Before a team is returned, however,
* it is properly referenced by incrementing the reference count (membership_count),
* assigning a team member ID to the requestor and fingerprinting the team with
* the member's primary cluster IP address.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* team_id - a unicode string (GUID) uniquely identifying the team to retrieve.
* Returns: PBDA_TEAM - a pointer to the requested team. NULL if it does not exists and
* a new team cannot be allocated, or if the team ID is invalid (empty).
* Author: shouse, 3.29.01
* Notes: This function should be called with the global teaming lock already acquired!!!
*/
PBDA_TEAM Main_get_team (IN PMAIN_CTXT ctxtp, IN PWSTR team_id) {
PBDA_TEAM team;
/* Make sure that team_id actually points to something. */
if (!team_id || team_id[0] == L'\0') {
UNIV_PRINT_CRIT(("Main_get_team: Invalid parameter. Exiting..."));
TRACE_CRIT("%!FUNC! Invalid parameter. Exiting...");
return NULL;
}
/* Try to find a previous instance of this team in the global list.
If we can't find it in the list, then allocate a new team. */
if (!(team = Main_find_team(ctxtp, ctxtp->params.bda_teaming.team_id))) {
/* Allocate and initialize a new team. */
if (!(team = Main_alloc_team(ctxtp, ctxtp->params.bda_teaming.team_id))) {
UNIV_PRINT_CRIT(("Main_get_team: Error attempting to allocate memory for a team. Exiting..."));
TRACE_CRIT("%!FUNC! Error attempting to allocate memory for a team. Exiting...");
return NULL;
}
/* If a new team was allocated, insert it into the list. */
Main_queue_team(team);
}
/* Increment the reference count on this team. This reference count prevents
a team from being destroyed while somebody is still using it. */
team->membership_count++;
/* Get a team member ID, which is my index into the consistency bit map. */
Main_teaming_get_member_id(team, &ctxtp->bda_teaming.member_id);
/* The fingerprint field is a cumulative XOR of all primary cluster IPs in the team. We
only use the two least significant bytes of the cluster IP, which, because the
cluster IP address is stored in host order, are the two most significant bytes. */
team->membership_fingerprint ^= ((ctxtp->cl_ip_addr >> 16) & 0x0000ffff);
return team;
}
/*
* Function: Main_put_team
* Description: This function releases a reference on a team and frees the team if
* no references remain. Dereferencing includes decrementing the
* membership_count, releasing this member's ID and removing our
* fingerprint from the team.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* team - a pointer to the team to dereference.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function should be called with the global teaming lock already acquired!!!
*/
VOID Main_put_team (IN PMAIN_CTXT ctxtp, IN PBDA_TEAM team) {
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* The fingerprint field is a cumulative XOR of all primary cluster IPs in the team.
We only use the two least significant bytes of the cluster IP, which, because the
cluster IP address is stored in host order, are the two most significant bytes.
Because a fingerprint is an XOR, the act of removing our fingerprint is the same
as it was to add it - we simply XOR our primary cluster IP address to remove it.
((NUM1 ^ NUM2) ^ NUM2) equals NUM1. */
team->membership_fingerprint ^= ((ctxtp->cl_ip_addr >> 16) & 0x0000ffff);
/* Release our team member ID back into the free pool. */
Main_teaming_put_member_id(team, &ctxtp->bda_teaming.member_id);
/* Decrement the number of adapters in this team and remove and free the team
if this is the last reference on the team. */
if (!--team->membership_count) {
/* Remove the team from the list. */
Main_dequeue_team(team);
/* Free the memory used by the team. */
Main_free_team(team);
}
}
/*
* Function: Main_teaming_check_consistency
* Description: This function is called by all adapters during Main_ping, wherein
* the master of every team should check its team for consitency and
* mark the team active if it is consistent. Teams are marked incon-
* sistent and inactive by the load module or when the master of an
* existing team is removed. Because on the master performs the con-
* sistency check in this function, a team without a master can NEVER
* be marked active.
* Parameters: ctxtp - a pointer to the adapter's MAIN_CTXT structure.
* Returns: Nothing
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
VOID Main_teaming_check_consistency (IN PMAIN_CTXT ctxtp) {
PBDA_TEAM team;
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we aren't teaming, just bail out here; if we really aren't teaming,
or are in the process of leaving a team, then no worries; if however, we were
teaming or in the process of joining a team, then we'll just catch this
the next time through. If we do think we're teaming, then we'll go ahead
and grab the global lock to make sure. */
if (!ctxtp->bda_teaming.active)
{
return;
}
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* If we actually aren't part of a team, bail out - nothing to do. */
if (!ctxtp->bda_teaming.active) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/* If we aren't the master of our team, bail out - nothing to do.
Only the master can change the state of a team to active. */
if (!ctxtp->bda_teaming.master) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/* Extract a pointer to my team. */
team = ctxtp->bda_teaming.bda_team;
/* Make sure that the team exists. */
UNIV_ASSERT(team);
/* If all members of my team are consistent, then activate the team. */
if (team->membership_map == team->consistency_map) {
if (!team->active) {
LOG_MSG(MSG_INFO_BDA_TEAM_REACTIVATED, MSG_NONE);
team->active = TRUE;
}
}
NdisReleaseSpinLock(&univ_bda_teaming_lock);
}
/*
* Function: Main_teaming_ip_addr_change
* Description: This function is called from Main_ip_addr_init when the primary
* cluster IP address of an adapter changes (potentially). We need
* to recognize this to properly re-fingerprint the team.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* old_ip - the old cluster IP addres (as a DWORD).
* new_ip - the new cluster IP address (as a DWORD).
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
VOID Main_teaming_ip_addr_change (IN PMAIN_CTXT ctxtp, IN ULONG old_ip, IN ULONG new_ip) {
PBDA_TEAM team;
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* If we aren't part of a team, bail out - nothing to do. Because this function is only
called during a re-configuration, we won't worry about optimizing and not grabbing the
lock as is done in some of the hot paths. */
if (!ctxtp->bda_teaming.active) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/* Grab a pointer to the team. */
team = ctxtp->bda_teaming.bda_team;
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* Remove the old cluster IP address by undoing the XOR. We only use the two
least significant bytes of the cluster IP, which, because the cluster IP
address is stored in host order, are the two most significant bytes. */
team->membership_fingerprint ^= ((old_ip >> 16) & 0x0000ffff);
/* XOR with the new cluster IP address. We only use the two least
significant bytes of the cluster IP, which, because the cluster IP
address is stored in host order, are the two most significant bytes. */
team->membership_fingerprint ^= ((new_ip >> 16) & 0x0000ffff);
NdisReleaseSpinLock(&univ_bda_teaming_lock);
}
/*
* Function: Main_teaming_reset
* Description: This function is called from Main_teaming_cleanup (or Main_teaming_init)
* tp cleanup any teaming configuration that may exist on an adapter. To
* do so, we cleanup our membership state and dereference the team. If
* we are the master for the team, however, we have to wait until there
* are no more references on our load module before allowing the operation
* to complete, because this might be called in the unbind path, in
* which case, our load module would be going away.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
VOID Main_teaming_reset (IN PMAIN_CTXT ctxtp) {
PBDA_TEAM team;
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* If we aren't part of a team, bail out - nothing to do. */
if (!ctxtp->bda_teaming.active) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/* Inactivate teaming on this adapter. This will cause other entities like the
load module and send/receive paths to stop thinking in teaming mode. */
ctxtp->bda_teaming.active = FALSE;
/* Grab a pointer to the team. */
team = ctxtp->bda_teaming.bda_team;
/* Make sure that team actually points to something. */
UNIV_ASSERT(team);
/* If we are the master for this team, make sure that all references to our load
module have been released and then remove our load information from the team. */
if (ctxtp->bda_teaming.master) {
/* Mark the team as inactive - a team cannot function without a master. Members
of an inactive team will NOT accept packets and therefore will not reference
our load module further while we wait for the reference count to go to zero. */
team->active = FALSE;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* No need to worry - the team pointer cannot go away even though we don't have
the lock acquired; we have a reference on the team until we call Main_put_team. */
while (Load_get_reference_count(team->load)) {
/* Sleep while there are references on our load module. */
Nic_sleep(10);
}
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Remove the pointer to my load module. We wait until now to prevent another
adapter from joining the team claiming to be the master until we are done
waiting for references on our load module to go away. */
team->load = NULL;
team->load_lock = NULL;
/* If we have just left a team without a master, log an event to notify
the user that a team cannot function without a designated master. */
LOG_MSG(MSG_INFO_BDA_MASTER_LEAVE, MSG_NONE);
}
/* Reset the teaming context (member_id is set and reset by Main_get(put)_team). */
ctxtp->bda_teaming.reverse_hash = 0;
ctxtp->bda_teaming.master = 0;
/* Remove the pointer to the team structure. */
ctxtp->bda_teaming.bda_team = NULL;
/* Decrements the reference count and frees the team if necessary. */
Main_put_team(ctxtp, team);
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/*
* Function: Main_teaming_cleanup
* Description: This function is called from Main_cleanup to cleanup any teaming
* configuration that may exist on an adapter.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
VOID Main_teaming_cleanup (IN PMAIN_CTXT ctxtp) {
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Make sure that another BDA teaming operation isn't in progress before proceeding. */
while (ctxtp->bda_teaming.operation != BDA_TEAMING_OPERATION_NONE) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* Sleep while some other operation is in progress. */
Nic_sleep(10);
NdisAcquireSpinLock(&univ_bda_teaming_lock);
}
/* If we aren't part of a team, bail out - nothing to do. */
if (!ctxtp->bda_teaming.active) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/* Set the state to deleting. */
ctxtp->bda_teaming.operation = BDA_TEAMING_OPERATION_DELETING;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* Call Main_teaming_reset to actually remove this adapter from its team. */
Main_teaming_reset(ctxtp);
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Now that we're done, set the state back to ready to allow other operations to proceed. */
ctxtp->bda_teaming.operation = BDA_TEAMING_OPERATION_NONE;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return;
}
/*
* Function: Main_teaming_init
* Description: This function is called by either Main_init or Main_ctrl to re-initialize
* the teaming confguration on this adapter. If the new teaming configuration,
* which is stored in ctxtp->params is the same as the current configuration,
* then we don't need to bother. Otherwise, if we are already part of a team,
* we begin by cleaning up that state, which may unnecssary in some cases, but
* it makes things simpler and more straight-forward, so we'll live with it.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* Returns: BOOLEAN - TRUE if successful, FALSE if unsuccessful.
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
BOOLEAN Main_teaming_init (IN PMAIN_CTXT ctxtp) {
BOOLEAN bSuccess = TRUE;
PBDA_TEAM team;
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Make sure that another BDA teaming operation isn't in progress before proceeding. */
while (ctxtp->bda_teaming.operation != BDA_TEAMING_OPERATION_NONE) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* Sleep while some other operation is in progress. */
Nic_sleep(10);
NdisAcquireSpinLock(&univ_bda_teaming_lock);
}
/* Set the state to creating. */
ctxtp->bda_teaming.operation = BDA_TEAMING_OPERATION_CREATING;
/* If the parameters are invalid, do nothing. */
if (!ctxtp->params_valid) {
UNIV_PRINT_CRIT(("Main_teaming_init: Parameters are invalid."));
TRACE_CRIT("%!FUNC! Parameters are invalid");
bSuccess = TRUE;
goto end;
}
/* Check to see if the state of teaming has changed. If we were actively teaming
before and we are still part of a team, then we may be able to get out of here
without disturbing anything, if the rest of the configuration hasn't changed. */
if (ctxtp->bda_teaming.active == ctxtp->params.bda_teaming.active) {
if (ctxtp->bda_teaming.active) {
/* Make sure that I have a pointer to my team. */
UNIV_ASSERT(ctxtp->bda_teaming.bda_team);
/* If all other teaming parameters are unchanged, then we can bail out
because no part of the teaming configuration changed. */
if ((ctxtp->bda_teaming.master == ctxtp->params.bda_teaming.master) &&
(ctxtp->bda_teaming.reverse_hash == ctxtp->params.bda_teaming.reverse_hash) &&
Univ_equal_unicode_string(ctxtp->bda_teaming.bda_team->team_id, ctxtp->params.bda_teaming.team_id, wcslen(ctxtp->bda_teaming.bda_team->team_id))) {
bSuccess = TRUE;
goto end;
}
} else {
/* If I wasn't teaming before, and I'm not teaming now, there's nothing for me to do. */
bSuccess = TRUE;
goto end;
}
}
/* If this adapter is already in a team, cleanup first. At this point, we know that
some part of the teaming configuration has changed, so we'll cleanup our old state
if we need to and then re-build it with the new parameters if necessary. */
if (ctxtp->bda_teaming.active) {
UNIV_PRINT_VERB(("Main_teaming_init: This adapter is already part of a team."));
TRACE_VERB("%!FUNC! This adapter is already part of a team");
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* Cleanup our old teaming state first. */
Main_teaming_reset(ctxtp);
NdisAcquireSpinLock(&univ_bda_teaming_lock);
}
/* If, according to the new configuration, this adapter is not part of a team, do nothing. */
if (!ctxtp->params.bda_teaming.active) {
ctxtp->bda_teaming.member_id = CVY_BDA_INVALID_MEMBER_ID;
bSuccess = TRUE;
goto end;
}
/* Try to find a previous instance of this team. If the team does not
exist, Main_get_team will allocate, intialize and reference a new team. */
if (!(team = Main_get_team(ctxtp, ctxtp->params.bda_teaming.team_id))) {
UNIV_PRINT_CRIT(("Main_teaming_init: Error attempting to allocate memory for a team."));
TRACE_CRIT("%!FUNC! Error attempting to allocate memory for a team");
bSuccess = FALSE;
goto end;
}
/* If we are supposed to be the master for this team, we need to make sure that the
team doesn't already have a master, and if so, setup the shared load context. */
if (ctxtp->params.bda_teaming.master) {
/* If we are supposed to be the master for this team, check for an existing master. */
if (team->load) {
/* If the load pointer is set, then this team already has a master. */
UNIV_PRINT_CRIT(("Main_teaming_init: This team already has a master."));
TRACE_CRIT("%!FUNC! This team already has a master");
LOG_MSG(MSG_INFO_BDA_MULTIPLE_MASTERS, MSG_NONE);
/* Turn teaming off - SHOULD be off already, but... */
ctxtp->bda_teaming.active = FALSE;
/* Reset the teaming context (member_id is set and reset by Main_get(put)_team). */
ctxtp->bda_teaming.reverse_hash = 0;
ctxtp->bda_teaming.master = 0;
/* Remove the pointer to the team structure. */
ctxtp->bda_teaming.bda_team = NULL;
/* Release our reference on this team. */
Main_put_team(ctxtp, team);
bSuccess = FALSE;
goto end;
} else {
/* Otherwise, we are it. Set the global load state pointers
to our load module and load lock. */
team->load = &ctxtp->load;
team->load_lock = &ctxtp->load_lock;
/* If all members of my team are consistent, then activate the team. */
if (team->membership_map == team->consistency_map) team->active = TRUE;
/* Log the fact that a master has now been assigned to this team. */
LOG_MSG(MSG_INFO_BDA_MASTER_JOIN, MSG_NONE);
}
}
/* If we have just joined a team without a master, log an event to notify
the user that a team cannot function without a designated master. */
if (!team->load) {
LOG_MSG(MSG_INFO_BDA_NO_MASTER, MSG_NONE);
}
/* Store a pointer to the team in the adapter's teaming context. */
ctxtp->bda_teaming.bda_team = team;
/* Copy the teaming configuration from the parameters into the teaming context. */
ctxtp->bda_teaming.master = ctxtp->params.bda_teaming.master;
ctxtp->bda_teaming.reverse_hash = ctxtp->params.bda_teaming.reverse_hash;
ctxtp->bda_teaming.active = TRUE;
/* Over-ride the default reverse-hashing setting with the BDA specific directive. */
ctxtp->reverse_hash = ctxtp->bda_teaming.reverse_hash;
end:
ctxtp->bda_teaming.operation = BDA_TEAMING_OPERATION_NONE;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return bSuccess;
}
/*
* Function: Main_teaming_acquire_load
* Description: This function determines which load module a particular adapter should be unsing,
* sets the appropriate pointers, and references the appropriate load module. If an
* adapter is not part of a BDA team, then it should always be using its own load
* module - in that case, this function does nothing. If the adapter is part of a
* team, but the team in inactive, we return FALSE to indicate that the adapter should
* not accept this packet - inactive teams drop all traffic except traffic to the DIP.
* If the adapter is part of an active team, then we set the load and lock pointers to
* point to the team's master load state and appropriately set the reverse hashing
* indication based on the parameter setting for this adapter. In this scenario, which
* creates a cross-adapter load reference, we reference the master's load module so
* that it doesn't go away while we are using a pointer to it.
* Parameters: member - a pointer to the teaming member information for this adapter.
* ppLoad - an out pointer to a pointer to a load module set appropriately upon exit.
* ppLock - an out pointer to a pointer to a load lock set appropriately upon exit.
* pbRefused - an out pointer to a boolean that we set if this packet is refused (TRUE = drop it).
* Returns: BOOLEAN - an indication of whether or not this adapter is actually part of a team.
* Author: shouse, 3.29.01
* Notes: This function acquires the global teaming lock.
*/
BOOLEAN Main_teaming_acquire_load (IN PBDA_MEMBER member, OUT PLOAD_CTXT * ppLoad, OUT PNDIS_SPIN_LOCK * ppLock, OUT BOOLEAN * pbRefused) {
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Assert that the team membership information actually points to something. */
UNIV_ASSERT(member);
/* Assert that the load pointer and the pointer to the load pointer actually point to something. */
UNIV_ASSERT(ppLoad && *ppLoad);
/* Assert that the lock pointer and the pointer to the lock pointer actually point to something. */
UNIV_ASSERT(ppLock && *ppLock);
/* Assert that the refused pointer actually points to something. */
UNIV_ASSERT(pbRefused);
/* By default, we're not refusing the packet. */
*pbRefused = FALSE;
/* If we are an active BDA team participant, check the team state to see whether we
should accept this packet and fill in the load module/configuration parameters. */
if (member->active) {
PBDA_TEAM team = member->bda_team;
/* Assert that the team actually points to something. */
UNIV_ASSERT(team);
/* If the team is inactive, we will not handle this packet. */
if (!team->active) {
/* Refuse the packet. */
*pbRefused = TRUE;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* We're not teaming. */
return FALSE;
}
/* Otherwise, tell the caller to use the team's load lock and module. */
*ppLoad = team->load;
*ppLock = team->load_lock;
/* In the case of cross-adapter load module reference, add a reference to
the load module we are going to use to keep it from disappering on us. */
Load_add_reference(*ppLoad);
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* We are teaming. */
return TRUE;
}
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* We're not teaming. */
return FALSE;
}
/*
* Function: Main_teaming_release_load
* Description: This function releases a reference to a load module if necessary. If we did not
* acquire this load module pointer in teaming mode, then this is unnessary. Other-
* wise, we need to decrement the count, now that we are done using the pointer.
* Parameters: pLoad - a pointer to the load module to dereference.
* pLock - a pointer to the load lock corresponding to the load module pointer (unused).
* bTeaming - a boolean indication of whether or not we acquired this pointer in teaming mode.
* Returns: Nothing.
* Author: shouse, 3.29.01
* Notes:
*/
VOID Main_teaming_release_load (IN PLOAD_CTXT pLoad, IN PNDIS_SPIN_LOCK pLock, IN BOOLEAN bTeaming) {
/* Assert that the load pointer actually points to something. */
UNIV_ASSERT(pLoad);
/* Assert that the lock pointer actually points to something. */
UNIV_ASSERT(pLock);
/* If this load module was referenced, remove the reference. */
if (bTeaming) Load_release_reference(pLoad);
}
/*
* Function: Main_conn_get
* Description: This function is, for all intents and purposes, a teaming-aware wrapper
* around Load_conn_get. It determines which load module to utilize,
* based on the BDA teaming configuration on this adapter. Adapters that
* are not part of a team continue to use their own load modules (Which is
* BY FAR, the most common case). Adapters that are part of a team will
* use the load context of the adapter configured as the team's master as
* long as the team is in an active state. In such as case, because of
* the cross-adapter referencing of load modules, the reference count on
* the master's load module is incremented to keep it from "going away"
* while another team member is using it. When a team is marke inactive,
* which is the result of a misconfigured team either on this host or
* another host in the cluster, the adapter handles NO traffic that would
* require the use of a load module. This function queries the load module
* for the connection parameters of an active connection on this adapter.
* If an active connection is found, the IP tuple information is returned
* in the first five OUT parameters.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* OUT svr_addr - the server IP address (source IP on send, destination IP on recv).
* OUT svr_port - the server port (source port on send, destination port on recv).
* OUT clt_addr - the client IP address (detination IP on send, source IP on recv).
* OUT clt_port - the client port (destination port on send, source port on recv).
* OUT protocol - the protocol for this packet.
* Returns: BOOLEAN - indication of whether or we retrieved an active connection.
* Author: shouse, 10.7.01
* Notes:
*/
__inline BOOLEAN Main_conn_get (
PMAIN_CTXT ctxtp,
PULONG svr_addr,
PULONG svr_port,
PULONG clt_addr,
PULONG clt_port,
PUSHORT protocol
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p", ctxtp);
/* Pre-initialize the IN parameters. */
*svr_addr = 0;
*clt_addr = 0;
*svr_port = 0;
*clt_port = 0;
*protocol = 0;
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop request - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
NdisAcquireSpinLock(pLock);
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. */
acpt = Load_conn_get(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol);
NdisReleaseSpinLock(pLock);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_acquire_load_module above bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u, acpt = %u",
ctxtp, IP_GET_OCTET(*svr_addr, 0), IP_GET_OCTET(*svr_addr, 1), IP_GET_OCTET(*svr_addr, 2), IP_GET_OCTET(*svr_addr, 3),*svr_port, IP_GET_OCTET(*clt_addr, 0),
IP_GET_OCTET(*clt_addr, 1), IP_GET_OCTET(*clt_addr, 2), IP_GET_OCTET(*clt_addr, 3),*clt_port, *protocol, acpt);
return acpt;
}
/*
* Function: Main_conn_sanction
* Description: This function is, for all intents and purposes, a teaming-aware wrapper
* around Load_conn_sanction. It determines which load module to utilize,
* based on the BDA teaming configuration on this adapter. Adapters that
* are not part of a team continue to use their own load modules (Which is
* BY FAR, the most common case). Adapters that are part of a team will
* use the load context of the adapter configured as the team's master as
* long as the team is in an active state. In such as case, because of
* the cross-adapter referencing of load modules, the reference count on
* the master's load module is incremented to keep it from "going away"
* while another team member is using it. When a team is marke inactive,
* which is the result of a misconfigured team either on this host or
* another host in the cluster, the adapter handles NO traffic that would
* require the use of a load module. This function notifies the load module
* that the connection identified by the given IP tuple has been confirmed
* to still be active on this host. The load module reacts by moving the
* state information for this connection to the end of its recycle queue.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* svr_addr - the server IP address (source IP on send, destination IP on recv).
* svr_port - the server port (source port on send, destination port on recv).
* clt_addr - the client IP address (detination IP on send, source IP on recv).
* clt_port - the client port (destination port on send, source port on recv).
* protocol - the protocol for this packet.
* Returns: BOOLEAN - indication of whether or not we were able to update the connection.
* Author: shouse, 10.7.01
* Notes:
*/
__inline BOOLEAN Main_conn_sanction (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3),svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3),clt_port, protocol);
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop request - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
NdisAcquireSpinLock(pLock);
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. */
acpt = Load_conn_sanction(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol);
NdisReleaseSpinLock(pLock);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_acquire_load_module above bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_packet_check
* Description: This function is, for all intents and purposes, a teaming-aware wrapper
* around Load_packet_check. It determines which load module to utilize,
* based on the BDA teaming configuration on this adapter. Adapters that
* are not part of a team continue to use their own load modules (Which is
* BY FAR, the most common case). Adapters that are part of a team will
* use the load context of the adapter configured as the team's master as
* long as the team is in an active state. In such as case, because of
* the cross-adapter referencing of load modules, the reference count on
* the master's load module is incremented to keep it from "going away"
* while another team member is using it. When a team is marke inactive,
* which is the result of a misconfigured team either on this host or
* another host in the cluster, the adapter handles NO traffic that would
* require the use of a load module. Other traffic, such as traffic to
* the DIP, or RAW IP traffic, is allowed to pass. This function is called
* to filter, in general, TCP data packets, UDP packets and IPSec and GRE
* data packets.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* svr_addr - the server IP address (source IP on send, destination IP on recv).
* svr_port - the server port (source port on send, destination port on recv).
* clt_addr - the client IP address (detination IP on send, source IP on recv).
* clt_port - the client port (destination port on send, source port on recv).
* protocol - the protocol for this packet.
#if defined (NLB_HOOK_ENABLE)
* filter - the hashing mandate from the packet hook, if invoked.
#endif
* Returns: BOOLEAN - indication of whether or not to accept the packet.
* Author: shouse, 3.29.01
* Notes:
*/
__inline BOOLEAN Main_packet_check (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
#if defined (NLB_HOOK_ENABLE)
USHORT protocol,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
USHORT protocol
#endif
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol);
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop packet - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
#if defined (NLB_HOOK_ENABLE)
TRACE_FILTER("%!FUNC! Checking the hook feedback: reverse = %u", bReverse);
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a reverse hash");
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a forward hash");
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
NdisAcquireSpinLock(pLock);
TRACE_FILTER("%!FUNC! Consulting the load module: reverse = %u", bReverse);
/* Consult the load module. */
acpt = Load_packet_check(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol, bTeaming, (BOOLEAN)bReverse);
NdisReleaseSpinLock(pLock);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_conn_advise
* Description: This function is, for all intents and purposes, a teaming-aware wrapper
* around Load_conn_advise. It determines which load module to utilize,
* based on the BDA teaming configuration on this adapter. Adapters that
* are not part of a team continue to use their own load modules (Which is
* BY FAR, the most common case). Adapters that are part of a team will
* use the load context of the adapter configured as the team's master as
* long as the team is in an active state. In such as case, because of
* the cross-adapter referencing of load modules, the reference count on
* the master's load module is incremented to keep it from "going away"
* while another team member is using it. When a team is marke inactive,
* which is the result of a misconfigured team either on this host or
* another host in the cluster, the adapter handles NO traffic that would
* require the use of a load module. Other traffic, such as traffic to
* the DIP, or RAW IP traffic, is allowed to pass. This function is called,
* in general, to filter TCP control packets - SYN, FIN and RST.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* svr_addr - the server IP address (source IP on send, destination IP on recv).
* svr_port - the server port (source port on send, destination port on recv).
* clt_addr - the client IP address (detination IP on send, source IP on recv).
* clt_port - the client port (destination port on send, source port on recv).
* protocol - the protocol for this packet.
* conn_status - the TCP flag in this packet - SYN (UP), FIN (DOWN) or RST (RESET).
#if defined (NLB_HOOK_ENABLE)
* filter - the hashing mandate from the packet hook, if invoked.
#endif
* Returns: BOOLEAN - indication of whether or not to accept the packet.
* Author: shouse, 3.29.01
* Notes:
*/
__inline BOOLEAN Main_conn_advise (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol,
#if defined (NLB_HOOK_ENABLE)
ULONG conn_status,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
ULONG conn_status
#endif
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u, status = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol, conn_status);
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop packet - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
#if defined (NLB_HOOK_ENABLE)
TRACE_FILTER("%!FUNC! Checking the hook feedback: reverse = %u", bReverse);
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a reverse hash");
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a forward hash");
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
NdisAcquireSpinLock(pLock);
TRACE_FILTER("%!FUNC! Consulting the load module: reverse = %u", bReverse);
/* Consult the load module. */
acpt = Load_conn_advise(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol, conn_status, bTeaming, (BOOLEAN)bReverse);
NdisReleaseSpinLock(pLock);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above, bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_conn_notify
* Description: This function is, for all intents and purposes, a teaming-aware wrapper
* around Load_conn_notify. It determines which load module to utilize,
* based on the BDA teaming configuration on this adapter. Adapters that
* are not part of a team continue to use their own load modules (Which is
* BY FAR, the most common case). Adapters that are part of a team will
* use the load context of the adapter configured as the team's master as
* long as the team is in an active state. In such as case, because of
* the cross-adapter referencing of load modules, the reference count on
* the master's load module is incremented to keep it from "going away"
* while another team member is using it. When a team is marke inactive,
* which is the result of a misconfigured team either on this host or
* another host in the cluster, the adapter handles NO traffic that would
* require the use of a load module. Other traffic, such as traffic to
* the DIP, or RAW IP traffic, is allowed to pass. This function is called
* to notify the load module of a detected change in a connection - this
* interface is not to _ask_ the load module what to do (as Main_conn_advise
* and Main_packet_check do), but rather to _tell_ the load module something
* about a connection that may belong on this host. This function can be,
* but often is not, called as a result of the reception or transmission of
* a physical network packet.
* Parameters: ctxtp - a pointer to the MAIN_CTXT structure for this adapter.
* svr_addr - the server IP address (source IP on send, destination IP on recv).
* svr_port - the server port (source port on send, destination port on recv).
* clt_addr - the client IP address (detination IP on send, source IP on recv).
* clt_port - the client port (destination port on send, source port on recv).
* protocol - the protocol for this packet.
* conn_status - the TCP flag in this packet - SYN (UP), FIN (DOWN) or RST (RESET).
#if defined (NLB_HOOK_ENABLE)
* filter - the hashing mandate from the packet hook, if invoked.
#endif
* Returns: BOOLEAN - indication of whether or not to accept the packet.
* Author: shouse, 3.29.01
* Notes:
*/
__inline BOOLEAN Main_conn_notify (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol,
#if defined (NLB_HOOK_ENABLE)
ULONG conn_status,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
ULONG conn_status
#endif
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u, status = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol, conn_status);
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop packet - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
#if defined (NLB_HOOK_ENABLE)
TRACE_FILTER("%!FUNC! Checking the hook feedback: reverse = %u", bReverse);
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a reverse hash");
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a forward hash");
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
NdisAcquireSpinLock(pLock);
TRACE_FILTER("%!FUNC! Consulting the load module: reverse = %u", bReverse);
/* Consult the load module. */
acpt = Load_conn_notify(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol, conn_status, bTeaming, (BOOLEAN)bReverse);
NdisReleaseSpinLock(pLock);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above, bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
#if defined (NLB_TCP_NOTIFICATION)
/*
* Function: Main_conn_up
* Description: This function is used to notify NLB that a new connection has been established
* on the given NLB instance. This function performs a few house-keeping duties
* such as BDA state lookup, hook filter feedback processing, etc. before calling
* the load module to create state to track this connection.
* Parameters: ctxtp - the adapter context for the NLB instance on which the connection was established.
* svr_addr - the server IP address of the connection, in network byte order.
* svr_port - the server port of the connection, in host byte order.
* clt_addr - the client IP address of the connection, in network byte order.
* clt_port - the client port of the connection, in host byte order.
* protocol - the protocol of the connection.
#if defined (NLB_HOOK_ENABLE)
* filter - the feedback from the query hook, if one was registered.
#endif
* Returns: BOOLEAN - whether or not state was successfully created to track this connection.
* Author: shouse, 4.15.02
* Notes: DO NOT ACQUIRE ANY LOAD LOCKS IN THIS FUNCTION.
*/
__inline BOOLEAN Main_conn_up (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
#if defined (NLB_HOOK_ENABLE)
USHORT protocol,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
USHORT protocol
#endif
)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol);
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop packet - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
#if defined (NLB_HOOK_ENABLE)
TRACE_FILTER("%!FUNC! Checking the hook feedback: reverse = %u", bReverse);
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a reverse hash");
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a forward hash");
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
TRACE_FILTER("%!FUNC! Consulting the load module: reverse = %u", bReverse);
/* Consult the load module. */
acpt = Load_conn_up(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol, bTeaming, (BOOLEAN)bReverse);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above, bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_conn_down
* Description: This function is used to notify NLB that a protocol is removing state for an exisiting
* (but not necessarily established) connection. This function calls into the load module
* to find and destroy and state associated with this connection, which may or may not
* exist; if the connection was established on a non-NLB adapter, then NLB has no state
* associated with the connection.
* Parameters: svr_addr - the server IP address of the connection, in network byte order.
* svr_port - the server port of the connection, in host byte order.
* clt_addr - the client IP address of the connection, in network byte order.
* clt_port - the client port of the connection, in host byte order.
* protocol - the protocol of the connection.
* conn_status - whether the connection is being torn-down or reset.
* Returns: BOOLEAN - whether or not NLB found and destroyed the state for this connection.
* Author: shouse, 4.15.02
* Notes: DO NOT ACQUIRE ANY LOAD LOCKS IN THIS FUNCTION.
*/
__inline BOOLEAN Main_conn_down (
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol,
ULONG conn_status
)
{
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u, status = %u",
IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol, conn_status);
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. */
acpt = Load_conn_down(svr_addr, svr_port, clt_addr, clt_port, protocol, conn_status);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_conn_pending
* Description: This function is used to notify NLB that an OUTGOING connection is being established.
* Because it is unknown on which adapter the connection will return and ultimately be
* established, NLB creates state to track this connection globally and when the connection
* is finally established, the protocol informs NLB on which adapter the connection was
* completed (via Main_conn_established). This function merely creates some global state
* to ensure that if the connection DOES come back on an NLB adapter, we'll be sure to
* pass the packet(s) up to the protocol.
* Parameters: svr_addr - the server IP address of the connection, in network byte order.
* svr_port - the server port of the connection, in host byte order.
* clt_addr - the client IP address of the connection, in network byte order.
* clt_port - the client port of the connection, in host byte order.
* protocol - the protocol of the connection.
* Returns: BOOLEAN - whether or not NLB was able to create state to track this pending connection.
* Author: shouse, 4.15.02
* Notes: DO NOT ACQUIRE ANY LOAD LOCKS IN THIS FUNCTION.
*/
__inline BOOLEAN Main_conn_pending (
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol
)
{
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol);
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. */
acpt = Load_conn_pending(svr_addr, svr_port, clt_addr, clt_port, protocol);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_pending_check
* Description: This function checks to see whether or not pending connection state is present
* for the given connection. If so, the packet is accepted; if not, it should be
* dropped.
* Parameters: svr_addr - the server IP address (source IP on send, destination IP on recv).
* svr_port - the server port (source port on send, destination port on recv).
* clt_addr - the client IP address (detination IP on send, source IP on recv).
* clt_port - the client port (destination port on send, source port on recv).
* protocol - the protocol for this packet.
* Returns: BOOLEAN - indication of whether or not the pending connection was found.
* Author: shouse, 4.15.02
* Notes: DO NOT ACQUIRE ANY LOAD LOCKS IN THIS FUNCTION.
*/
__inline BOOLEAN Main_pending_check (
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
USHORT protocol
)
{
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol);
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. */
acpt = Load_pending_check(svr_addr, svr_port, clt_addr, clt_port, protocol);
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_conn_establish
* Description: This function is used to notify NLB that a new OUTGOING connection has been
* established on the given NLB adapter. Note that the context CAN BE NULL if
* the connection was established on a non-NLB adapter. In that case, we don't
* want to create state to track the connection, but we need to remove our state
* that was tracking this pending outgoing connection. If the context is non-
* NULL, then in addition, we need to create the state to track this new connection.
* This function performs a few house-keeping duties such as BDA state lookup, hook
* filter feedback processing, etc. before calling the load module to modify the
* state for this connection.
* Parameters: ctxtp - the adapter context for the NLB instance on which the connection was established.
* svr_addr - the server IP address of the connection, in network byte order.
* svr_port - the server port of the connection, in host byte order.
* clt_addr - the client IP address of the connection, in network byte order.
* clt_port - the client port of the connection, in host byte order.
* protocol - the protocol of the connection.
#if defined (NLB_HOOK_ENABLE)
* filter - the feedback from the query hook, if one was registered.
#endif
* Returns: BOOLEAN - whether or not state was successfully updated for this connection.
* Author: shouse, 4.15.02
* Notes: ctxtp CAN BE NULL if the outgoing connection was established on a non-NLB NIC.
* DO NOT ACQUIRE ANY LOAD LOCKS IN THIS FUNCTION.
*/
__inline BOOLEAN Main_conn_establish (
PMAIN_CTXT ctxtp,
ULONG svr_addr,
ULONG svr_port,
ULONG clt_addr,
ULONG clt_port,
#if defined (NLB_HOOK_ENABLE)
USHORT protocol,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
USHORT protocol
#endif
)
{
BOOLEAN acpt = TRUE;
TRACE_FILTER("%!FUNC! Enter: ctxtp = %p, server IP = %u.%u.%u.%u, server port = %u, client IP = %u.%u.%u.%u, client port = %u, protocol = %u",
ctxtp, IP_GET_OCTET(svr_addr, 0), IP_GET_OCTET(svr_addr, 1), IP_GET_OCTET(svr_addr, 2), IP_GET_OCTET(svr_addr, 3), svr_port,
IP_GET_OCTET(clt_addr, 0), IP_GET_OCTET(clt_addr, 1), IP_GET_OCTET(clt_addr, 2), IP_GET_OCTET(clt_addr, 3), clt_port, protocol);
/* Convert TCP port 1723 to PPTP for use by the load module. This conversion must
be done in main and not load, as the load module would not know whether to look
at the server port or client port because of reverse hashing. */
if ((protocol == TCPIP_PROTOCOL_TCP) && (svr_port == PPTP_CTRL_PORT))
protocol = TCPIP_PROTOCOL_PPTP;
if (ctxtp == NULL)
{
TRACE_FILTER("%!FUNC! Consulting the load module");
/* Consult the load module. Note that Load_conn_establish MUST handle a NULL load pointer.
The values of limit_map_fn and reverse_hash are irrelevant in this case. */
acpt = Load_conn_establish(NULL, svr_addr, svr_port, clt_addr, clt_port, protocol, FALSE, FALSE);
}
else
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the bRefused is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, dump it. */
if (bRefused) {
TRACE_FILTER("%!FUNC! Drop packet - BDA team inactive");
acpt = FALSE;
goto exit;
}
}
#if defined (NLB_HOOK_ENABLE)
TRACE_FILTER("%!FUNC! Checking the hook feedback: reverse = %u", bReverse);
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a reverse hash");
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
TRACE_FILTER("%!FUNC! Forcing a forward hash");
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
TRACE_FILTER("%!FUNC! Consulting the load module: reverse = %u", bReverse);
/* Consult the load module. */
acpt = Load_conn_establish(pLoad, svr_addr, svr_port, clt_addr, clt_port, protocol, bTeaming, (BOOLEAN)bReverse);
exit:
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above, bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
}
TRACE_FILTER("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
#endif
/*
* Function: Main_query_packet_filter
* Desctription: This function takes an IP address tuple and a protocol and determines whether
* or not this virtual packet would be accepted by this instance of NLB. This
* function checks reasons for accept/drop such as NLB being turned off, or the
* destination IP address being the dedicated IP address, BDA teaming, etc. The
* load module is then consulted to make a accept/drop decision based on the
* current load balancing policy in place. The reason for accepting or dropping
* the packet is returned, as well as some load-balancing state used to make the
* decision, in appropriate instances. This function DOES NOT change the state
* of NLB at ALL, so its execution does not change or affect normal NLB operation
* in any way.
* Parameters: ctxtp - a pointer to the NLB context buffer for the appropriate NLB instance.
* pQuery - a buffer into which the results of filtering the virtual packet are placed.
* Returns: Nothing.
* Author: shouse, 5.18.01
* Notes: It is critical the NO CHANGES are made to NLB here - only observe, don't interfere.
*/
VOID Main_query_packet_filter (PMAIN_CTXT ctxtp, PNLB_OPTIONS_PACKET_FILTER pQuery)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad;
PNDIS_SPIN_LOCK pLock;
ULONG bReverse = ctxtp->reverse_hash;
BOOLEAN bTeaming = FALSE;
BOOLEAN bRefused = FALSE;
BOOLEAN acpt = TRUE;
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
#endif
UNIV_ASSERT(ctxtp);
UNIV_ASSERT(pQuery);
pLoad = &ctxtp->load;
pLock = &ctxtp->load_lock;
/* NOTE: This entire operation assumes RECEIVE path semantics - most outgoing traffic
is not filtered by NLB anyway, so there isn't much need to query send filtering. */
/* First check for remote control requests, which are always UDP and are always allowed to pass, but
of course, are never actually seen by the protocol stack (they're turned around internally). */
if (pQuery->Protocol == TCPIP_PROTOCOL_UDP) {
/* Otherwise, if the server UDP port is the remote control port, then this is an incoming
remote control request from another NLB cluster host. These are always allowed to pass. */
if (ctxtp->params.rct_enabled &&
(pQuery->ServerPort == ctxtp->params.rct_port || pQuery->ServerPort == CVY_DEF_RCT_PORT_OLD) &&
(pQuery->ServerIPAddress == ctxtp->cl_ip_addr || pQuery->ServerIPAddress == TCPIP_BCAST_ADDR)) {
pQuery->Accept = NLB_ACCEPT_REMOTE_CONTROL_REQUEST;
return;
}
}
#if defined (NLB_HOOK_ENABLE)
/* Invoke the packet query hook, if one has been registered. */
filter = Main_query_hook(ctxtp, pQuery->ServerIPAddress, pQuery->ServerPort, pQuery->ClientIPAddress, pQuery->ClientPort, pQuery->Protocol);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* Unconditionally accept the packet. */
pQuery->Accept = NLB_REJECT_HOOK;
return;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* Unconditionally drop the packet. */
pQuery->Accept = NLB_ACCEPT_HOOK;
return;
}
#endif
/* Before we pass remote control responses up the stack, which are normally not filtered,
we consult the hook to make sure we aren't supposed to drop it. */
if (pQuery->Protocol == TCPIP_PROTOCOL_UDP) {
/* If the client UDP port is the remote control port, then this is a remote control
response from another NLB cluster host. These are always allowed to pass. */
if (pQuery->ClientPort == ctxtp->params.rct_port || pQuery->ClientPort == CVY_DEF_RCT_PORT_OLD) {
pQuery->Accept = NLB_ACCEPT_REMOTE_CONTROL_RESPONSE;
return;
}
}
/* Check for traffic destined for the dedicated IP address of this host.
These packets are always allowed to pass. */
if (pQuery->ServerIPAddress == ctxtp->ded_ip_addr) {
pQuery->Accept = NLB_ACCEPT_DIP;
return;
}
/* Check for traffic destined for the cluster or dedicated broadcast IP addresses.
These packets are always allowed to pass. */
if (pQuery->ServerIPAddress == ctxtp->ded_bcast_addr || pQuery->ServerIPAddress == ctxtp->cl_bcast_addr) {
pQuery->Accept = NLB_ACCEPT_BROADCAST;
return;
}
/* Check for passthru packets. When the cluster IP address has not been specified, the
cluster moves into passthru mode, in which it passes up ALL packets received. */
if (ctxtp->cl_ip_addr == 0) {
pQuery->Accept = NLB_ACCEPT_PASSTHRU_MODE;
return;
}
/* Before we load-balance this packet, check to see whether or not its destined for
the dedicated IP address of another NLB host in our cluster. If it is, drop it. */
if (DipListCheckItem(&ctxtp->dip_list, pQuery->ServerIPAddress)) {
pQuery->Accept = NLB_REJECT_DIP;
return;
}
/* If the cluster is not operational, which can happen, for example as a result of a wlbs.exe
command such as "wlbs stop", or as a result of bad parameter settings, then drop all traffic
that does not meet the above conditions. */
if (!ctxtp->convoy_enabled) {
pQuery->Accept = NLB_REJECT_CLUSTER_STOPPED;
return;
}
/* If this is an ICMP filter request, whether or not its filtered at all depends on the FilterICMP
registry setting. If we're not filtering ICMP, return ACCEPT now; otherwise, ICMP is filtered
like UDP with no port information - fall through and consult the load module. */
if (pQuery->Protocol == TCPIP_PROTOCOL_ICMP) {
/* If we are filtering ICMP, change the protocol to UDP and the ports to 0, 0 before continuing. */
if (ctxtp->params.filter_icmp) {
pQuery->Protocol = TCPIP_PROTOCOL_UDP;
pQuery->ClientPort = 0;
pQuery->ServerPort = 0;
/* Otherwise, return ACCEPT now and bail out. */
} else {
pQuery->Accept = NLB_ACCEPT_UNFILTERED;
return;
}
}
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it - in the worst case, we handle a
packet we perhaps shouldn't have while we were joining a team or changing
our current team configuration. */
if (ctxtp->bda_teaming.active) {
/* Check the teaming configuration and add a reference to the load module before consulting the load
module. If the return value is TRUE, then the load module was NOT referenced, so we can bail out. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If teaming has suggested that we not allow this packet to pass, the cluster will
drop it. This occurs when teams are inconsistently configured, or when a team is
without a master, in which case there is no load context to consult anyway. */
if (bRefused) {
pQuery->Accept = NLB_REJECT_BDA_TEAMING_REFUSED;
return;
}
}
#if defined (NLB_HOOK_ENABLE)
switch (filter) {
case NLB_FILTER_HOOK_PROCEED_WITH_HASH:
/* Do nothing different as a result of the hook. */
break;
case NLB_FILTER_HOOK_REVERSE_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
bReverse = TRUE;
break;
case NLB_FILTER_HOOK_FORWARD_HASH:
/* Ignore whatever hashing settings we found in our configuration
and hash in the dircetion the hook asked us to. */
bReverse = FALSE;
break;
case NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY:
case NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY:
default:
/* These cases should be taken care of long before we get here. */
UNIV_ASSERT(FALSE);
break;
}
#endif
NdisAcquireSpinLock(pLock);
/* Consult the load module. */
Load_query_packet_filter(pLoad, pQuery, pQuery->ServerIPAddress, pQuery->ServerPort, pQuery->ClientIPAddress, pQuery->ClientPort, pQuery->Protocol, pQuery->Flags, bTeaming, (BOOLEAN)bReverse);
NdisReleaseSpinLock(pLock);
/* Release the reference on the load module if necessary. If we aren't teaming, even in
the case we skipped calling Main_teaming_Acquire_load_module above, bTeaming is FALSE,
so there is no need to call this function to release a reference. */
if (bTeaming) Main_teaming_release_load(pLoad, pLock, bTeaming);
}
ULONG Main_ip_addr_init (
PMAIN_CTXT ctxtp)
{
ULONG byte [4];
ULONG i;
PWCHAR tmp;
ULONG old_ip_addr;
/* initialize dedicated IP address from the register string */
tmp = ctxtp -> params . ded_ip_addr;
ctxtp -> ded_ip_addr = 0;
/* do not initialize if one was not specified */
if (tmp [0] == 0)
goto ded_netmask;
for (i = 0; i < 4; i ++)
{
if (! Univ_str_to_ulong (byte + i, tmp, & tmp, 3, 10) ||
(i < 3 && * tmp != L'.'))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Bad dedicated IP address"));
TRACE_CRIT("%!FUNC! Bad dedicated IP address");
LOG_MSG (MSG_WARN_DED_IP_ADDR, ctxtp -> params . ded_ip_addr);
ctxtp -> ded_net_mask = 0;
goto ded_netmask;
}
tmp ++;
}
IP_SET_ADDR (& ctxtp -> ded_ip_addr, byte [0], byte [1], byte [2], byte [3]);
UNIV_PRINT_VERB(("Main_ip_addr_init: Dedicated IP address: %u.%u.%u.%u = %x", byte [0], byte [1], byte [2], byte [3], ctxtp -> ded_ip_addr));
TRACE_VERB("%!FUNC! Dedicated IP address: %u.%u.%u.%u = 0x%x", byte [0], byte [1], byte [2], byte [3], ctxtp -> ded_ip_addr);
ded_netmask:
/* initialize dedicated net mask from the register string */
tmp = ctxtp -> params . ded_net_mask;
ctxtp -> ded_net_mask = 0;
/* do not initialize if one was not specified */
if (tmp [0] == 0)
goto cluster;
for (i = 0; i < 4; i ++)
{
if (! Univ_str_to_ulong (byte + i, tmp, & tmp, 3, 10) ||
(i < 3 && * tmp != L'.'))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Bad dedicated net mask address"));
TRACE_CRIT("%!FUNC! Bad dedicated net mask address");
LOG_MSG (MSG_WARN_DED_NET_MASK, ctxtp -> params . ded_net_mask);
ctxtp -> ded_ip_addr = 0;
goto cluster;
}
tmp ++;
}
IP_SET_ADDR (& ctxtp -> ded_net_mask, byte [0], byte [1], byte [2], byte [3]);
UNIV_PRINT_VERB(("Main_ip_addr_init: Dedicated net mask: %u.%u.%u.%u = %x", byte [0], byte [1], byte [2], byte [3], ctxtp -> ded_net_mask));
TRACE_VERB("%!FUNC! Dedicated net mask: %u.%u.%u.%u = 0x%x", byte [0], byte [1], byte [2], byte [3], ctxtp -> ded_net_mask);
cluster:
/* initialize cluster IP address from the register string */
tmp = ctxtp -> params . cl_ip_addr;
/* Save the previous cluster IP address to notify bi-directional affinity teaming. */
old_ip_addr = ctxtp -> cl_ip_addr;
ctxtp -> cl_ip_addr = 0;
for (i = 0; i < 4; i ++)
{
if (! Univ_str_to_ulong (byte + i, tmp, & tmp, 3, 10) ||
(i < 3 && * tmp != L'.'))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Bad cluster IP address"));
TRACE_CRIT("%!FUNC! Bad cluster IP address");
LOG_MSG (MSG_ERROR_CL_IP_ADDR, ctxtp -> params . cl_ip_addr);
ctxtp -> cl_net_mask = 0;
return FALSE;
}
tmp ++;
}
IP_SET_ADDR (& ctxtp -> cl_ip_addr, byte [0], byte [1], byte [2], byte [3]);
UNIV_PRINT_VERB(("Main_ip_addr_init: Cluster IP address: %u.%u.%u.%u = %x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_ip_addr));
TRACE_VERB("%!FUNC! Cluster IP address: %u.%u.%u.%u = 0x%x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_ip_addr);
/* Notify BDA teaming config that a cluster IP address might have changed. */
Main_teaming_ip_addr_change(ctxtp, old_ip_addr, ctxtp->cl_ip_addr);
/* initialize cluster net mask from the register string */
tmp = ctxtp -> params . cl_net_mask;
ctxtp -> cl_net_mask = 0;
/* do not initialize if one was not specified */
for (i = 0; i < 4; i ++)
{
if (! Univ_str_to_ulong (byte + i, tmp, & tmp, 3, 10) ||
(i < 3 && * tmp != L'.'))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Bad cluster net mask address"));
TRACE_CRIT("%!FUNC! Bad cluster net mask address");
LOG_MSG (MSG_ERROR_CL_NET_MASK, ctxtp -> params . cl_net_mask);
return FALSE;
}
tmp ++;
}
IP_SET_ADDR (& ctxtp -> cl_net_mask, byte [0], byte [1], byte [2], byte [3]);
UNIV_PRINT_VERB(("Main_ip_addr_init: Cluster net mask: %u.%u.%u.%u = %x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_net_mask));
TRACE_VERB("%!FUNC! Cluster net mask: %u.%u.%u.%u = 0x%x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_net_mask);
if (ctxtp -> params . mcast_support && ctxtp -> params . igmp_support)
{
/* Initialize the multicast IP address for IGMP support */
tmp = ctxtp -> params . cl_igmp_addr;
ctxtp -> cl_igmp_addr = 0;
/* do not initialize if one was not specified */
for (i = 0; i < 4; i ++)
{
if (! Univ_str_to_ulong (byte + i, tmp, & tmp, 3, 10) ||
(i < 3 && * tmp != L'.'))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Bad cluster igmp address"));
TRACE_CRIT("%!FUNC! Bad cluster igmp address");
LOG_MSG (MSG_ERROR_CL_IGMP_ADDR, ctxtp -> params . cl_igmp_addr);
return FALSE;
}
tmp ++;
}
IP_SET_ADDR (& ctxtp -> cl_igmp_addr, byte [0], byte [1], byte [2], byte [3]);
UNIV_PRINT_VERB(("Main_ip_addr_init: Cluster IGMP Address: %u.%u.%u.%u = %x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_igmp_addr));
TRACE_VERB("%!FUNC! Cluster IGMP Address: %u.%u.%u.%u = 0x%x", byte [0], byte [1], byte [2], byte [3], ctxtp -> cl_igmp_addr);
}
if ((ctxtp -> ded_ip_addr != 0 && ctxtp -> ded_net_mask == 0) ||
(ctxtp -> ded_ip_addr == 0 && ctxtp -> ded_net_mask != 0))
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Need to specify both dedicated IP address AND network mask"));
TRACE_CRIT("%!FUNC! Need to specify both dedicated IP address AND network mask");
LOG_MSG (MSG_WARN_DED_MISMATCH, MSG_NONE);
ctxtp -> ded_ip_addr = 0;
ctxtp -> ded_net_mask = 0;
}
IP_SET_BCAST (& ctxtp -> cl_bcast_addr, ctxtp -> cl_ip_addr, ctxtp -> cl_net_mask);
UNIV_PRINT_VERB(("Main_ip_addr_init: Cluster broadcast address: %u.%u.%u.%u = %x", ctxtp -> cl_bcast_addr & 0xff, (ctxtp -> cl_bcast_addr >> 8) & 0xff, (ctxtp -> cl_bcast_addr >> 16) & 0xff, (ctxtp -> cl_bcast_addr >> 24) & 0xff, ctxtp -> cl_bcast_addr));
TRACE_VERB("%!FUNC! Cluster broadcast address: %u.%u.%u.%u = 0x%x", ctxtp -> cl_bcast_addr & 0xff, (ctxtp -> cl_bcast_addr >> 8) & 0xff, (ctxtp -> cl_bcast_addr >> 16) & 0xff, (ctxtp -> cl_bcast_addr >> 24) & 0xff, ctxtp -> cl_bcast_addr);
if (ctxtp -> ded_ip_addr != 0)
{
IP_SET_BCAST (& ctxtp -> ded_bcast_addr, ctxtp -> ded_ip_addr, ctxtp -> ded_net_mask);
UNIV_PRINT_VERB(("Main_ip_addr_init: Dedicated broadcast address: %u.%u.%u.%u = %x", ctxtp -> ded_bcast_addr & 0xff, (ctxtp -> ded_bcast_addr >> 8) & 0xff, (ctxtp -> ded_bcast_addr >> 16) & 0xff, (ctxtp -> ded_bcast_addr >> 24) & 0xff, ctxtp -> ded_bcast_addr));
TRACE_VERB("%!FUNC! Dedicated broadcast address: %u.%u.%u.%u = 0x%x", ctxtp -> ded_bcast_addr & 0xff, (ctxtp -> ded_bcast_addr >> 8) & 0xff, (ctxtp -> ded_bcast_addr >> 16) & 0xff, (ctxtp -> ded_bcast_addr >> 24) & 0xff, ctxtp -> ded_bcast_addr);
}
else
ctxtp -> ded_bcast_addr = 0;
if (ctxtp -> cl_ip_addr == 0)
{
UNIV_PRINT_CRIT(("Main_ip_addr_init: Cluster IP address = 0. Cluster host stopped"));
TRACE_CRIT("%!FUNC! Cluster IP address = 0. Cluster host stopped");
return FALSE;
}
return TRUE;
} /* end Main_ip_addr_init */
ULONG Main_mac_addr_init (
PMAIN_CTXT ctxtp)
{
ULONG i, b, len;
PUCHAR ap;
PWCHAR tmp;
PUCHAR srcp, dstp;
ULONG non_zero = 0;
CVY_MAC_ADR old_mac_addr;
UNIV_ASSERT(ctxtp -> medium == NdisMedium802_3);
/* remember old mac address so we can yank it out of the multicast list */
old_mac_addr = ctxtp->cl_mac_addr;
/* at the time this routine is called by Prot_bind - ded_mad_addr is
already set */
tmp = ctxtp -> params . cl_mac_addr;
len = CVY_MAC_ADDR_LEN (ctxtp -> medium);
ap = (PUCHAR) & ctxtp -> cl_mac_addr;
for (i = 0; i < len; i ++)
{
/* setup destination broadcast and source cluster addresses */
if (! Univ_str_to_ulong (& b, tmp, & tmp, 2, 16) ||
(i < len - 1 && * tmp != L'-' && * tmp != L':'))
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: Bad cluster network address"));
TRACE_CRIT("%!FUNC! Bad cluster network address");
LOG_MSG (MSG_ERROR_NET_ADDR, ctxtp -> params . cl_mac_addr);
/* WLBS 2.3 prevent from failing if no MAC address - just use the
dedicated one as cluster */
NdisMoveMemory (& ctxtp -> cl_mac_addr, & ctxtp -> ded_mac_addr, len);
non_zero = 1;
break;
}
tmp ++;
ap [i] = (UCHAR) b;
/* WLBS 2.3 sum up bytes for future non-zero check */
non_zero += b;
}
/* WLBS 2.3 - use dedicated address as cluster address if specified address
is zero - this could be due to parameter errors */
if (non_zero == 0)
NdisMoveMemory (& ctxtp -> cl_mac_addr, & ctxtp -> ded_mac_addr, len);
/* enforce group flag to proper value */
if (ctxtp -> params . mcast_support)
ap [0] |= ETHERNET_GROUP_FLAG;
else
ap [0] &= ~ETHERNET_GROUP_FLAG;
dstp = ctxtp -> media_hdr . ethernet . dst . data;
srcp = ctxtp -> media_hdr . ethernet . src . data;
len = ETHERNET_ADDRESS_FIELD_SIZE;
CVY_ETHERNET_ETYPE_SET (& ctxtp -> media_hdr . ethernet, MAIN_FRAME_SIG);
ctxtp -> etype_old = FALSE;
/* V1.3.1b - load multicast address as destination instead of broadcast */
for (i = 0; i < len; i ++)
{
if (ctxtp -> params . mcast_support)
dstp [i] = ap [i];
else
dstp [i] = 0xff;
srcp [i] = ((PUCHAR) & ctxtp -> ded_mac_addr) [i];
}
if (! ctxtp -> params . mcast_support)
{
/* V2.0.6 - override source MAC address to prevent switch confusion */
if (ctxtp -> params . mask_src_mac)
{
ULONG byte [4];
CVY_MAC_ADDR_LAA_SET (ctxtp -> medium, srcp);
* ((PUCHAR) (srcp + 1)) = (UCHAR) ctxtp -> params . host_priority;
IP_GET_ADDR(ctxtp->cl_ip_addr, &byte[0], &byte[1], &byte[2], &byte[3]);
* ((PUCHAR) (srcp + 2)) = (UCHAR) byte[0];
* ((PUCHAR) (srcp + 3)) = (UCHAR) byte[1];
* ((PUCHAR) (srcp + 4)) = (UCHAR) byte[2];
* ((PUCHAR) (srcp + 5)) = (UCHAR) byte[3];
// * ((PULONG) (srcp + 2)) = ctxtp -> cl_ip_addr;
}
/* make source address look difference than our dedicated to prevent
Compaq drivers from optimizing their reception out */
else
CVY_MAC_ADDR_LAA_TOGGLE (ctxtp -> medium, srcp);
}
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "Cluster network address: ", ap);
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "Dedicated network address: ", srcp);
{
ULONG xferred = 0;
ULONG needed = 0;
PNDIS_REQUEST request;
MAIN_ACTION act;
PUCHAR ptr;
NDIS_STATUS status;
ULONG size, j;
len = CVY_MAC_ADDR_LEN (ctxtp->medium);
size = ctxtp->max_mcast_list_size * len;
status = NdisAllocateMemoryWithTag (& ptr, size, UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: Error allocating space %d %x", size, status));
TRACE_CRIT("%!FUNC! Error allocating space %d 0x%x", size, status);
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
return FALSE;
}
act.code = MAIN_ACTION_CODE;
act.ctxtp = ctxtp;
act.op.request.xferred = &xferred;
act.op.request.needed = &needed;
act.op.request.external = FALSE;
act.op.request.buffer_len = 0;
act.op.request.buffer = NULL;
NdisInitializeEvent(&act.op.request.event);
NdisResetEvent(&act.op.request.event);
NdisZeroMemory(&act.op.request.req, sizeof(NDIS_REQUEST));
request = &act.op.request.req;
/* get current mcast list */
request -> RequestType = NdisRequestQueryInformation;
request -> DATA . QUERY_INFORMATION . Oid = OID_802_3_MULTICAST_LIST;
request -> DATA . QUERY_INFORMATION . InformationBufferLength = size;
request -> DATA . QUERY_INFORMATION . InformationBuffer = ptr;
act.status = NDIS_STATUS_FAILURE;
status = Prot_request (ctxtp, & act, FALSE);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: Error %x querying multicast address list %d %d", status, xferred, needed));
TRACE_CRIT("%!FUNC! Error 0x%x querying multicast address list %d %d", status, xferred, needed);
NdisFreeMemory (ptr, size, 0);
return FALSE;
}
for (i = 0; i < xferred; i += len)
{
if (CVY_MAC_ADDR_COMP (ctxtp -> medium, (PUCHAR) ptr + i, & old_mac_addr))
{
UNIV_PRINT_VERB(("Main_mac_addr_init: Old cluster MAC matched"));
TRACE_VERB("%!FUNC! Old cluster MAC matched");
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "", & old_mac_addr);
break;
}
}
/* Load cluster address as multicast one to the NIC. If the cluster IP address 0.0.0.0, then we
don't want to add the multicast MAC address to the NIC. */
if (ctxtp -> params . mcast_support)
{
if (ctxtp -> params . cl_ip_addr != 0)
{
if (i < xferred)
{
UNIV_PRINT_VERB(("Main_mac_addr_init: Copying new MAC into multicast list[%d]", i / len));
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "", & ctxtp->cl_mac_addr);
CVY_MAC_ADDR_COPY (ctxtp->medium, (PUCHAR) ptr + i, & ctxtp->cl_mac_addr);
}
else
{
UNIV_PRINT_VERB(("Main_mac_addr_init: Adding new MAC"));
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "", & ctxtp->cl_mac_addr);
if (xferred + len > size)
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: No room for cluster mac %d", ctxtp->max_mcast_list_size));
LOG_MSG1 (MSG_ERROR_MCAST_LIST_SIZE, MSG_NONE, ctxtp->max_mcast_list_size);
NdisFreeMemory (ptr, size, 0);
return FALSE;
}
UNIV_PRINT_VERB(("Main_mac_addr_init: Copying new MAC into multicast list[%d]", i / len));
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "", & ctxtp->cl_mac_addr);
CVY_MAC_ADDR_COPY (ctxtp->medium, (PUCHAR) ptr + xferred, & ctxtp->cl_mac_addr);
xferred += len;
}
}
else
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: Refusing to add an unconfigured cluster MAC address to the multicast list"));
NdisFreeMemory (ptr, size, 0);
return FALSE;
}
}
else
{
if (i < xferred)
{
for (j = i + len; j < xferred; j += len, i+= len)
{
if (CVY_MAC_ADDR_COMP (ctxtp -> medium, (PUCHAR) ptr + j, & old_mac_addr))
{
UNIV_PRINT_VERB(("Main_mac_addr_init: Old cluster MAC matched AGAIN - this shouldn't happen!!!"));
CVY_MAC_ADDR_PRINT (ctxtp -> medium, "", & old_mac_addr);
break;
}
CVY_MAC_ADDR_COPY (ctxtp->medium, (PUCHAR) ptr + i, (PUCHAR) ptr + j);
}
xferred -= len;
}
else
{
NdisFreeMemory (ptr, size, 0);
return TRUE;
}
}
request -> RequestType = NdisRequestSetInformation;
request -> DATA . SET_INFORMATION . Oid = OID_802_3_MULTICAST_LIST;
request -> DATA . SET_INFORMATION . InformationBufferLength = xferred;
request -> DATA . SET_INFORMATION . InformationBuffer = ptr;
act.status = NDIS_STATUS_FAILURE;
status = Prot_request (ctxtp, & act, FALSE);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_mac_addr_init: Error %x setting multicast address %d %d", status, xferred, needed));
TRACE_CRIT("%!FUNC! Error 0x%x setting multicast address %d %d", status, xferred, needed);
NdisFreeMemory (ptr, size, 0);
return FALSE;
}
NdisFreeMemory (ptr, size, 0);
}
return TRUE;
} /* end Main_mac_addr_init */
/* Initialize the Ethernet Header and IP packet for sending out IGMP joins/leaves */
ULONG Main_igmp_init (
PMAIN_CTXT ctxtp,
BOOLEAN join)
{
PUCHAR ptr;
ULONG checksum;
PMAIN_IGMP_DATA igmpd = & (ctxtp -> igmp_frame . igmp_data);
PMAIN_IP_HEADER iph = & (ctxtp -> igmp_frame . ip_data);
PUCHAR srcp, dstp;
UINT i;
UNIV_ASSERT (ctxtp -> medium == NdisMedium802_3);
if ((!ctxtp -> params . mcast_support) || (!ctxtp -> params . igmp_support))
{
return FALSE;
}
/* Fill in the igmp data */
igmpd -> igmp_vertype = 0x12; /* Needs to be changed for join/leave */
igmpd -> igmp_unused = 0x00;
igmpd -> igmp_xsum = 0x0000;
igmpd -> igmp_address = ctxtp -> cl_igmp_addr;
/* Compute the IGMP checksum */
ptr = (PUCHAR) igmpd;
checksum = 0;
for (i = 0; i < sizeof (MAIN_IGMP_DATA) / 2; i ++, ptr += 2)
checksum += (ULONG) ((ptr [0] << 8) | ptr [1]);
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += (checksum >> 16);
checksum = (~ checksum);
ptr = (PUCHAR) (& igmpd -> igmp_xsum);
ptr [0] = (CHAR) ((checksum >> 8) & 0xff);
ptr [1] = (CHAR) (checksum & 0xff);
/* Fill in the IP Header */
iph -> iph_verlen = 0x45;
iph -> iph_tos = 0;
iph -> iph_length = 0x1c00;
iph -> iph_id = 0xabcd; /* Need to find the significance of this later */
iph -> iph_offset = 0;
iph -> iph_ttl = 0x1;
iph -> iph_protocol = 0x2;
iph -> iph_xsum = 0x0;
iph -> iph_src = ctxtp -> cl_ip_addr;
iph -> iph_dest = ctxtp -> cl_igmp_addr;
/* Fill in the ethernet header */
dstp = ctxtp -> media_hdr_igmp . ethernet . dst . data;
srcp = ctxtp -> media_hdr_igmp . ethernet . src . data;
CVY_ETHERNET_ETYPE_SET (& ctxtp -> media_hdr_igmp . ethernet, MAIN_IP_SIG);
CVY_MAC_ADDR_COPY (ctxtp -> medium, dstp, & ctxtp -> cl_mac_addr);
CVY_MAC_ADDR_COPY (ctxtp -> medium, srcp, & ctxtp -> ded_mac_addr);
/* Fill in a MAIN_PACKET_INFO structure and calculate the IP checksum.
Note that we are filling in WAY more information than Tcpip_chksum
actually needs, but we do so not for correctness, but for completeness. */
{
MAIN_PACKET_INFO PacketInfo;
/* Fill in the packet info strucutre. */
PacketInfo.Medium = NdisMedium802_3;
PacketInfo.Length = sizeof(MAIN_IP_HEADER);
PacketInfo.Group = MAIN_FRAME_MULTICAST;
PacketInfo.Type = TCPIP_IP_SIG;
PacketInfo.Operation = MAIN_FILTER_OP_NONE;
/* Fill in the ethernet header information. */
PacketInfo.Ethernet.pHeader = &ctxtp->media_hdr_igmp.ethernet;
PacketInfo.Ethernet.Length = sizeof(CVY_ETHERNET_HDR);
/* Fill in the IP header information. */
PacketInfo.IP.pHeader = (PIP_HDR)iph;
PacketInfo.IP.Length = sizeof(MAIN_IP_HEADER);
PacketInfo.IP.Protocol = TCPIP_PROTOCOL_IGMP;
PacketInfo.IP.bFragment = FALSE;
/* Compute the checksum for the IP header */
checksum = Tcpip_chksum(&ctxtp->tcpip, &PacketInfo, TCPIP_PROTOCOL_IP);
IP_SET_CHKSUM((PIP_HDR)iph, (USHORT)checksum);
}
return TRUE;
} /* end Main_igmp_init */
VOID Main_idhb_init(
PMAIN_CTXT ctxtp
)
{
ULONG ulBodySize = 0, ulBodySize8 = 0; /* Size of identity heartbeat in bytes and 8-byte units respectively */
ULONG ulFqdnCB = 0; /* Number of bytes in fqdn, bounded by the size of the destination below */
ulFqdnCB = min(sizeof(ctxtp->idhb_msg.fqdn) - sizeof(WCHAR),
sizeof(WCHAR)*wcslen(ctxtp->params.hostname)
);
ulBodySize = sizeof(TLV_HEADER) + ulFqdnCB + sizeof(WCHAR); /* Include a NULL character whether or not there is an fqdn */
/* Round up to the nearest 8-byte boundary */
ulBodySize8 = (ulBodySize + 7)/8;
UNIV_ASSERT(ulBodySize8 <= WLBS_MAX_ID_HB_BODY_SIZE);
NdisZeroMemory(&(ctxtp->idhb_msg), sizeof(ctxtp->idhb_msg));
ctxtp->idhb_msg.header.type = MAIN_PING_EX_TYPE_IDENTITY;
ctxtp->idhb_msg.header.length8 = (UCHAR) ulBodySize8;
/* Copy the host name minus the null-terminator. We've initialized the destination to zero
so we don't need to overwrite that location. */
if (ulFqdnCB > 0)
{
NdisMoveMemory(&(ctxtp->idhb_msg.fqdn), &(ctxtp->params.hostname), ulFqdnCB);
}
ctxtp->idhb_size = 8*ulBodySize8;
return;
}
NDIS_STATUS Main_init (
PMAIN_CTXT ctxtp)
{
ULONG i, size;
NDIS_STATUS status;
PMAIN_FRAME_DSCR dscrp;
UNIV_ASSERT (ctxtp -> medium == NdisMedium802_3);
/* Re-set the reference count. */
ctxtp->ref_count = 0;
/* Re-set BDA teaming - this will be initialized at the bottom of this function. */
ctxtp->bda_teaming.active = FALSE;
if (sizeof (PING_MSG) + sizeof (MAIN_FRAME_HDR) > ctxtp -> max_frame_size)
{
UNIV_PRINT_CRIT(("Main_init: Ping message will not fit in the media frame %d > %d", sizeof (PING_MSG) + sizeof (MAIN_FRAME_HDR), ctxtp -> max_frame_size));
TRACE_CRIT("%!FUNC! Ping message will not fit in the media frame %d > %d", sizeof (PING_MSG) + sizeof (MAIN_FRAME_HDR), ctxtp -> max_frame_size);
LOG_MSG2 (MSG_ERROR_INTERNAL, MSG_NONE, sizeof (PING_MSG) + sizeof (MAIN_FRAME_HDR), ctxtp -> max_frame_size);
return NDIS_STATUS_FAILURE;
}
/* V2.0.6 initialize IP addresses - might be used in the Main_mac_addr_init
so have to do it here */
if (! Main_ip_addr_init (ctxtp))
{
ctxtp -> convoy_enabled = FALSE;
ctxtp -> params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_init: Error initializing IP addresses"));
TRACE_CRIT("%!FUNC! Error initializing IP addresses");
}
/* V1.3.1b parse cluster MAC address from parameters */
if (! Main_mac_addr_init (ctxtp))
{
ctxtp -> convoy_enabled = FALSE;
ctxtp -> params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_init: Error initializing cluster MAC address"));
TRACE_CRIT("%!FUNC! Error initializing cluster MAC address");
}
#if defined (NLB_TCP_NOTIFICATION)
/* Now that the cluster IP address is set, try to map this adapter to its IP interface index. */
Main_set_interface_index(NULL, ctxtp);
#endif
/* Initialize IGMP message if in igmp mode */
if (ctxtp -> params . mcast_support && ctxtp -> params . igmp_support)
{
if (! Main_igmp_init (ctxtp, TRUE))
{
ctxtp -> convoy_enabled = FALSE;
ctxtp -> params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_init: Error initializing IGMP message"));
TRACE_CRIT("%!FUNC! Error initializing IGMP message");
}
UNIV_PRINT_VERB(("Main_init: IGMP message initialized"));
TRACE_VERB("%!FUNC! IGMP message initialized");
}
/* can extract the send message pointer even if load is not inited yet V1.1.4 */
ctxtp -> load_msgp = Load_snd_msg_get (& ctxtp -> load);
/* Initialize identity cache */
NdisZeroMemory(&(ctxtp->identity_cache), sizeof(ctxtp->identity_cache));
ctxtp->idhb_size = 0;
Main_idhb_init(ctxtp);
/* initalize lists and locks */
NdisInitializeListHead (& ctxtp -> act_list);
NdisInitializeListHead (& ctxtp -> buf_list);
NdisInitializeListHead (& ctxtp -> frame_list);
NdisAllocateSpinLock (& ctxtp -> act_lock);
NdisAllocateSpinLock (& ctxtp -> buf_lock);
NdisAllocateSpinLock (& ctxtp -> recv_lock);
NdisAllocateSpinLock (& ctxtp -> send_lock);
NdisAllocateSpinLock (& ctxtp -> frame_lock);
NdisAllocateSpinLock (& ctxtp -> load_lock);
/* #ps# */
NdisInitializeNPagedLookasideList (& ctxtp -> resp_list, NULL, NULL, 0,
sizeof (MAIN_PROTOCOL_RESERVED),
UNIV_POOL_TAG, 0);
/* capture boot-time parameters */
ctxtp -> num_packets = ctxtp -> params . num_packets;
ctxtp -> num_actions = ctxtp -> params . num_actions;
ctxtp -> num_send_msgs = ctxtp -> params . num_send_msgs;
#if 0
/* ###### for tracking send filtering - ramkrish */
ctxtp -> sends_in = 0;
ctxtp -> sends_completed = 0;
ctxtp -> sends_filtered = 0;
ctxtp -> arps_filtered = 0;
ctxtp -> mac_modified = 0;
ctxtp -> uninited_return = 0;
#endif
ctxtp->cntr_recv_tcp_resets = 0;
ctxtp->cntr_xmit_tcp_resets = 0;
/* V1.1.1 - initalize other contexts */
Load_init (& ctxtp -> load, & ctxtp -> params);
UNIV_PRINT_VERB(("Main_init: Initialized load"));
TRACE_VERB("%!FUNC! Initialized load");
if (! Tcpip_init (& ctxtp -> tcpip, & ctxtp -> params))
{
UNIV_PRINT_CRIT(("Main_init: Error initializing tcpip layer"));
TRACE_CRIT("%!FUNC! Error initializing tcpip layer");
goto error;
}
UNIV_PRINT_VERB(("Main_init: Initialized tcpip"));
TRACE_VERB("%!FUNC! Initialized tcpip");
/* Check the last known host state and see whether or not we are supposed
to persist that state. If we are, then no problems; if not, then we
need to revert to the preferred initial host state and update the last
known host state in the registry. */
switch (ctxtp->params.init_state) {
case CVY_HOST_STATE_STARTED:
if (!(ctxtp->params.persisted_states & CVY_PERSIST_STATE_STARTED)) {
/* If the host state is already correct, don't bother to do anything. */
if (ctxtp->params.init_state == ctxtp->params.cluster_mode)
break;
/* Set the desired state - this is the "cached" value. */
ctxtp->cached_state = ctxtp->params.cluster_mode;
/* Update the intial state registry key appropriately. Because the adapter is not
yet "inited" (we're in the process), we cannot increment the reference count on
the context. Therefore, we cannot call Main_set_host_state, which would fire an
NDIS work item and increment the reference count on the context. Instead, call
the work item function directly to write to the registry (which is OK because
we're guaranteed to be running at PASSIVE_LEVEL here). However, in this case, we
do NOT want to decrement the reference count in Params_set_host_state because we
did not increment it here. By passing NULL for the NDIS work item pointer, we are
notifying this function that it was NOT called as the result of an NDIS work item
and therefore should NOT decrement the reference count before it exits.*/
Params_set_host_state(NULL, ctxtp);
break;
}
LOG_MSG(MSG_INFO_HOST_STATE_PERSIST_STARTED, MSG_NONE);
break;
case CVY_HOST_STATE_STOPPED:
if (!(ctxtp->params.persisted_states & CVY_PERSIST_STATE_STOPPED)) {
/* If the host state is already correct, don't bother to do anything. */
if (ctxtp->params.init_state == ctxtp->params.cluster_mode)
break;
/* Set the desired state - this is the "cached" value. */
ctxtp->cached_state = ctxtp->params.cluster_mode;
/* Update the intial state registry key appropriately. Because the adapter is not
yet "inited" (we're in the process), we cannot increment the reference count on
the context. Therefore, we cannot call Main_set_host_state, which would fire an
NDIS work item and increment the reference count on the context. Instead, call
the work item function directly to write to the registry (which is OK because
we're guaranteed to be running at PASSIVE_LEVEL here). However, in this case, we
do NOT want to decrement the reference count in Params_set_host_state because we
did not increment it here. By passing NULL for the NDIS work item pointer, we are
notifying this function that it was NOT called as the result of an NDIS work item
and therefore should NOT decrement the reference count before it exits.*/
Params_set_host_state(NULL, ctxtp);
break;
}
LOG_MSG(MSG_INFO_HOST_STATE_PERSIST_STOPPED, MSG_NONE);
break;
case CVY_HOST_STATE_SUSPENDED:
if (!(ctxtp->params.persisted_states & CVY_PERSIST_STATE_SUSPENDED)) {
/* If the host state is already correct, don't bother to do anything. */
if (ctxtp->params.init_state == ctxtp->params.cluster_mode)
break;
/* Set the desired state - this is the "cached" value. */
ctxtp->cached_state = ctxtp->params.cluster_mode;
/* Update the intial state registry key appropriately. Because the adapter is not
yet "inited" (we're in the process), we cannot increment the reference count on
the context. Therefore, we cannot call Main_set_host_state, which would fire an
NDIS work item and increment the reference count on the context. Instead, call
the work item function directly to write to the registry (which is OK because
we're guaranteed to be running at PASSIVE_LEVEL here). However, in this case, we
do NOT want to decrement the reference count in Params_set_host_state because we
did not increment it here. By passing NULL for the NDIS work item pointer, we are
notifying this function that it was NOT called as the result of an NDIS work item
and therefore should NOT decrement the reference count before it exits.*/
Params_set_host_state(NULL, ctxtp);
break;
}
LOG_MSG(MSG_INFO_HOST_STATE_PERSIST_SUSPENDED, MSG_NONE);
break;
default:
UNIV_PRINT_CRIT(("Main_init: Unknown host state: %u", ctxtp->params.init_state));
goto error;
}
/* If there have been no errors to this point, setup the host state accordingly. */
if (ctxtp->params_valid && ctxtp->convoy_enabled) {
/* If the initial state is started, then start the load module now. */
if (ctxtp->params.init_state == CVY_HOST_STATE_STARTED) {
UNIV_PRINT_VERB(("Main_init: Calling load_start"));
Load_start(&ctxtp->load);
/* If the initial state is suspended, set the suspended flag. */
} else if (ctxtp->params.init_state == CVY_HOST_STATE_SUSPENDED) {
ctxtp->suspended = TRUE;
}
}
/* allocate actions */
size = sizeof (MAIN_ACTION);
#ifdef _WIN64 // 64-bit -- ramkrish
ctxtp -> act_size = (size & 0x7) ? (size + 8 - (size & 0x7) ) : size;
#else
ctxtp -> act_size = size;
#endif
if (! Main_actions_alloc (ctxtp))
goto error;
/* V1.3.2b - allocate buffers */
ctxtp -> buf_mac_hdr_len = CVY_MAC_HDR_LEN (ctxtp -> medium);
ctxtp -> buf_size = sizeof (MAIN_BUFFER) + ctxtp -> buf_mac_hdr_len +
ctxtp -> max_frame_size - 1;
/* 64-bit -- ramkrish */
UNIV_PRINT_VERB(("Main_init: ctxtp -> buf_size = %d", ctxtp -> buf_size));
TRACE_VERB("%!FUNC! ctxtp -> buf_size = %d", ctxtp -> buf_size);
size = ctxtp -> buf_size;
#ifdef _WIN64
ctxtp -> buf_size = (size & 0x7) ? (size + 8 - (size & 0x7)) : size;
UNIV_PRINT_VERB(("Main_init: ctxtp -> buf_size = %d", ctxtp -> buf_size));
TRACE_VERB("%!FUNC! ctxtp -> buf_size = %d", ctxtp -> buf_size);
#else
ctxtp -> buf_size = size;
#endif
if (! Main_bufs_alloc (ctxtp))
goto error;
size = ctxtp -> num_packets;
/* V1.1.2 - allocate packet pools */
NdisAllocatePacketPool (& status, & (ctxtp -> send_pool_handle [0]),
ctxtp -> num_packets,
sizeof (MAIN_PROTOCOL_RESERVED));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating send packet pool %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating send packet pool %d 0x%x", size, status);
goto error;
}
ctxtp -> num_send_packet_allocs = 1;
ctxtp -> cur_send_packet_pool = 0;
ctxtp -> num_sends_alloced = ctxtp->num_packets;
NdisAllocatePacketPool (& status, & (ctxtp -> recv_pool_handle [0]),
ctxtp -> num_packets,
sizeof (MAIN_PROTOCOL_RESERVED));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating recv packet pool %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating recv packet pool %d 0x%x", size, status);
goto error;
}
ctxtp -> num_recv_packet_allocs = 1;
ctxtp -> cur_recv_packet_pool = 0;
ctxtp -> num_recvs_alloced = ctxtp->num_packets;
/* allocate support for heartbeat ping messages */
size = sizeof (MAIN_FRAME_DSCR) * ctxtp -> num_send_msgs;
status = NdisAllocateMemoryWithTag (& ctxtp -> frame_dscrp, size,
UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating frame descriptors %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating frame descriptors %d 0x%x", size, status);
goto error;
}
size = ctxtp -> num_send_msgs;
NdisAllocatePacketPool (& status, & ctxtp -> frame_pool_handle,
ctxtp -> num_send_msgs,
sizeof (MAIN_PROTOCOL_RESERVED));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating ping packet pool %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating ping packet pool %d 0x%x", size, status);
goto error;
}
size = 5 * ctxtp -> num_send_msgs;
NdisAllocateBufferPool (& status, & ctxtp -> frame_buf_pool_handle,
5 * ctxtp -> num_send_msgs);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating ping buffer pool %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating ping buffer pool %d 0x%x", size, status);
goto error;
}
for (i = 0; i < ctxtp -> num_send_msgs; i ++)
{
dscrp = & (ctxtp -> frame_dscrp [i]);
/* this buffer describes Ethernet MAC header */
size = sizeof (CVY_ETHERNET_HDR);
NdisAllocateBuffer (& status, & dscrp -> media_hdr_bufp,
ctxtp -> frame_buf_pool_handle,
& dscrp -> media_hdr . ethernet,
sizeof (CVY_ETHERNET_HDR));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating ethernet header buffer %d %x", i, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating ethernet header buffer %d 0x%x", i, status);
goto error;
}
dscrp -> recv_len = 0;
dscrp -> recv_len += sizeof (MAIN_FRAME_HDR) + sizeof (PING_MSG);
/* this buffer describes frame headers */
size = sizeof (MAIN_FRAME_HDR);
NdisAllocateBuffer (& status, & dscrp -> frame_hdr_bufp,
ctxtp -> frame_buf_pool_handle,
& dscrp -> frame_hdr,
sizeof (MAIN_FRAME_HDR));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating frame header buffer %d %x", i, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating frame header buffer %d 0x%x", i, status);
goto error;
}
/* this buffer describes receive ping message buffer V1.1.4 */
size = sizeof (PING_MSG);
NdisAllocateBuffer (& status, & dscrp -> recv_data_bufp,
ctxtp -> frame_buf_pool_handle,
& dscrp -> msg,
sizeof (PING_MSG));
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_init: Error allocating recv msg buffer %d %x", i, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating recv msg buffer %d 0x%x", i, status);
goto error;
}
dscrp -> send_data_bufp = NULL; /* Allocate this in Main_frame_get */
NdisInterlockedInsertTailList (& ctxtp -> frame_list,
& dscrp -> link,
& ctxtp -> frame_lock);
}
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Set the current state of BDA teaming on this adapter. This flag
is used for synchronization to BDA teaming on this adapter. */
ctxtp->bda_teaming.operation = BDA_TEAMING_OPERATION_NONE;
NdisReleaseSpinLock(&univ_bda_teaming_lock);
/* Reset the number of connections forcefully purged. */
ctxtp->num_purged = 0;
/* Turn reverse-hashing off. If we're part of a BDA team, the teaming configuration
that is setup by the call to Main_teaming_init will over-write this setting. */
ctxtp->reverse_hash = FALSE;
/* Initialize the bi-directional affinity teaming if it has been configured. */
if (!Main_teaming_init(ctxtp))
{
ctxtp->convoy_enabled = FALSE;
ctxtp->params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_init: Error initializing bi-directional affinity teaming"));
TRACE_CRIT("%!FUNC! Error initializing bi-directional affinity teaming");
}
else
{
UNIV_PRINT_VERB(("Main_init: Initialized bi-directional affinity teaming"));
TRACE_VERB("%!FUNC! Initialized bi-directional affinity teaming");
}
/* Initialize the DIP list structure. */
DipListInitialize(&ctxtp->dip_list);
return NDIS_STATUS_SUCCESS;
error:
Main_cleanup (ctxtp);
return NDIS_STATUS_FAILURE;
} /* end Main_init */
VOID Main_cleanup (
PMAIN_CTXT ctxtp)
{
ULONG i, j;
PMAIN_BUFFER bp;
/* V1.1.4 */
/* #ps# */
/* While using packet stacking, ensure that all packets are returned
* before clearing up the context
*/
/* Wait for all references on our context have elapsed. By now, the inited
flag is reset, which will prevent this reference count from increasing
while we sit here waiting for it to go to zero. */
while (Main_get_reference_count(ctxtp)) {
UNIV_PRINT_VERB(("Main_cleanup: Sleeping...\n"));
TRACE_VERB("%!FUNC! sleeping");
/* Sleep while there are references on our context.
These references come from pending IOCTLs. */
Nic_sleep(10);
}
NdisDeleteNPagedLookasideList (& ctxtp -> resp_list);
for (i = 0; i < CVY_MAX_ALLOCS; i ++)
{
if (ctxtp -> send_pool_handle [i] != NULL)
{
while (1)
{
if (NdisPacketPoolUsage (ctxtp -> send_pool_handle [i]) == 0)
break;
Nic_sleep (10); /* wait for 10 milliseconds for the packets to be returned */
}
NdisFreePacketPool (ctxtp -> send_pool_handle [i]);
ctxtp -> send_pool_handle [i] = NULL;
}
if (ctxtp -> recv_pool_handle [i] != NULL)
{
while (1)
{
if (NdisPacketPoolUsage (ctxtp -> recv_pool_handle [i]) == 0)
break;
Nic_sleep (10); /* wait for 10 milliseconds for the packets to be returned */
}
NdisFreePacketPool (ctxtp -> recv_pool_handle [i]);
ctxtp -> recv_pool_handle [i] = NULL;
}
if (ctxtp -> act_buf [i] != NULL)
NdisFreeMemory (ctxtp -> act_buf [i],
ctxtp -> num_actions * ctxtp -> act_size, 0);
/* V1.3.2b */
if (ctxtp -> buf_array [i] != NULL)
{
for (j = 0; j < ctxtp -> num_packets; j ++)
{
bp = (PMAIN_BUFFER) (ctxtp -> buf_array [i] + j * ctxtp -> buf_size);
if (bp -> full_bufp != NULL)
{
NdisAdjustBufferLength (bp -> full_bufp,
ctxtp -> buf_mac_hdr_len +
ctxtp -> max_frame_size);
NdisFreeBuffer (bp -> full_bufp);
}
if (bp -> frame_bufp != NULL)
{
NdisAdjustBufferLength (bp -> frame_bufp,
ctxtp -> max_frame_size);
NdisFreeBuffer (bp -> frame_bufp);
}
}
NdisFreeMemory (ctxtp -> buf_array [i],
ctxtp -> num_packets * ctxtp -> buf_size, 0);
}
if (ctxtp -> buf_pool_handle [i] != NULL)
NdisFreeBufferPool (ctxtp -> buf_pool_handle [i]);
}
if (ctxtp -> frame_dscrp != NULL)
{
for (i = 0; i < ctxtp -> num_send_msgs; i ++)
{
if (ctxtp -> frame_dscrp [i] . media_hdr_bufp != NULL)
NdisFreeBuffer (ctxtp -> frame_dscrp [i] . media_hdr_bufp);
if (ctxtp -> frame_dscrp [i] . frame_hdr_bufp != NULL)
NdisFreeBuffer (ctxtp -> frame_dscrp [i] . frame_hdr_bufp);
if (ctxtp -> frame_dscrp [i] . send_data_bufp != NULL)
NdisFreeBuffer (ctxtp -> frame_dscrp [i] . send_data_bufp);
if (ctxtp -> frame_dscrp [i] . recv_data_bufp != NULL)
NdisFreeBuffer (ctxtp -> frame_dscrp [i] . recv_data_bufp);
}
NdisFreeMemory (ctxtp -> frame_dscrp, sizeof (MAIN_FRAME_DSCR) *
ctxtp -> num_send_msgs, 0);
}
if (ctxtp -> frame_buf_pool_handle != NULL)
NdisFreeBufferPool (ctxtp -> frame_buf_pool_handle);
/* This packet pool is being used only for the heartbeat messages,
* so prefer not to check the packet pool usage.
*/
if (ctxtp -> frame_pool_handle != NULL)
NdisFreePacketPool (ctxtp -> frame_pool_handle);
NdisFreeSpinLock (& ctxtp -> act_lock);
NdisFreeSpinLock (& ctxtp -> buf_lock); /* V1.3.2b */
NdisFreeSpinLock (& ctxtp -> recv_lock);
NdisFreeSpinLock (& ctxtp -> send_lock);
NdisFreeSpinLock (& ctxtp -> frame_lock);
/* De-nitialize the DIP list structure. */
DipListDeinitialize(&ctxtp->dip_list);
/* Cleanup BDA teaming state. Note: this function will sleep under certain circumstances. */
Main_teaming_cleanup(ctxtp);
/* Stop the load module. If it is not currently active, this is a no-op. */
Load_stop(&ctxtp->load);
/* Cleanup the load module before we release our context.
DO NOT ACQUIRE THE LOAD LOCK BEFORE CALLING THIS FUNCTION. */
Load_cleanup(&ctxtp->load);
NdisFreeSpinLock (& ctxtp -> load_lock);
return;
} /* end Main_cleanup */
ULONG Main_arp_handle (
PMAIN_CTXT ctxtp,
PMAIN_PACKET_INFO pPacketInfo,
ULONG send
)
{
PUCHAR macp;
PARP_HDR arp_hdrp = pPacketInfo->ARP.pHeader;
/* V1.3.0b multicast support - ARP spoofing. use either this one or
code in Nic_request_complete that makes TCP/IP believe that station
current MAC address is the multicast one */
#if defined(TRACE_ARP)
DbgPrint ("(ARP) %s\n", send ? "send" : "recv");
DbgPrint (" MAC type = %x\n", ARP_GET_MAC_TYPE (arp_hdrp));
DbgPrint (" prot type = %x\n", ARP_GET_PROT_TYPE (arp_hdrp));
DbgPrint (" MAC length = %d\n", ARP_GET_MAC_LEN (arp_hdrp));
DbgPrint (" prot length = %d\n", ARP_GET_PROT_LEN (arp_hdrp));
DbgPrint (" message type = %d\n", ARP_GET_MSG_TYPE (arp_hdrp));
DbgPrint (" src MAC addr = %02x-%02x-%02x-%02x-%02x-%02x\n",
ARP_GET_SRC_MAC (arp_hdrp, 0),
ARP_GET_SRC_MAC (arp_hdrp, 1),
ARP_GET_SRC_MAC (arp_hdrp, 2),
ARP_GET_SRC_MAC (arp_hdrp, 3),
ARP_GET_SRC_MAC (arp_hdrp, 4),
ARP_GET_SRC_MAC (arp_hdrp, 5));
DbgPrint (" src prot addr = %u.%u.%u.%u\n",
ARP_GET_SRC_PROT (arp_hdrp, 0),
ARP_GET_SRC_PROT (arp_hdrp, 1),
ARP_GET_SRC_PROT (arp_hdrp, 2),
ARP_GET_SRC_PROT (arp_hdrp, 3));
DbgPrint (" dst MAC addr = %02x-%02x-%02x-%02x-%02x-%02x\n",
ARP_GET_DST_MAC (arp_hdrp, 0),
ARP_GET_DST_MAC (arp_hdrp, 1),
ARP_GET_DST_MAC (arp_hdrp, 2),
ARP_GET_DST_MAC (arp_hdrp, 3),
ARP_GET_DST_MAC (arp_hdrp, 4),
ARP_GET_DST_MAC (arp_hdrp, 5));
DbgPrint (" dst prot addr = %u.%u.%u.%u\n",
ARP_GET_DST_PROT (arp_hdrp, 0),
ARP_GET_DST_PROT (arp_hdrp, 1),
ARP_GET_DST_PROT (arp_hdrp, 2),
ARP_GET_DST_PROT (arp_hdrp, 3));
#endif
/* block sending out ARPs while we are changing IPs */
if (send && univ_changing_ip > 0)
{
/* if source IP is the one we are switching to - stop blocking ARPs */
if (ARP_GET_SRC_PROT_64(arp_hdrp) == ctxtp->cl_ip_addr) /* 64-bit -- ramkrish */
{
NdisAcquireSpinLock (& ctxtp -> load_lock);
univ_changing_ip = 0;
NdisReleaseSpinLock (& ctxtp -> load_lock);
UNIV_PRINT_VERB(("Main_arp_handle: IP address changed - stop blocking"));
}
else if (ARP_GET_SRC_PROT_64(arp_hdrp) != ctxtp -> ded_ip_addr) /* 64-bit -- ramkrish */
{
#if defined(TRACE_ARP)
DbgPrint ("blocked due to IP switching\n");
#endif
// ctxtp -> arps_filtered ++;
return FALSE;
}
}
if (ctxtp -> params . mcast_spoof &&
ctxtp -> params . mcast_support &&
ARP_GET_PROT_TYPE (arp_hdrp) == ARP_PROT_TYPE_IP &&
ARP_GET_PROT_LEN (arp_hdrp) == ARP_PROT_LEN_IP)
{
if (send)
{
/* if this is a cluster IP address and our dedicated MAC -
replace dedicated MAC with cluster MAC */
if (ARP_GET_SRC_PROT_64 (arp_hdrp) != ctxtp -> ded_ip_addr) /* 64-bit -- ramkrish */
{
macp = ARP_GET_SRC_MAC_PTR (arp_hdrp);
if (CVY_MAC_ADDR_COMP (ctxtp -> medium, macp, & ctxtp -> ded_mac_addr))
CVY_MAC_ADDR_COPY (ctxtp -> medium, macp, & ctxtp -> cl_mac_addr);
}
}
else
{
/* if this is a cluster IP address and our cluster MAC -
replace cluster MAC with dedicated MAC */
if (ARP_GET_SRC_PROT_64 (arp_hdrp) != ctxtp -> ded_ip_addr) /* 64-bit -- ramkrish */
{
macp = ARP_GET_SRC_MAC_PTR (arp_hdrp);
if (CVY_MAC_ADDR_COMP (ctxtp -> medium, macp, & ctxtp -> cl_mac_addr))
CVY_MAC_ADDR_COPY (ctxtp -> medium, macp, & ctxtp -> ded_mac_addr);
}
if (ARP_GET_DST_PROT_64 (arp_hdrp) != ctxtp -> ded_ip_addr) /* 64-bit -- ramkrish */
{
macp = ARP_GET_DST_MAC_PTR (arp_hdrp);
if (CVY_MAC_ADDR_COMP (ctxtp -> medium, macp, & ctxtp -> cl_mac_addr))
CVY_MAC_ADDR_COPY (ctxtp -> medium, macp, & ctxtp -> ded_mac_addr);
}
}
}
#if defined(TRACE_ARP)
DbgPrint ("---- spoofed to -----\n");
DbgPrint (" src MAC addr = %02x-%02x-%02x-%02x-%02x-%02x\n",
ARP_GET_SRC_MAC (arp_hdrp, 0),
ARP_GET_SRC_MAC (arp_hdrp, 1),
ARP_GET_SRC_MAC (arp_hdrp, 2),
ARP_GET_SRC_MAC (arp_hdrp, 3),
ARP_GET_SRC_MAC (arp_hdrp, 4),
ARP_GET_SRC_MAC (arp_hdrp, 5));
DbgPrint (" src prot addr = %u.%u.%u.%u\n",
ARP_GET_SRC_PROT (arp_hdrp, 0),
ARP_GET_SRC_PROT (arp_hdrp, 1),
ARP_GET_SRC_PROT (arp_hdrp, 2),
ARP_GET_SRC_PROT (arp_hdrp, 3));
DbgPrint (" dst MAC addr = %02x-%02x-%02x-%02x-%02x-%02x\n",
ARP_GET_DST_MAC (arp_hdrp, 0),
ARP_GET_DST_MAC (arp_hdrp, 1),
ARP_GET_DST_MAC (arp_hdrp, 2),
ARP_GET_DST_MAC (arp_hdrp, 3),
ARP_GET_DST_MAC (arp_hdrp, 4),
ARP_GET_DST_MAC (arp_hdrp, 5));
DbgPrint (" dst prot addr = %u.%u.%u.%u\n",
ARP_GET_DST_PROT (arp_hdrp, 0),
ARP_GET_DST_PROT (arp_hdrp, 1),
ARP_GET_DST_PROT (arp_hdrp, 2),
ARP_GET_DST_PROT (arp_hdrp, 3));
#endif
return TRUE;
} /* end Main_arp_handle */
/*
* Function: Main_parse_ipsec
* Description: This function parses UDP packets received on port 500/4500, which are IPSec
* control packets. This function attempts to recognize the start of an
* IPSec session - its virtual 'SYN' packet. IPSec sessions begin with an
* IKE key exchange which is an IKE Main Mode Security Association. This
* function parses the IKE header and payload to identify the Main Mode
* SAs, which NLB will treat like TCP SYNs - all other UDP 500/4500 and IPSec
* traffic is treated like TCP data packets. The problem is that NLB
* cannot necessarily tell the difference between a new Main Mode SA and
* a re-key of an existing Main Mode SA. Therefore, if the client does not
* support intitial contact notification, then every Main Mode SA will be
* considered a new session, which means that sessions can potentially
* break depending on how often Main Mode SAs are negotiated. However, if
* the client does support initial contact notification, then the only Main
* Mode SAs that will be reported as such are the initial ones (when no state
* currently exists between the client and the server), which allows NLB to
* distinguish the two types of Main Mode SAs, which should allow NLB to
* reliably keep IPSec sessions "sticky". IPSec notifies NLB through the
* connection notification APIs to tell NLB when IPSec sessions go up and down,
* allowing NLB to create and clean out descriptors for IPSec sessions.
* Parameters: pIKEPacket - a pointer to the IKE packet buffer (this is beyond the UDP header).
* ServerPort - the server UDP port on which the packet arrived.
* Returns: BOOLEAN - TRUE if the packet is a new IPSec session, FALSE if it is not.
* Author: shouse, 4.28.01
*/
NLB_IPSEC_PACKET_TYPE Main_parse_ipsec (PMAIN_PACKET_INFO pPacketInfo, ULONG ServerPort)
{
/* Pointer to the IKE header. */
PIPSEC_ISAKMP_HDR pISAKMPHeader = (PIPSEC_ISAKMP_HDR)pPacketInfo->IP.UDP.Payload.pPayload;
/* Pointer to the subsequent generic payloads in the IKE packet. */
PIPSEC_GENERIC_HDR pGenericHeader;
/* The length of memory contigously accessible from the IKE packet pointer. */
ULONG cUDPDataLength = pPacketInfo->IP.UDP.Payload.Length;
/* The NAT delimiter - should be zero if this is really an NAT'd IKE packet. */
UCHAR NATEncapsulatedIPSecDelimiter[IPSEC_ISAKMP_NAT_DELIMITER_LENGTH] = IPSEC_ISAKMP_NAT_DELIMITER;
/* The Microsoft client vendor ID - used to determine whether or not the client supports initial contact notification. */
UCHAR VIDMicrosoftClient[IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH] = IPSEC_VENDOR_ID_MICROSOFT;
/* The initial contact suport vendor ID - used to determine whether or not this client support initial contact notification. */
UCHAR VIDInitialContactSupport[IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH] = IPSEC_VENDOR_ID_INITIAL_CONTACT_SUPPORT;
/* The initial contact vendor ID - used to determine whether or not this is an initial contact MMSA. */
UCHAR VIDInitialContact[IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH] = IPSEC_VENDOR_ID_INITIAL_CONTACT;
/* Whether or not we've determined the client to be compatible. */
BOOLEAN bInitialContactEnabled = FALSE;
/* Whether or not this is indeed an initial contact. */
BOOLEAN bInitialContact = FALSE;
/* The length of the IKE packet. */
ULONG cISAKMPPacketLength;
/* The next payload code in the IKE payload chain. */
UCHAR NextPayload;
TRACE_PACKET("%!FUNC! Sniffing IKE header %p, len=%u", pISAKMPHeader, cUDPDataLength);
/* The UDP data should be at least as long as the initiator cookie. If the packet is
UDP encapsulated IPSec, then the I cookie will be 0 to indicate such. */
if (cUDPDataLength < IPSEC_ISAKMP_NAT_DELIMITER_LENGTH) {
TRACE_PACKET("%!FUNC! Malformed UDP data: UDP data length = %u", cUDPDataLength);
return NLB_IPSEC_OTHER;
}
/* If the UDP data length is non-zero, then the UDP payload pointer had BETTER be non-NULL. */
UNIV_ASSERT(pISAKMPHeader);
/* If this packet arrived on the IPSec NAT port (4500), it may or may not be IKE. Check
the delimiter at the first four bytes of the payload to see whether or not its IKE. If
the packet arrived on the IPSec control port (500), then this packet MUST be IKE. */
if (ServerPort == IPSEC_NAT_PORT) {
/* Need to check the NAT delimiter, which will distinguish clients behind a NAT,
which also send their IPSec (ESP) traffic to UDP port 4500. If the delimiter
is non-zero, then this is NOT an IKE packet, so we return OTHER. */
if (!NdisEqualMemory((PVOID)pISAKMPHeader, (PVOID)&NATEncapsulatedIPSecDelimiter[0], sizeof(UCHAR) * IPSEC_ISAKMP_NAT_DELIMITER_LENGTH)) {
TRACE_PACKET("%!FUNC! This packet is UDP encapsulated IPSec traffic, not an IKE packet");
return NLB_IPSEC_OTHER;
}
/* If this IS encapsulated IKE, then advance the IKISAKMP header pointer by the length of the
delimiter and adjust the UDP data length. */
pISAKMPHeader = (PIPSEC_ISAKMP_HDR)((PUCHAR)pISAKMPHeader + IPSEC_ISAKMP_NAT_DELIMITER_LENGTH);
cUDPDataLength -= IPSEC_ISAKMP_NAT_DELIMITER_LENGTH;
}
/* At this point, this packet should be IKE, so the UDP data should be at least
as long as an ISAKMP header. */
if (cUDPDataLength < IPSEC_ISAKMP_HEADER_LENGTH) {
TRACE_PACKET("%!FUNC! Malformed ISAKMP header: UDP data length = %u", cUDPDataLength);
return NLB_IPSEC_OTHER;
}
/* Get the total length of the IKE packet from the ISAKMP header. */
cISAKMPPacketLength = IPSEC_ISAKMP_GET_PACKET_LENGTH(pISAKMPHeader);
/* Sanity check - the UDP data length and IKE packet length SHOULD be the same, unless the packet
is fragmented. If it is, then we can only look into the packet as far as the UDP data length.
If that's not far enough for us to find what we need, then we might miss an initial contact
main mode SA; the consequence of which is that we might not accept this connection if we are
in non-optimized mode, because we'll treat this like data, which requires a descriptor lookup -
if this is an initial contact, chances are great that no descriptor will exist and all hosts
in the cluster will drop the packet. Or, we may end up deciding that this is an IPSec SYN
because we were not able to verify the vendor ID or notify payloads. In this case, the client
is basically treated like a legacy client. */
if (cUDPDataLength < cISAKMPPacketLength)
/* Only look as far as the end of the UDP packet. */
cISAKMPPacketLength = cUDPDataLength;
/* The IKE packet should be at least as long as an ISAKMP header (a whole lot longer, actually). */
if (cISAKMPPacketLength < IPSEC_ISAKMP_HEADER_LENGTH) {
TRACE_PACKET("%!FUNC! Malformed ISAKMP header: ISAKMP Packet length = %u", cISAKMPPacketLength);
return NLB_IPSEC_OTHER;
}
/* Get the first payload type out of the ISAKMP header. */
NextPayload = IPSEC_ISAKMP_GET_NEXT_PAYLOAD(pISAKMPHeader);
/* IKE security associations are identified by a payload type byte in the header.
Check that first - this does not ensure that this is what we are looking for
because this check will not exclude, for instance, main mode re-keys. */
if (NextPayload != IPSEC_ISAKMP_SA) {
/* If this is a NAT encapsulated IKE ID packet, we have to handle this specially, as we MAY
NOT yet have the correct descriptor to track this session. In most (*) cases of IPSec/L2TP
behind a NAT, the negotiation starts across UDP 500 and then switches to UDP 4500 on the
IKE ID packet. Because our state is tracking UDP 500, we might not correctly handle this
packet. Therefore, special case this packet and pass it up on all "appropriate" (**) hosts
in the cluster, at which point we will be notified by IPSec to change our session tracking
state to the UDP 4500 and the new ephemeral source port. From that point on, we can correctly
track the rest of the session.
(*) In some cases, the negotiation may start on UDP 4500, in which case there is no transition
in UDP ports, and NLB has the correct session tracking state from the get go.
(**) The appropriate hosts are (i) the current bucket owner, and (ii) any hosts that currently
have IPSec sessions with the same client IP address. Because we require single affinity for
IPSec/L2TP, normally this is just one host; if a change in cluster membership occurs, it can
be 2 hosts, but will rarely be more than that. */
if ((ServerPort == IPSEC_NAT_PORT) && (NextPayload == IPSEC_ISAKMP_ID)) {
TRACE_PACKET("%!FUNC! NAT encapsulated IKE ID: Payload=%u", NextPayload);
return NLB_IPSEC_IDENTIFICATION;
}
TRACE_PACKET("%!FUNC! Not a Main Mode Security Association: Payload=%u", NextPayload);
return NLB_IPSEC_OTHER;
}
/* Calculate a pointer to the fist generic payload, which is directly after the ISAKMP header. */
pGenericHeader = (PIPSEC_GENERIC_HDR)((PUCHAR)pISAKMPHeader + IPSEC_ISAKMP_HEADER_LENGTH);
/* Decrement the remaining packet length by the length of the ISAKMP header. */
cISAKMPPacketLength -= IPSEC_ISAKMP_HEADER_LENGTH;
/* We are looping through the generic payloads looking for the vendor ID and/or notify information. */
while (cISAKMPPacketLength > IPSEC_GENERIC_HEADER_LENGTH) {
/* Extract the payload length from the generic header. */
USHORT cPayloadLength = IPSEC_GENERIC_GET_PAYLOAD_LENGTH(pGenericHeader);
/* The payload length must be AT LEAST as long as a generic header. If its
not, then this packet may have been tampered with - bail out. */
if (cPayloadLength < IPSEC_GENERIC_HEADER_LENGTH) {
TRACE_PACKET("%!FUNC! Malformed generic header: Payload length = %d", cPayloadLength);
return NLB_IPSEC_OTHER;
}
/* If the length of the next payload is longer than the remaining buffer we have
available to read, then either (i) the packet is malformed, (ii) the rest of
the packet is in another NDIS_BUFFER attached to the packet, or (iii) the packet
was fragmented and we've gone as far as we can. If any of these is the case,
bail out now and make a decision based on the information we were able to gather
from the packet thusfar. */
if (cISAKMPPacketLength < cPayloadLength) {
TRACE_PACKET("%!FUNC! Missing some necessary IKE packet information: Assuming this is an IPSec SYN");
goto exit;
}
/* Not all clients are going to support this (in fact, only the Microsoft client
will support it, so we need to first see what the vendor ID of the client is.
if it is a Microsoft client that supports the initial contact vendor ID, then
we'll look for the initial contact, which provides better stickiness for IPSec
connections. If either the client is non-MS, or if it is not a version that
supports initial contact, then we can revert to the "second-best" solution,
which is to provide stickiness _between_ Main Mode SAs. This means that if a
client re-keys their Main Mode session, they _may_ be rebalanced to another
server. This is still better than the old UDP implementation, but the only
way to provide full session support for IPSec (without the distributed session
table nightmare) is to be able to distinguish initial Main Mode SAs from sub-
sequent Main Mode SAs (re-keys). */
if (NextPayload == IPSEC_ISAKMP_VENDOR_ID) {
PIPSEC_VENDOR_HDR pVendorHeader = (PIPSEC_VENDOR_HDR)pGenericHeader;
/* Make sure that the vendor ID payload is at least as long as a vendor ID. */
if (cPayloadLength < (IPSEC_GENERIC_HEADER_LENGTH + IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH)) {
TRACE_PACKET("%!FUNC! Malformed vendor ID header: Payload length = %d", cPayloadLength);
return NLB_IPSEC_OTHER;
}
/* Look for the Microsoft client vendor ID. If it is the right version, then we know that
the client is going to appropriately set the initial contact information, allowing NLB
to provide the best possible support for session stickiness. */
if (NdisEqualMemory((PVOID)IPSEC_VENDOR_ID_GET_ID_POINTER(pVendorHeader), (PVOID)&VIDMicrosoftClient[0], sizeof(UCHAR) * IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH)) {
/* Make sure that their is a version number attached to the Microsoft Vendor ID. Not
all vendor IDs have versions attached, but the Microsoft vendor ID should. */
if (cPayloadLength < (IPSEC_GENERIC_HEADER_LENGTH + IPSEC_VENDOR_ID_PAYLOAD_LENGTH)) {
TRACE_PACKET("%!FUNC! Unable to determine MS client version: Payload length = %d", cPayloadLength);
return NLB_IPSEC_OTHER;
}
if (IPSEC_VENDOR_ID_GET_VERSION(pVendorHeader) >= IPSEC_VENDOR_ID_MICROSOFT_MIN_VERSION) {
/* Microsoft clients whose version is greater than or equal to 4 will support
initial contact. Non-MS clients, or old MS clients will not, so they
receive decent, but not guaranteed sitckines, based solely on MM SAs. */
bInitialContactEnabled = TRUE;
}
}
/* Look for the initial contact supported vendor ID. If we find it, then we know that
the client is going to appropriately set the initial contact information, allowing NLB
to provide the best possible support for session stickiness. */
else if (NdisEqualMemory((PVOID)IPSEC_VENDOR_ID_GET_ID_POINTER(pVendorHeader), (PVOID)&VIDInitialContactSupport[0], sizeof(UCHAR) * IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH)) {
/* This client supports initial contact, which tells NLB that the inclusion or exclusion
of initial contact information is meaningful. Those clients that do not support initial
contact will receive decent, but not guaranteed sitckines, based solely on MM SAs. */
bInitialContactEnabled = TRUE;
}
/* Look for the initial contact vendor ID. If the initial contact vendor ID is present,
then this MMSA is a SYN-equivalent. For backward-compatability reasons, we will also
support this notification through the ISAKMP_NOTIFY payload, although it violates an
RFC, as the INITIAL_CONTACT in the ISAKMP_NOTIFY payload MUST be secured by a security
association and can therefore NOT be presented in the Security Association packet. */
else if (NdisEqualMemory((PVOID)IPSEC_VENDOR_ID_GET_ID_POINTER(pVendorHeader), (PVOID)&VIDInitialContact[0], sizeof(UCHAR) * IPSEC_VENDOR_HEADER_VENDOR_ID_LENGTH)) {
/* This is an initial contact notification from the client, which means that this is
the first time that the client has contacted this server; more precisely, the client
currently has no state associated with this peer. NLB will "re-balance" on initial
contact notifications, but not other Main Mode key exchanges as long as it can
determine that the client will comply with initial contact notification. */
bInitialContact = TRUE;
}
/* Using the ISAKMP_NOTIFY payload to relay the INITIAL_CONTACT information CANNOT be done in
the Security Association packet, as it violates an RFC (this information MUST be secured by
a security association and can therefore NOT be transported in the first packet of a negotiation.
We will continue to support it for legacy reasons, but note that Version 4 Microsoft clients
will NOT use this method, but rather will insert an INITIAL_CONTACT vendor ID to notify NLB
that this is an INITIAL_CONTACT Main Mode Security Association. */
} else if (NextPayload == IPSEC_ISAKMP_NOTIFY) {
PIPSEC_NOTIFY_HDR pNotifyHeader = (PIPSEC_NOTIFY_HDR)pGenericHeader;
/* Make sure that the notify payload is the correct length. */
if (cPayloadLength < (IPSEC_GENERIC_HEADER_LENGTH + IPSEC_NOTIFY_PAYLOAD_LENGTH)) {
TRACE_PACKET("%!FUNC! Malformed notify header: Payload length = %d", cPayloadLength);
return NLB_IPSEC_OTHER;
}
if (IPSEC_NOTIFY_GET_NOTIFY_MESSAGE(pNotifyHeader) == IPSEC_NOTIFY_INITIAL_CONTACT) {
/* This is an initial contact notification from the client, which means that this is
the first time that the client has contacted this server; more precisely, the client
currently has no state associated with this peer. NLB will "re-balance" on initial
contact notifications, but not other Main Mode key exchanges as long as it can
determine that the client will comply with initial contact notification. */
bInitialContact = TRUE;
}
}
/* Get the next payload type out of the generic header. */
NextPayload = IPSEC_GENERIC_GET_NEXT_PAYLOAD(pGenericHeader);
/* Calculate a pointer to the next generic payload. */
pGenericHeader = (PIPSEC_GENERIC_HDR)((PUCHAR)pGenericHeader + cPayloadLength);
/* Decrement the remaining packet length by the length of this payload. */
cISAKMPPacketLength -= cPayloadLength;
}
exit:
/* If the vendor ID / Notify did not indicate that this client supports initial contact notification,
then return INITIAL_CONTACT, and we go with the less-than-optimal solution of treating Main Mode
SAs as the connection boundaries, which potentially breaks sessions on MM SA re-keys. */
if (!bInitialContactEnabled) {
TRACE_PACKET("%!FUNC! This client does not support initial contact notifications.");
return NLB_IPSEC_INITIAL_CONTACT;
}
/* If this was a Main Mode SA from a client that supports initial contact, but did not specify
the initial contact vendor ID / Notify, then this is a re-key for an existing session. */
if (!bInitialContact) {
TRACE_PACKET("%!FUNC! Not an initial contact Main Mode Security Association.");
return NLB_IPSEC_OTHER;
}
TRACE_PACKET("%!FUNC! Found an initial contact Main Mode Security Association.");
return NLB_IPSEC_INITIAL_CONTACT;
}
/*
* Function: Main_ip_send_filter
* Description: This function filters outgoing IP traffic, often by querying the load
* module for load-balancing decisions. Packets addressed to the dedicated
* address are always allowed to pass, as are protocols that are not
* specifically filtered by NLB. Generally, all outgoing traffic is allowed
* to pass.
* Parameters: ctxtp - a pointer to the NLB main context structure for this adapter.
* pPacketInfo - a pointer to the MAIN_PACKET_INFO structure parsed by Main_send_frame_parse
* which contains pointers to the IP and TCP/UDP headers.
#if defined (NLB_HOOK_ENABLE)
* filter - the filtering directive returned by the filtering hook, if registered.
#endif
* Returns: BOOLEAN - if TRUE, accept the packet, otherwise, reject it.
* Author: kyrilf, shouse 3.4.02
* Notes:
*/
BOOLEAN Main_ip_send_filter (
PMAIN_CTXT ctxtp,
#if defined (NLB_HOOK_ENABLE)
PMAIN_PACKET_INFO pPacketInfo,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
PMAIN_PACKET_INFO pPacketInfo
#endif
)
{
PIP_HDR ip_hdrp = NULL;
PUDP_HDR udp_hdrp = NULL;
PTCP_HDR tcp_hdrp = NULL;
BOOLEAN acpt = TRUE; // Whether or not to accept the packet.
ULONG svr_port; // Port for this host.
ULONG svr_addr; // IP address for this host.
ULONG clt_port; // Port for destination client.
ULONG clt_addr; // IP address for destination client.
ULONG flags; // TCP flags.
ULONG Protocol; // Protocol derived from IP header.
TRACE_PACKET("%!FUNC! Enter: ctxtp = %p", ctxtp);
#if defined (NLB_HOOK_ENABLE)
/* These cases should be taken care of outside of this function. */
UNIV_ASSERT(filter != NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY);
UNIV_ASSERT(filter != NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY);
#endif
ip_hdrp = pPacketInfo->IP.pHeader;
TRACE_PACKET("%!FUNC! IP source address = %u.%u.%u.%u, IP destination address = %u.%u.%u.%u, Protocol = %u\n",
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
IP_GET_DST_ADDR (ip_hdrp, 0),
IP_GET_DST_ADDR (ip_hdrp, 1),
IP_GET_DST_ADDR (ip_hdrp, 2),
IP_GET_DST_ADDR (ip_hdrp, 3),
IP_GET_PROT (ip_hdrp));
if (((IP_GET_FRAG_FLGS(ip_hdrp) & 0x1) != 0) || (IP_GET_FRAG_OFF(ip_hdrp) != 0)) {
TRACE_PACKET("%!FUNC! Fragmented datagram, ID = %u, flags = 0x%x, offset = %u",
IP_GET_FRAG_ID(ip_hdrp),
IP_GET_FRAG_FLGS(ip_hdrp),
IP_GET_FRAG_OFF(ip_hdrp));
}
if (pPacketInfo->IP.bFragment) {
TRACE_FILTER("%!FUNC! Accept packet - allow fragmented packets to pass");
/* Always let fragmented packets go out. */
acpt = TRUE;
goto exit;
}
/* Server address is the source IP and client address is the destination IP. */
svr_addr = IP_GET_SRC_ADDR_64 (ip_hdrp);
clt_addr = IP_GET_DST_ADDR_64 (ip_hdrp);
/* Get the IP protocol form the IP header. */
Protocol = pPacketInfo->IP.Protocol;
/* Packets directed to the dedicated IP address are always passed through. If the
cluster IP address hasn't been set (parameter error), then fall into a pass-
through mode and pass all traffic up to the upper-layer protocols. */
if (svr_addr == ctxtp -> ded_ip_addr || ctxtp -> cl_ip_addr == 0)
{
TRACE_FILTER("%!FUNC! Accept packet - allow packets directed to the DIP to pass (or we're in passthru mode)");
acpt = TRUE;
goto exit;
}
switch (Protocol)
{
case TCPIP_PROTOCOL_TCP:
tcp_hdrp = pPacketInfo->IP.TCP.pHeader;
TRACE_PACKET("%!FUNC! TCP Source port = %u, Destination port = %u, Sequence number = %u, ACK number = %u, Flags = 0x%x",
TCP_GET_SRC_PORT (tcp_hdrp),
TCP_GET_DST_PORT (tcp_hdrp),
TCP_GET_SEQ_NO (tcp_hdrp),
TCP_GET_ACK_NO (tcp_hdrp),
TCP_GET_FLAGS (tcp_hdrp));
svr_port = TCP_GET_SRC_PORT (tcp_hdrp);
clt_port = TCP_GET_DST_PORT (tcp_hdrp);
UNIV_ASSERT(!pPacketInfo->IP.bFragment);
/* Apply filtering algorithm. process connection boundaries different from regular packets. */
/* Get the TCP flags to find out the packet type. */
flags = TCP_GET_FLAGS (tcp_hdrp);
if (flags & TCP_FLAG_SYN)
{
TRACE_PACKET("%!FUNC! Outgoing SYN");
TRACE_FILTER("%!FUNC! Accept packet - TCP SYNs always permitted to pass");
}
else if (flags & TCP_FLAG_FIN)
{
TRACE_PACKET("%!FUNC! Outgoing FIN");
TRACE_FILTER("%!FUNC! Accept packet - TCP FINs always permitted to pass");
}
else if (flags & TCP_FLAG_RST)
{
TRACE_PACKET("%!FUNC! Outgoing RST");
#if defined (NLB_TCP_NOTIFICATION)
/* If TCP notifications are NOT turned on, then we need to pay attention to outgoing
RSTs and destroy our connection state. If notifications ARE on, then there is no
need; allow the packet to pass and clean up when TCP tells us to. */
if (!NLB_NOTIFICATIONS_ON())
{
#endif
/* In the case of an outgoing RST, we always want to allow the packet to pass,
so we ignore the return value, which is the response from the load module. */
#if defined (NLB_HOOK_ENABLE)
Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_RESET, filter);
#else
Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_RESET);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
/* Count the number of outgoing resets we've seen. */
if (acpt) ctxtp->cntr_xmit_tcp_resets++;
}
else
{
TRACE_PACKET("%!FUNC! Outgoing data");
TRACE_FILTER("%!FUNC! Accept packet - TCP data always permitted to pass");
}
break;
case TCPIP_PROTOCOL_UDP:
udp_hdrp = pPacketInfo->IP.UDP.pHeader;
TRACE_PACKET("%!FUNC! UDP Source port = %u, Destinoation port = %u",
UDP_GET_SRC_PORT (udp_hdrp),
UDP_GET_DST_PORT (udp_hdrp));
TRACE_FILTER("%!FUNC! Accept packet - UDP traffic always allowed to pass");
break;
case TCPIP_PROTOCOL_GRE:
TRACE_FILTER("%!FUNC! Accept packet - GRE traffic always allowed to pass");
/* PPTP packets are treated like TCP data, which are always allowed to pass. */
break;
case TCPIP_PROTOCOL_IPSEC1:
case TCPIP_PROTOCOL_IPSEC2:
TRACE_FILTER("%!FUNC! Accept packet - IPSec traffic always allowed to pass");
/* IPSec packets are treated kind of like TCP data, which are always allowed to pass. */
break;
case TCPIP_PROTOCOL_ICMP:
TRACE_FILTER("%!FUNC! Accept packet - ICMP traffic always allowed to pass");
/* Allow all outgoing ICMP to pass; incoming ICMP may be filtered, however. */
break;
default:
TRACE_FILTER("%!FUNC! Accept packet - Unknown protocol traffic always allowed to pass");
/* Allow other protocols to go out on all hosts. */
break;
}
exit:
TRACE_PACKET("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_get_full_payload
* Description:
* Parameters: ctxtp - a pointer to the NLB main context structure for this adapter.
* pBuffer - a pointer to the NDIS buffer in which the beginning of the payload is located.
* BufferLength - the length of the payload (in bytes) contained in pBuffer.
* pPayload - a pointer to the payload, which resides in pBuffer.
* ppMem - a pointer to a PUCHAR to hold the address of the new buffer, if one is allocated.
* pMemLength - a pointer to a ULONG to hold the length of the memory buffer allocated.
* Returns: BOOLEAN - if TRUE, the full payload is present; if FALSE, it is not
* Author: kyrilf, shouse 4.20.02
* Notes: If ppMem is non-NULL upon exiting this function, the caller is responsible for freeing that memory.
*/
BOOLEAN Main_get_full_payload (
PMAIN_CTXT ctxtp,
PNDIS_BUFFER pBuffer,
ULONG BufferLength,
PUCHAR pPayload,
OUT PUCHAR * ppMem,
OUT PULONG pMemLength)
{
PNDIS_BUFFER pNDISBuffer = pBuffer;
ULONG AllocateLength = BufferLength;
NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
PUCHAR pAllocate = NULL;
PUCHAR pVMem = NULL;
ULONG Length = 0;
ULONG Copied = 0;
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Initialize the OUT params. */
*ppMem = NULL;
*pMemLength = 0;
/* If we don't have enough information to retrieve the entire packet payload,
then simply return failure. */
if ((pBuffer == NULL) || (pPayload == NULL) || (BufferLength == 0))
return FALSE;
while (pNDISBuffer != NULL)
{
/* Get the next buffer in the chain. */
NdisGetNextBuffer(pNDISBuffer, &pNDISBuffer);
/* If there are no buffers left, bail out. */
if (pNDISBuffer == NULL) break;
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(pNDISBuffer, &pVMem, &Length, NormalPagePriority);
/* If querying the buffer fails, resources are low, bail out. */
if (pVMem == NULL) return FALSE;
/* Remember how many bytes we've encountered thusfar. */
AllocateLength += Length;
}
/* If the buffer we were passed in already contains everything there
is to be had, then return TRUE to indicate that the entire payload
has successfully been extracted from the NDIS buffer chain. */
if (AllocateLength == BufferLength)
return TRUE;
/* Allocate the new buffer. */
Status = NdisAllocateMemoryWithTag(&pAllocate, AllocateLength, UNIV_POOL_TAG);
/* If we could not successfully allocate the memory to hold the payload, bail out. */
if (Status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_get_full_payload: Could not allocate memory to hold entire payload, status=0x%08x", Status));
TRACE_CRIT("%!FUNC! Could not allocate memory to hold entire payload, status=0x%08x", Status);
return FALSE;
}
/* Re-set the NDIS_BUFFER pointer to the payload buffer. */
pNDISBuffer = pBuffer;
/* Re-set pVMem, which is the source for copying, to the payload pointer
and the length to the length of the payload buffer. */
pVMem = pPayload;
Length = BufferLength;
while (pNDISBuffer != NULL) {
/* Copy this chunk of NDIS_BUFFER into the new buffer. */
RtlCopyMemory(pAllocate + Copied, pVMem, Length);
/* Remember how many bytes we've copied in to the new buffer so far. */
Copied += Length;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(pNDISBuffer, &pNDISBuffer);
/* If there are no buffers left, bail out. */
if (pNDISBuffer == NULL) break;
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(pNDISBuffer, &pVMem, &Length, NormalPagePriority);
/* If querying the buffer fails, resources are low, bail out. */
if (pVMem == NULL) break;
}
/* We have succeeded at copying all payload bytes into the new buffer. */
if (Copied == AllocateLength)
{
/* Copy the new buffer pointer and its length into the OUT params.
The caller of this function is responsible for freeing this memory. */
*ppMem = pAllocate;
*pMemLength = AllocateLength;
return TRUE;
}
/* Failure to copy all bytes necessary. */
else
{
UNIV_PRINT_CRIT(("Main_get_full_payload: Unable to copy entire payload"));
TRACE_CRIT("%!FUNC! Unable to copy entire payload");
/* Free the buffer memory. */
NdisFreeMemory(pAllocate, AllocateLength, 0);
return FALSE;
}
}
/*
* Function: Main_ip_recv_filter
* Description: This function filters incoming IP traffic, often by querying the load
* module for load-balancing decisions. Packets addressed to the dedicated
* address are always allowed to pass, as are protocols that are not
* specifically filtered by NLB.
* Parameters: ctxtp - a pointer to the NLB main context structure for this adapter.
* pPacketInfo - a pointer to the MAIN_PACKET_INFO structure parsed by Main_recv_frame_parse
* which contains pointers to the IP and TCP/UDP headers.
#if defined (NLB_HOOK_ENABLE)
* filter - the filtering directive returned by the filtering hook, if registered.
#endif
* Returns: BOOLEAN - if TRUE, accept the packet, otherwise, reject it.
* Author: kyrilf, shouse 3.4.02
* Notes:
*/
BOOLEAN Main_ip_recv_filter(
PMAIN_CTXT ctxtp,
#if defined (NLB_HOOK_ENABLE)
PMAIN_PACKET_INFO pPacketInfo,
NLB_FILTER_HOOK_DIRECTIVE filter
#else
PMAIN_PACKET_INFO pPacketInfo
#endif
)
{
PIP_HDR ip_hdrp = NULL;
PUDP_HDR udp_hdrp = NULL;
PTCP_HDR tcp_hdrp = NULL;
BOOLEAN acpt = TRUE; // Whether or not to accept the packet.
ULONG svr_port; // Port for this host.
ULONG svr_addr; // IP address for this host.
ULONG clt_port; // Port for destination client.
ULONG clt_addr; // IP address for destination client.
ULONG flags; // TCP flags.
#if defined (OPTIMIZE_FRAGMENTS)
BOOLEAN fragmented = FALSE;
#endif
ULONG Protocol; // Protocol derived from IP header.
TRACE_PACKET("%!FUNC! Enter: ctxtp = %p", ctxtp);
#if defined (NLB_HOOK_ENABLE)
/* These cases should be taken care of outside of this function. */
UNIV_ASSERT(filter != NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY);
UNIV_ASSERT(filter != NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY);
#endif
ip_hdrp = pPacketInfo->IP.pHeader;
TRACE_PACKET("%!FUNC! IP source address = %u.%u.%u.%u, IP destination address = %u.%u.%u.%u, Protocol = %u\n",
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
IP_GET_DST_ADDR (ip_hdrp, 0),
IP_GET_DST_ADDR (ip_hdrp, 1),
IP_GET_DST_ADDR (ip_hdrp, 2),
IP_GET_DST_ADDR (ip_hdrp, 3),
IP_GET_PROT (ip_hdrp));
if (((IP_GET_FRAG_FLGS(ip_hdrp) & 0x1) != 0) || (IP_GET_FRAG_OFF(ip_hdrp) != 0)) {
TRACE_PACKET("%!FUNC! Fragmented datagram, ID = %u, flags = 0x%x, offset = %u",
IP_GET_FRAG_ID(ip_hdrp),
IP_GET_FRAG_FLGS(ip_hdrp),
IP_GET_FRAG_OFF(ip_hdrp));
}
#if 0 /* Disable fragment flooding. */
if (pPacketInfo->IP.bFragment)
{
#if defined (OPTIMIZE_FRAGMENTS)
/* In optimized-fragment mode; If we have no rules, or a single rule that will
not look at anything or only source IP address (the only exception to this
is multiple handling mode with no affinity that also uses source port for
its decision making), then we can just rely on normal mechanism to handle
every fragmented packet, since the algorithm will not attempt to look past
the IP header.
For multiple rules, or single rule with no affinity, apply algorithm only
to the first packet that has UDP/TCP header and then let fragmented packets
up on all of the systems. TCP will then do the right thing and throw away
the fragments on all of the systems other than the one that handled the first
fragment.
If port rules will not let us handle IP fragments reliably, let TCP filter
them out based on sequence numbers. */
if (! ctxtp -> optimized_frags)
{
TRACE_FILTER("%!FUNC! Accept packet - allow fragmented packets to pass");
acpt = TRUE;
goto exit;
}
fragmented = TRUE;
#else
TRACE_FILTER("%!FUNC! Accept packet - allow fragmented packets to pass");
acpt = TRUE;
goto exit;
#endif
}
#endif /* Disable fragment flooding. */
/* Server address is the destination IP and client address is the source IP. */
svr_addr = IP_GET_DST_ADDR_64(ip_hdrp);
clt_addr = IP_GET_SRC_ADDR_64(ip_hdrp);
/* Get the protocol ID from the IP header. */
Protocol = pPacketInfo->IP.Protocol;
/* Packets directed to the dedicated IP address are always passed through. If the
cluster IP address hasn't been set (parameter error), then fall into a pass-
through mode and pass all traffic up to the upper-layer protocols. */
if (svr_addr == ctxtp -> ded_ip_addr || ctxtp -> cl_ip_addr == 0 ||
svr_addr == ctxtp -> ded_bcast_addr || svr_addr == ctxtp -> cl_bcast_addr)
{
TRACE_FILTER("%!FUNC! Accept packet - allow packets directed to the DIP to pass (or we're in passthru mode)");
acpt = TRUE;
goto exit;
}
/* Before we load-balance this packet, check to see whether or not its destined for
the dedicated IP address of another NLB host in our cluster. If it is, drop it. */
if (DipListCheckItem(&ctxtp->dip_list, svr_addr))
{
TRACE_FILTER("%!FUNC! Drop packet - packet is destined for the DIP of another cluster host");
acpt = FALSE;
goto exit;
}
/* If the load module is stopped, drop most packets. */
if (! ctxtp -> convoy_enabled)
{
/* Drop TCP, UDP, GRE and IPSEC immediately. Other protocols will be allowed to pass. */
if (Protocol == TCPIP_PROTOCOL_TCP ||
Protocol == TCPIP_PROTOCOL_UDP ||
Protocol == TCPIP_PROTOCOL_GRE ||
Protocol == TCPIP_PROTOCOL_IPSEC1 ||
Protocol == TCPIP_PROTOCOL_IPSEC2)
{
TRACE_FILTER("%!FUNC! Drop packet - block non-remote control traffic when NLB is stopped");
acpt = FALSE;
goto exit;
}
}
switch (Protocol)
{
case TCPIP_PROTOCOL_TCP:
/* If we have a TCP subsequent fragment, treat it as a TCP data packet with Source Port = 80,
Destination Port = 80. It will be accepted if this host has the hash bucket or if it has a
matching connection descriptor.
This is really a best-effort implementation. It is quite possible that we may be indicating the
packet up on the wrong host if the actual port tuple is not 80/80. We could very well have
chosen 0/0 as the "assumed" port tuple. Instead, we chose 80/80 since NLB is most commonly
used for web traffic.
*/
if (pPacketInfo->IP.bFragment)
{
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, TCP_HTTP_PORT, clt_addr, TCP_HTTP_PORT, TCPIP_PROTOCOL_TCP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, TCP_HTTP_PORT, clt_addr, TCP_HTTP_PORT, TCPIP_PROTOCOL_TCP);
#endif
break;
}
tcp_hdrp = pPacketInfo->IP.TCP.pHeader;
#if defined (OPTIMIZE_FRAGMENTS)
if (! fragmented)
{
#endif
TRACE_PACKET("%!FUNC! TCP Source port = %u, Destination port = %u, Sequence number = %u, ACK number = %u, Flags = 0x%x",
TCP_GET_SRC_PORT (tcp_hdrp),
TCP_GET_DST_PORT (tcp_hdrp),
TCP_GET_SEQ_NO (tcp_hdrp),
TCP_GET_ACK_NO (tcp_hdrp),
TCP_GET_FLAGS (tcp_hdrp));
clt_port = TCP_GET_SRC_PORT (tcp_hdrp);
svr_port = TCP_GET_DST_PORT (tcp_hdrp);
flags = TCP_GET_FLAGS (tcp_hdrp);
#if defined (OPTIMIZE_FRAGMENTS)
}
else
{
clt_port = 0;
svr_port = 0;
flags = 0;
}
#endif
/* Apply filtering algorithm. Process connection boundaries different from regular packets. */
if (flags & TCP_FLAG_SYN)
{
TRACE_PACKET("%!FUNC! Incoming SYN");
/* Make sure the SYN/FIN/RST flags in the TCP header are mutually exclusive. */
if ((flags & ~TCP_FLAG_SYN) & (TCP_FLAG_FIN | TCP_FLAG_RST))
{
TRACE_FILTER("%!FUNC! Drop packet - Invalid TCP flags (0x%x)", flags);
acpt = FALSE;
goto exit;
}
#if defined (NLB_TCP_NOTIFICATION)
/* If this is a "pure" SYN packet incoming (no ACK flag set), then treat this as a new connection
and call Main_conn_advise. If TCP connection notification is NOT turned on, then we treat
incoming SYN+ACKs the same as SYNs, so call Main_conn_advise in this case as well. */
if (!(flags & TCP_FLAG_ACK) || !NLB_NOTIFICATIONS_ON())
{
#endif
#if defined (NLB_HOOK_ENABLE)
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_UP, filter);
#else
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_UP);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
/* Otherwise, a SYN+ACK corresponds to an outgoing TCP connection from the server that is being
established. In that case, with TCP notification turned on, we should have state for this
connection in our pending queue; look it up. */
else
{
acpt = Main_pending_check(svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP);
}
#endif
}
else if (flags & TCP_FLAG_FIN)
{
TRACE_PACKET("%!FUNC! Incoming FIN");
/* Make sure the SYN/FIN/RST flags in the TCP header are mutually exclusive. */
if ((flags & ~TCP_FLAG_FIN) & (TCP_FLAG_SYN | TCP_FLAG_RST))
{
TRACE_FILTER("%!FUNC! Drop packet - Invalid TCP flags (0x%x)", flags);
acpt = FALSE;
goto exit;
}
#if defined (NLB_TCP_NOTIFICATION)
/* If TCP notification is turned on, then we should treat FINs and RSTs as data packets and not
as some special control packet. Call Main_packet_check which will accept the packet if either
we own the packet unconditionally, or if we find the corresponding state for this connection. */
if (NLB_NOTIFICATIONS_ON())
{
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP);
#endif
}
/* Otherwise, if we're not using TCP notification, then treat this FIN/RST differently and use it
to notify the load module to remove the state for this connection. */
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_DOWN, filter);
#else
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_DOWN);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
}
else if (flags & TCP_FLAG_RST)
{
TRACE_PACKET("%!FUNC! Incoming RST");
/* Make sure the SYN/FIN/RST flags in the TCP header are mutually exclusive. */
if ((flags & ~TCP_FLAG_RST) & (TCP_FLAG_FIN | TCP_FLAG_SYN))
{
TRACE_FILTER("%!FUNC! Drop packet - Invalid TCP flags (0x%x)", flags);
acpt = FALSE;
goto exit;
}
#if defined (NLB_TCP_NOTIFICATION)
/* If TCP notification is turned on, then we should treat FINs and RSTs as data packets and not
as some special control packet. Call Main_packet_check which will accept the packet if either
we own the packet unconditionally, or if we find the corresponding state for this connection. */
if (NLB_NOTIFICATIONS_ON())
{
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP);
#endif
}
/* Otherwise, if we're not using TCP notification, then treat this FIN/RST differently and use it
to notify the load module to remove the state for this connection. */
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_RESET, filter);
#else
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, CVY_CONN_RESET);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
/* Count the number of inbound resets we've seen. */
if (acpt) ctxtp->cntr_recv_tcp_resets++;
}
else
{
TRACE_PACKET("%!FUNC! Incoming data");
UNIV_ASSERT(! (flags & (TCP_FLAG_SYN | TCP_FLAG_FIN | TCP_FLAG_RST)));
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_TCP);
#endif
}
break;
case TCPIP_PROTOCOL_UDP:
/* If we have a UDP subsequent fragment, treat it as a data packet within an IPSEC
tunnel. It will be accepted if this host has the hash bucket or if it has a
matching virtual descriptor. Note that UDP subsequent fragments for other
protocols will also be accepted if this host has the hash bucket. */
if (pPacketInfo->IP.bFragment)
{
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC_UDP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC_UDP);
#endif
break;
}
udp_hdrp = pPacketInfo->IP.UDP.pHeader;
#if defined (OPTIMIZE_FRAGMENTS)
if (! fragmented)
{
#endif
TRACE_PACKET("%!FUNC! UDP Source port = %u, Destination port = %u",
UDP_GET_SRC_PORT (udp_hdrp),
UDP_GET_DST_PORT (udp_hdrp));
#if defined (OPTIMIZE_FRAGMENTS)
}
#endif
#if defined (OPTIMIZE_FRAGMENTS)
if (! fragmented)
{
#endif
clt_port = UDP_GET_SRC_PORT (udp_hdrp);
svr_port = UDP_GET_DST_PORT (udp_hdrp);
#if defined (OPTIMIZE_FRAGMENTS)
}
else
{
clt_port = 0;
svr_port = 0;
}
#endif
/* UDP packets that arrive on port 500/4500 are IPSec control packets. */
if ((svr_port == IPSEC_CTRL_PORT) || (svr_port == IPSEC_NAT_PORT)) {
NLB_IPSEC_PACKET_TYPE PacketType = NLB_IPSEC_OTHER;
PUCHAR pOldBuffer = pPacketInfo->IP.UDP.Payload.pPayload;
ULONG OldLength = pPacketInfo->IP.UDP.Payload.Length;
PUCHAR pNewBuffer = NULL;
ULONG NewLength = 0;
/* Because we need to dig really deep into IPSec IKE payloads, we want to make sure we have access
to as much of the payload as is available. If necessary, this function will allocate a new
buffer and copy the entire payload from the buffer chain into the single contigous payload buffer. */
if (Main_get_full_payload(ctxtp, pPacketInfo->IP.UDP.Payload.pPayloadBuffer, pPacketInfo->IP.UDP.Payload.Length,
pPacketInfo->IP.UDP.Payload.pPayload, &pNewBuffer, &NewLength))
{
/* If Main_get_full_payload allocated a new buffer to hold the payload, re-point
the PACKET_INFO structure information to the new buffer and length. */
if (pNewBuffer != NULL) {
pPacketInfo->IP.UDP.Payload.pPayload = pNewBuffer;
pPacketInfo->IP.UDP.Payload.Length = NewLength;
}
}
/* First, parse the IKE payload to find out whether or not
this is an initial contact IKE Main Mode SA, etc. */
PacketType = Main_parse_ipsec(pPacketInfo, svr_port);
/* Copy the old buffer pointer back into the packet and free the memory allocated to hold the contiguous buffer. */
if (pNewBuffer != NULL) {
pPacketInfo->IP.UDP.Payload.pPayload = pOldBuffer;
pPacketInfo->IP.UDP.Payload.Length = OldLength;
/* Free the buffer memory. */
NdisFreeMemory(pNewBuffer, NewLength, 0);
}
/* If this is an intial contact, treat this as a TCP SYN. Otherwise, treat it like a TCP data packet. */
if (PacketType == NLB_IPSEC_INITIAL_CONTACT) {
/* If we own the bucket for this tuple, we'll create a descriptor and accept the packet. If the client is not behind a
NAT, then the source port will be IPSEC_CTRL_PORT (500). If the client is behind a NAT, the source port will be
arbitrary, but will persist for the entire IPSec session, so we can use it to distinguish clients behind a NAT. In
such a scenario, all IPSec data (non-control traffic) is encapsulated in UDP packets, so the packet check will be
performed in the else case of this branch. In a non-NAT case, the data is in IPSec1/2 protocol packets, which will
be handled analagously in another case of this protocol switch statement. */
#if defined (NLB_HOOK_ENABLE)
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_IPSEC1, CVY_CONN_UP, filter);
#else
acpt = Main_conn_advise(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_IPSEC1, CVY_CONN_UP);
#endif
} else if (PacketType == NLB_IPSEC_IDENTIFICATION) {
/* If this is a NAT'd IKE ID, we may need to pass it up on more than one host. Use the same semantics used for UDP
fragmentation, which is to pass it up on the bucket owner and any hosts that have IPSec tunnels established with
the same client IP address. The IPSEC_UDP descriptors are virtual and the presence of a matching one indicates
that there is at least one IPSec/L2TP tunnel between the given client and server IP addresses. */
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC_UDP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC_UDP);
#endif
} else {
/* If this is part of an existing IPSec session, then we have to have a descriptor in order to accpet it. This will
keep all IPSec traffic during the key exchange sticky, plus the data exchange if the client is behind a NAT. */
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_IPSEC1, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_IPSEC1);
#endif
}
} else {
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_UDP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, svr_port, clt_addr, clt_port, TCPIP_PROTOCOL_UDP);
#endif
}
break;
case TCPIP_PROTOCOL_GRE:
/* If session support is active, then we have a GRE virtual descriptor that makes sure that
we only pass up GRE traffic on host(s) that have active PPTP tunnels between this client
and server IP address. This does not guarantee that the packet goes up ONLY on the correct
host, but that it will go up on AT LEAST the correct host. */
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, PPTP_CTRL_PORT, clt_addr, PPTP_CTRL_PORT, TCPIP_PROTOCOL_GRE, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, PPTP_CTRL_PORT, clt_addr, PPTP_CTRL_PORT, TCPIP_PROTOCOL_GRE);
#endif
break;
case TCPIP_PROTOCOL_IPSEC1:
case TCPIP_PROTOCOL_IPSEC2:
/* If this is part of an existing IPSec session, then we have to have a descriptor in order to accpet it. Because
this can only happen in the case where the client is NOT behind a NAT, we can safely hardcode the client port
to IPSEC_CTRL_PORT (500). In NAT scenarios, the data traffic is UDP encapsulated, not IPSec protocol type
traffic, and is distinguished by source port. */
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC1, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, IPSEC_CTRL_PORT, clt_addr, IPSEC_CTRL_PORT, TCPIP_PROTOCOL_IPSEC1);
#endif
break;
case TCPIP_PROTOCOL_ICMP:
/* If NLB is configured to filter ICMP, then do so, sending it up on only one host.
Hardcode the ports, since ICMP has no notion of port numbers. Otherwise, send
up the ICMP traffic on all hosts (this is the default behavior). */
if (ctxtp->params.filter_icmp)
{
#if defined (NLB_HOOK_ENABLE)
acpt = Main_packet_check(ctxtp, svr_addr, 0, clt_addr, 0, TCPIP_PROTOCOL_UDP, filter);
#else
acpt = Main_packet_check(ctxtp, svr_addr, 0, clt_addr, 0, TCPIP_PROTOCOL_UDP);
#endif
}
break;
default:
TRACE_FILTER("%!FUNC! Accept packet - Unknown protocol traffic always allowed to pass");
/* Allow other protocols to go out on all hosts. */
break;
}
exit:
TRACE_PACKET("%!FUNC! Exit: acpt = %u", acpt);
return acpt;
}
/*
* Function: Main_recv_idhb
* Description: This function processes an extended heartbeat, looking for
* identity information to cache.
* Parameters: ctxtp - a pointer to the context structure for thie NLB instance.
* heartbeatp - a pointer to the NLB heartbeat wrapper that contains
* the length and a pointer to the heartbeat payload.
* Returns: VOID
* Author: chrisdar, 2002 May 22
* Notes: This function should only be called with the load lock held.
*/
VOID Main_recv_idhb(
PMAIN_CTXT ctxtp,
PMAIN_PACKET_HEARTBEAT_INFO heartbeatp)
{
PMAIN_FRAME_HDR cvy_hdrp = heartbeatp->pHeader;
PTLV_HEADER pHBBody = heartbeatp->Payload.pPayloadEx;
ULONG ulPayloadLenBytes = heartbeatp->Payload.Length;
/* cvy_hdrp->host has range 1-32. Change this to 0-31 so that it has the
same value as the index in the cache array */
const ULONG host_id = cvy_hdrp->host - 1;
/* The caller was supposed to validate the information in the header for us.
But the host ID is pretty darn important because it defines uniqueness.
So check it anyway. */
if (host_id >= CVY_MAX_HOSTS)
{
UNIV_PRINT_CRIT(("Main_recv_idhb: host_id [0-31] %u is out of range", host_id));
TRACE_CRIT("%!FUNC! host_id [0-31] %u is out of range", host_id);
goto error;
}
{
PTLV_HEADER pTLV = NULL;
ULONG ulTotBodyLen = 0;
/* The payload of any extended heartbeat type must begin with a TLV header. */
while ((ulTotBodyLen + sizeof(TLV_HEADER)) <= ulPayloadLenBytes)
{
pTLV = (TLV_HEADER *) ((UCHAR *) pHBBody + ulTotBodyLen);
/* The size stated in the TLV header must be large enough to contain the TLV header. */
if (8*(pTLV->length8) < sizeof(TLV_HEADER))
{
UNIV_PRINT_CRIT(("Main_recv_idhb: Extended heartbeat contains a TLV header with a size (%d) less than the size of the header itself (%d)", 8*(pTLV->length8), sizeof(TLV_HEADER)));
TRACE_CRIT("%!FUNC! Extended heartbeat contains a TLV header with a size (%d) less than the size of the header itself (%d)", 8*(pTLV->length8), sizeof(TLV_HEADER));
goto error;
}
ulTotBodyLen += 8*(pTLV->length8);
if (ulTotBodyLen > ulPayloadLenBytes)
{
UNIV_PRINT_CRIT(("Main_recv_idhb: Extended heartbeat received from host_id [0-31] %u has length %u (bytes) but internal data implies length should be at least %u", host_id, ulPayloadLenBytes, ulTotBodyLen));
TRACE_CRIT("%!FUNC! Extended heartbeat received from host_id [0-31] %u has length %u (bytes) but internal data implies length should be at least %u", host_id, ulPayloadLenBytes, ulTotBodyLen);
goto error;
}
if (pTLV->type == MAIN_PING_EX_TYPE_IDENTITY)
{
ULONG fqdn_char = 0;
UNALIGNED PWCHAR pwszFQDN = (WCHAR *) (((UCHAR *) pTLV) + sizeof(TLV_HEADER));
/* In the general case, this is an overestimate of the fqdn length because of padding. That's OK
though since the range we use is guaranteed valid and we avoid buffer overflow by bounding
the number to the size of the destination */
fqdn_char = min(sizeof(ctxtp->identity_cache[host_id].fqdn),
(8*(pTLV->length8) - sizeof(TLV_HEADER))
)/sizeof(WCHAR);
if (fqdn_char == 0)
{
UNIV_PRINT_CRIT(("Main_recv_idhb: Identity heartbeat from host_id [0-31] %u has no fqdn (at a minimum a NULL-terminator is required)", host_id));
TRACE_CRIT("%!FUNC! Identity heartbeat from host_id [0-31] %u has no fqdn (at a minimum a NULL-terminator is required)", host_id);
goto error;
}
if (pwszFQDN[fqdn_char - 1] != UNICODE_NULL)
{
UNIV_PRINT_CRIT(("Main_recv_idhb: Identity heartbeat received from host_id [0-31] %u has an fqdn that isn't NULL-terminated", host_id));
TRACE_CRIT("%!FUNC! Identity heartbeat received from host_id [0-31] %u has an fqdn that isn't NULL-terminated", host_id);
goto error;
}
NdisMoveMemory(&ctxtp->identity_cache[host_id].fqdn, pwszFQDN, fqdn_char*sizeof(WCHAR));
if (ctxtp->identity_cache[host_id].ttl == 0)
{
UNIV_PRINT_INFO(("Main_recv_idhb: adding cache entry for host [0-31] %u, dip=0x%x", host_id, cvy_hdrp->ded_ip_addr));
TRACE_INFO("%!FUNC! adding cache entry for host [0-31] %u. dip=0x%x, fqdn=%ls", host_id, cvy_hdrp->ded_ip_addr, pwszFQDN);
}
else
{
UNIV_PRINT_VERB(("Main_recv_idhb: Updating cache entry for host [0-31] %u, dip=0x%x", host_id, cvy_hdrp->ded_ip_addr));
TRACE_VERB("%!FUNC! Updating cache entry for host [0-31] %u, dip=0x%x, fqdn=%ls", host_id, cvy_hdrp->ded_ip_addr, pwszFQDN);
}
/* Update the entry with the received data */
ctxtp->identity_cache[host_id].ded_ip_addr = cvy_hdrp->ded_ip_addr;
ctxtp->identity_cache[host_id].host_id = (USHORT) host_id;
/* Set the time-to-live for expiring this entry */
UNIV_ASSERT(ctxtp->params.identity_period > 0);
ctxtp->identity_cache[host_id].ttl = WLBS_ID_HB_TOLERANCE*(ctxtp->params.identity_period);
/* Update the entry in the DIP list. */
DipListSetItem(&ctxtp->dip_list, host_id, ctxtp->identity_cache[host_id].ded_ip_addr);
/* There can only be one identity bundle in the heartbeat. */
break;
}
}
}
error:
return;
}
/*
* Function: Main_recv_ping
* Description: This function processes an extended heartbeat, looking for
* identity information to cache.
* Parameters: ctxtp - a pointer to the context structure for thie NLB instance.
* heartbeatp - a pointer to the NLB heartbeat wrapper that contains
* the length and a pointer to the heartbeat payload.
* Returns: ULONG - treated as boolean. FALSE means don't process this packet any further.
* Author: unknown
* Notes: This function should NOT be called with the load lock held.
*/
ULONG Main_recv_ping (
PMAIN_CTXT ctxtp,
PMAIN_PACKET_HEARTBEAT_INFO heartbeatp)
{
#if defined(TRACE_CVY)
DbgPrint ("(CVY %d)\n", cvy_hdrp->host);
#endif
PMAIN_FRAME_HDR cvy_hdrp = heartbeatp->pHeader;
/* Only accept messages from our cluster. */
if (cvy_hdrp->cl_ip_addr == 0 || cvy_hdrp->cl_ip_addr != ctxtp->cl_ip_addr)
{
return FALSE;
}
/* Sanity check host id. */
if (cvy_hdrp->host == 0 || cvy_hdrp->host > CVY_MAX_HOSTS)
{
UNIV_PRINT_CRIT(("Main_recv_ping: Bad host id %d", cvy_hdrp -> host));
TRACE_CRIT("%!FUNC! Bad host id %d", cvy_hdrp -> host);
if (!ctxtp->bad_host_warned && ctxtp->convoy_enabled)
{
WCHAR num[20];
Univ_ulong_to_str(cvy_hdrp->host, num, 10);
LOG_MSG(MSG_ERROR_HOST_ID, num);
ctxtp->bad_host_warned = TRUE;
}
return FALSE;
}
/* Check the heartbeat type */
if (cvy_hdrp->code == MAIN_FRAME_EX_CODE)
{
/* Cache the information in this heartbeat */
if (ctxtp->params.identity_enabled)
{
NdisDprAcquireSpinLock(&ctxtp->load_lock);
Main_recv_idhb(ctxtp, heartbeatp);
NdisDprReleaseSpinLock(&ctxtp->load_lock);
}
/* Don't process these heartbeats any further */
return FALSE;
}
if (!ctxtp->convoy_enabled)
{
return FALSE;
}
if ((cvy_hdrp->host != ctxtp->params.host_priority) &&
(cvy_hdrp->ded_ip_addr == ctxtp->ded_ip_addr) &&
(ctxtp->ded_ip_addr != 0))
{
UNIV_PRINT_CRIT(("Main_recv_ping: Duplicate dedicated IP address 0x%x", ctxtp -> ded_ip_addr));
TRACE_CRIT("%!FUNC! Duplicate dedicated IP address 0x%x", ctxtp -> ded_ip_addr);
if (!ctxtp->dup_ded_ip_warned)
{
LOG_MSG(MSG_ERROR_DUP_DED_IP_ADDR, ctxtp->params.ded_ip_addr);
ctxtp->dup_ded_ip_warned = TRUE;
}
}
/* Might want to take appropriate actions for a message from a host
running different version number of software. */
if (cvy_hdrp->version != CVY_VERSION_FULL)
{
;
}
return TRUE;
}
/*
* Function: Main_ctrl_process
* Description: This function processes a remote control request and replies if
* the request is proper and the cluster is configured to handle
* remote control. If not, the packet is released HERE. If the
* reply succeeds and the packet send is not pending, the packet
* is released HERE. If the send is pending, the packet will be
* released in Prot_send_complete.
* Parameters: ctxtp - a pointer to the context structure for thie NLB instance.
* packetp - a pointer to the NDIS packet on the send path.
* Returns: NDIS_STATUS - the status of the remote control request.
* Author: shouse, 10.15.01
* Notes:
*/
NDIS_STATUS Main_ctrl_process (PMAIN_CTXT ctxtp, PNDIS_PACKET packetp)
{
MAIN_PACKET_INFO PacketInfo;
NDIS_STATUS status;
/* Parse the packet. */
if (!Main_recv_frame_parse(ctxtp, packetp, &PacketInfo))
{
/* If processing the request fails because of a packet error,
misdirected request, malformed request or some other reason,
then free the packet we allocated here and bail out. */
Main_packet_put(ctxtp, packetp, TRUE, NDIS_STATUS_SUCCESS);
}
/* Handle remote control request now. */
status = Main_ctrl_recv(ctxtp, &PacketInfo);
if (status != NDIS_STATUS_SUCCESS)
{
/* If processing the request fails because of a packet error,
misdirected request, malformed request or some other reason,
then free the packet we allocated here and bail out. */
Main_packet_put(ctxtp, packetp, TRUE, NDIS_STATUS_SUCCESS);
}
else
{
/* If processing the request succeeds, send the response out. */
NdisSend(&status, ctxtp->mac_handle, packetp);
/* If the send is pending, Prot_send_complete will be called
to free the packet. If we're done, free it here. */
if (status != NDIS_STATUS_PENDING)
Main_packet_put(ctxtp, packetp, TRUE, status);
}
return status;
}
/*
* Function: Main_ctrl_loopback
* Description: This function is called from Main_send when a potential remote control
* packet is seen being sent. We call this function to loop the packet
* back to ourselves so that we can respond if need be. This functionality
* used to be done by asking NDIS to loop the packet back, but Netmon
* caused problems because the packet was ALREADY looped back at the NDIS
* interface above NLB, so NDIS refused to loop it back again when we asked
* it to. Further, all well-bahaved protocols loop back their own packets
* to themselves anyway, so we bite the bullet and take a stab at becoming
* a well-behaved protocol implementation. This function takes a remote
* control packet on the send path, makes a copy of it and tacks it onto
* the remote control request queue, where it will be serviced later by the
* heartbeat timer.
* Parameters: ctxtp - a pointer to the context structure for thie NLB instance.
* packtep - a pointer to the NDIS packet on the send path.
* Returns: Nothing.
* Author: shouse, 6.10.01
* Notes: Because this function emulates our receive path, but is called internally on
* our send path, the caller of this function should make sure that the IRQL is
* set correctly before calling us and re-setting it after. Packet receive
* functions are run at DPC level by NDIS, yet send packet functions are run at
* DPC OR LOWER. So, the caller should raise the IRQL to DISPATCH_LEVEL and then
* restore it after this function returns. This is just for the sake of paranoia.
*/
VOID Main_ctrl_loopback (PMAIN_CTXT ctxtp, PNDIS_PACKET packetp, PMAIN_PACKET_INFO pPacketInfo)
{
PMAIN_PROTOCOL_RESERVED resp;
PNDIS_PACKET_OOB_DATA oobp;
PNDIS_PACKET newp;
PLIST_ENTRY entryp;
PMAIN_BUFFER bp;
ULONG size;
ULONG xferred;
ULONG lowmark;
UNIV_PRINT_VERB(("Main_ctrl_loopback: Looping back a remote control request to ourselves"));
/* Start - code copied from Main_recv. */
/* Allocate a new packet for the remote control request. */
newp = Main_packet_alloc(ctxtp, FALSE, &lowmark);
/* If allocation failed, bail out. */
if (!newp)
{
UNIV_PRINT_CRIT(("Main_ctrl_loopback: Unable to allocate a packet"));
TRACE_CRIT("%!FUNC! Unable to allocate a packet");
/* Increment the number of failed receive allocations. */
ctxtp->cntr_recv_no_buf++;
return;
}
/* Try to get a buffer to hold the packet contents. */
while (1)
{
NdisDprAcquireSpinLock(&ctxtp->buf_lock);
/* Pull the head of the list off. */
entryp = RemoveHeadList(&ctxtp->buf_list);
/* If we got a buffer, we're good to go - break out of the loop. */
if (entryp != &ctxtp->buf_list)
{
/* Increment the number of outstanding buffers. */
ctxtp->num_bufs_out++;
NdisDprReleaseSpinLock(&ctxtp->buf_lock);
break;
}
NdisDprReleaseSpinLock(&ctxtp->buf_lock);
/* Otherwise, the list was empty - try to replenish it. */
if (!Main_bufs_alloc(ctxtp))
{
UNIV_PRINT_CRIT(("Main_ctrl_loopback: Unable to allocate a buffer"));
TRACE_CRIT("%!FUNC! Unable to allocate a buffer");
/* If we couldn't allocate any buffers, free the packet and bail. */
NdisFreePacket(newp);
/* Increment the number of failed receive allocations. */
ctxtp->cntr_recv_no_buf++;
return;
}
}
/* Get a pointer to the actual buffer. */
bp = CONTAINING_RECORD(entryp, MAIN_BUFFER, link);
UNIV_ASSERT(bp->code == MAIN_BUFFER_CODE);
/* Calculate the total size of the packet. */
size = ctxtp->buf_mac_hdr_len + pPacketInfo->Length;
/* Adjust the size of the buffer to this size. */
NdisAdjustBufferLength(bp->full_bufp, size);
/* Chain the buffer onto the new packet we've allocated. */
NdisChainBufferAtFront(newp, bp->full_bufp);
/* Copy the contents of all buffers in the original packet into
the new packet we've allocated. This should result in the
data (buffers) moving from being spread across several buffers
(probably 4 - one per layer, since this was a packet on the
send path) to a single buffer, which is what we are generally
expecting on the receive path anyway. */
NdisCopyFromPacketToPacketSafe(newp, 0, size, packetp, 0, &xferred, NormalPagePriority);
/* Copy the out-of-band data from the old packet to the new one. */
oobp = NDIS_OOB_DATA_FROM_PACKET(newp);
oobp->HeaderSize = ctxtp->buf_mac_hdr_len;
oobp->MediaSpecificInformation = NULL;
oobp->SizeMediaSpecificInfo = 0;
oobp->TimeSent = 0;
oobp->TimeReceived = 0;
/* Because packets marked as CTRL never pass above NLB in the
network stack, we can always use the ProtocolReserved field. */
resp = MAIN_PROTOCOL_FIELD(newp);
/* If the packet allocation indicated that we have reached the low
watermark for available packets, set STATUS_RESOURCES, so that
the adapter returns the packet to use as soon as possible (this
is the newly allocated remote control packet, which we will
respond to and send in a heartbeat timer handler. */
if (lowmark) NDIS_SET_PACKET_STATUS(newp, NDIS_STATUS_RESOURCES);
/* Fill in the NLB private packet data. */
resp->type = MAIN_PACKET_TYPE_CTRL;
resp->miscp = bp;
resp->data = 0;
resp->group = pPacketInfo->Group;
resp->len = pPacketInfo->Length;
/* If the copy operation failed to copy the entire packet, return the
allocated resources and return NULL to drop the packet. */
if (xferred < size)
{
/* Note that although this IS a receive, Main_packet_put expects
all packets marked as MAIN_PACKET_TYPE_CTRL to specify TRUE
in the send parameter, as these packets are usually returned
in the send complete code path when the reply is sent. */
Main_packet_put(ctxtp, newp, TRUE, NDIS_STATUS_FAILURE);
TRACE_CRIT("%!FUNC! Copy remote control packet contents failed");
return;
}
/* Because this is a remote control packet, MiniportReserved
should not contain a pointer to a private protocol buffer. */
UNIV_ASSERT(!MAIN_MINIPORT_FIELD(newp));
/* End Main_recv. */
/* Start - code copied from Prot_packet_recv. */
/* Handle remote control request now. */
(VOID)Main_ctrl_process(ctxtp, newp);
/* End Prot_packet_recv. */
}
/*
* Function: Main_send
* Description: This function parses packets being sent out on an NLB adapter and
* processes them. This processing includes filtering IP packets
* and internally looping back outgoing NLB remote control requests.
* Parameters: ctxtp - pointer to the NLB context for this adapter.
* packetp - pointer to the NDIS_PACKET being sent.
* exhausted - OUT parameter to indicate that packet resources have
* been internally exhausted.
* Returns: PNDIS_PACKET - the packet to be sent down to the miniport.
* Author: kyrilf, shouse, 3.4.02
* Notes:
*/
PNDIS_PACKET Main_send (
PMAIN_CTXT ctxtp,
PNDIS_PACKET packetp,
PULONG exhausted)
{
MAIN_PACKET_INFO PacketInfo;
PNDIS_PACKET newp;
*exhausted = FALSE;
/* Parse the packet. */
if (!Main_send_frame_parse(ctxtp, packetp, &PacketInfo))
{
TRACE_CRIT("%!FUNC! failed to parse out IP and MAC pointers");
return NULL;
}
/* Munge the Ethernet MAC addresses, if necessary. */
Main_spoof_mac(ctxtp, &PacketInfo, TRUE);
/* Process IP frames. */
if (PacketInfo.Type == TCPIP_IP_SIG)
{
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter;
/* Invoke the send filter hook, if registered. */
filter = Main_send_hook(ctxtp, packetp, &PacketInfo);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* If the hook asked us to reject this packet, then we can do so here. */
TRACE_FILTER("%!FUNC! Packet send filter hook: REJECT packet");
return NULL;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* If the hook asked us to accept this packet, then break out - do
NOT call Main_ip_send_filter. */
TRACE_FILTER("%!FUNC! Packet send filter hook: ACCEPT packet");
}
else
{
/* Filter the IP traffic. */
if (!Main_ip_send_filter(ctxtp, &PacketInfo, filter))
#else
if (!Main_ip_send_filter(ctxtp, &PacketInfo))
#endif
{
TRACE_INFO("%!FUNC! Main_ip_send_filter either failed handling the packet or filtered it out");
return NULL;
}
#if defined (NLB_HOOK_ENABLE)
}
#endif
}
/* Process ARP frames. */
else if (PacketInfo.Type == TCPIP_ARP_SIG)
{
/* Munge the ARPs if necessary. */
if (!Main_arp_handle(ctxtp, &PacketInfo, TRUE))
{
TRACE_INFO("%!FUNC! Main_arp_handle either failed handling ARP or filtered it out");
return NULL;
}
}
/* If still sending out - get a new packet. */
newp = Main_packet_get(ctxtp, packetp, TRUE, PacketInfo.Group, PacketInfo.Length);
*exhausted = (newp == NULL);
/* If this is an out-going remote control request, we need to internally
loop this packet back to ourselves, rather than relying on NDIS to
do this for us. Only bother, however, if remote control is enabled. */
if ((newp != NULL) && (PacketInfo.Operation == MAIN_FILTER_OP_CTRL_REQUEST))
{
if (ctxtp->params.rct_enabled)
{
KIRQL irql;
/* Get the current IRQL. */
irql = KeGetCurrentIrql();
/* The send function for a miniport is ALWAYS run at an
IRQL less than or equal to dispatch, but make sure. */
ASSERT(irql <= DISPATCH_LEVEL);
/* Since we are about to emulate our packet receive path, we want
to make sure that this call to Main_ctrl_loopback runs at the
same IRQL that the rest of our receive code runs at, such as
Prot_packet_recv, so we'll raise it to DISPATCH level, which is
the level that all protocol receive functions run at, and we'll
restore the original IRQL when we're done. */
KeRaiseIrql(DISPATCH_LEVEL, &irql);
/* Internally loop the remote control request back to ourselves,
so that we can also reply to it. According to NDIS, all good
protocol implementations loop back packets internally when
necessary and we shouln't rely on NDIS to do this for us. */
Main_ctrl_loopback(ctxtp, newp, &PacketInfo);
/* Restore the IRQL. */
KeLowerIrql(irql);
}
/* Since we are looping the packet back ourselves,
ask NDIS NOT to loop it back to us again. */
NdisSetPacketFlags(newp, NDIS_FLAGS_DONT_LOOPBACK);
}
return newp;
}
/*
* Function: Main_recv
* Description: This function is the packet receive engine for incoming packets.
* It parses the packet, filters it appropriately, and performs any
* necessary processing on packets that will be passed up to the
* protocol(s).
* Parameters: ctxtp - a pointer to the NLB main context structure for this adapter.
* packetp - a pointer to the received NDIS_PACKET.
* Returns: PNDIS_PACKET - a pointer to the packet to be propagated up to the protocol(s).
* Author: kyrilf, shouse, 3.4.02
* Notes:
*/
PNDIS_PACKET Main_recv (
PMAIN_CTXT ctxtp,
PNDIS_PACKET packetp)
{
MAIN_PACKET_INFO PacketInfo;
PNDIS_PACKET newp;
PMAIN_BUFFER bp;
PMAIN_PROTOCOL_RESERVED resp;
PNDIS_PACKET_OOB_DATA oobp;
ULONG size;
ULONG xferred;
PLIST_ENTRY entryp;
USHORT group;
ULONG packet_lowmark;
UNIV_ASSERT (ctxtp->medium == NdisMedium802_3);
/* Parse the packet. */
if (!Main_recv_frame_parse(ctxtp, packetp, &PacketInfo))
{
TRACE_CRIT("%!FUNC! failed to parse out IP and MAC pointers");
return NULL;
}
/* Munge the Ethernet MAC addresses, if necessary. */
Main_spoof_mac(ctxtp, &PacketInfo, FALSE);
/* Process IP frames. Because remote control requests are not filtered, they
should fall straight through and be accepted. Remote control responses,
however, need to be passed to the filter hook, so although they are not
filtered by NLB, they may be filtered by the hook consumer. */
if ((PacketInfo.Type == TCPIP_IP_SIG) && (PacketInfo.Operation != MAIN_FILTER_OP_CTRL_REQUEST))
{
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter;
/* Invoke the receive packet hook, if one is registered. */
filter = Main_recv_hook(ctxtp, packetp, &PacketInfo);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* If the hook asked us to reject this packet, then we can do so here. */
TRACE_INFO("%!FUNC! Packet receive filter hook: REJECT packet");
return NULL;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* If the hook asked us to accept this packet, then break out - do
NOT call Main_ip_recv_filter. */
TRACE_INFO("%!FUNC! Packet receive filter hook: ACCEPT packet");
}
else
{
/* If this is NOT a remote control response, filter the IP traffic. */
if ((PacketInfo.Operation != MAIN_FILTER_OP_CTRL_RESPONSE) && !Main_ip_recv_filter(ctxtp, &PacketInfo, filter))
#else
if ((PacketInfo.Operation != MAIN_FILTER_OP_CTRL_RESPONSE) && !Main_ip_recv_filter(ctxtp, &PacketInfo))
#endif
{
TRACE_VERB("%!FUNC! Main_ip_recv_filter either failed handling the packet or filtered it out");
return NULL;
}
#if defined (NLB_HOOK_ENABLE)
}
#endif
}
/* Process ARP frames. */
else if (PacketInfo.Type == TCPIP_ARP_SIG)
{
/* Munge the ARPs if necessary. */
if (!Main_arp_handle(ctxtp, &PacketInfo, FALSE))
{
TRACE_INFO("%!FUNC! Main_arp_handle either failed handling ARP or filtered it out");
return NULL;
}
}
/* Process heartbeat frames. */
else if ((PacketInfo.Type == MAIN_FRAME_SIG) || (PacketInfo.Type == MAIN_FRAME_SIG_OLD))
{
/* Call Main_recv_ping to check for invalid host IDs and to ensure that this
heartbeat is actually intended for this cluster. */
if (Main_recv_ping(ctxtp, &PacketInfo.Heartbeat))
{
/* Switch into backward compatibility mode if a convoy hearbeat is detected. */
if ((PacketInfo.Type == MAIN_FRAME_SIG_OLD) && !ctxtp->etype_old)
{
CVY_ETHERNET_ETYPE_SET(&ctxtp->media_hdr.ethernet, MAIN_FRAME_SIG_OLD);
ctxtp->etype_old = TRUE;
}
NdisDprAcquireSpinLock(&ctxtp->load_lock);
/* Have load deal with contents. */
if (ctxtp->convoy_enabled)
{
BOOLEAN bConverging = FALSE;
bConverging = Load_msg_rcv(&ctxtp->load, PacketInfo.Heartbeat.pHeader, PacketInfo.Heartbeat.Payload.pPayload);
if (bConverging)
{
/* While the cluster is in a converging state, we keep the DIP list clear.
As soon as we have completed convergence, we begin updating the DIP list
on every heartbeat while converged. */
if (!ctxtp->params.identity_enabled)
{
DipListClear(&ctxtp->dip_list);
}
}
else
{
/* Once converged, every heartbeat updates the relevant DIP entry in the list. */
DipListSetItem(&ctxtp->dip_list, PacketInfo.Heartbeat.pHeader->host - 1, PacketInfo.Heartbeat.pHeader->ded_ip_addr);
}
}
NdisDprReleaseSpinLock(&ctxtp->load_lock);
}
/* If we're NOT passing the packet up the protocols in order to view it
in NetMon, we can drop it here, as we're already done with it. */
if (!ctxtp->params.netmon_alive)
{
return NULL;
}
}
/* Post-process NBT traffic. */
if (PacketInfo.Operation == MAIN_FILTER_OP_NBT)
{
Tcpip_nbt_handle(&ctxtp->tcpip, &PacketInfo);
}
/* Get a new packet. For all non-remote control request packets,
attempt to use packet stacking to avoid allocating a new packet. */
if (PacketInfo.Operation != MAIN_FILTER_OP_CTRL_REQUEST)
{
/* Either re-use this packet (packet stacking), or get a new one if necessary. */
newp = Main_packet_get(ctxtp, packetp, FALSE, PacketInfo.Group, PacketInfo.Length);
if (newp == NULL) {
/* Increment the number of failed receive allocations. */
ctxtp->cntr_recv_no_buf++;
return NULL;
}
}
/* Copy incoming remote control packet into our own, so we re-use it later to send back reply. */
else
{
/* Allocate a new packet for the remote control request. */
newp = Main_packet_alloc(ctxtp, FALSE, &packet_lowmark);
/* If allocation failed, bail out. */
if (newp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv: Unable to allocate a packet"));
TRACE_CRIT("%!FUNC! Unable to allocate a packet");
/* Increment the number of failed receive allocations. */
ctxtp->cntr_recv_no_buf ++;
return NULL;
}
/* Try to get a buffer to hold the packet contents. */
while (1)
{
NdisDprAcquireSpinLock(&ctxtp->buf_lock);
/* Pull the head of the list off. */
entryp = RemoveHeadList(&ctxtp->buf_list);
/* If we got a buffer, we're good to go - break out of the loop. */
if (entryp != &ctxtp->buf_list)
{
/* Increment the number of outstanding buffers. */
ctxtp->num_bufs_out++;
NdisDprReleaseSpinLock(&ctxtp->buf_lock);
break;
}
NdisDprReleaseSpinLock(&ctxtp->buf_lock);
UNIV_PRINT_VERB(("Main_recv: Out of buffers"));
TRACE_VERB("%!FUNC! Out of buffers");
/* Otherwise, the list was empty - try to replenish it. */
if (!Main_bufs_alloc(ctxtp))
{
TRACE_CRIT("%!FUNC! Main_bufs_alloc failed");
/* If we couldn't allocate any buffers, free the packet and bail. */
NdisFreePacket(newp);
/* Increment the number of failed receive allocations. */
ctxtp->cntr_recv_no_buf++;
return NULL;
}
}
/* Get a pointer to the actual buffer. */
bp = CONTAINING_RECORD(entryp, MAIN_BUFFER, link);
UNIV_ASSERT(bp->code == MAIN_BUFFER_CODE);
/* Calculate the total packet size. */
size = ctxtp->buf_mac_hdr_len + PacketInfo.Length;
/* Adjust the size of the buffer to this size. */
NdisAdjustBufferLength(bp->full_bufp, size);
/* Chain the buffer onto the new packet we've allocated. */
NdisChainBufferAtFront(newp, bp->full_bufp);
/* Copy actual data. Check for success after filling in the private
packet data so we can call Main_packet_put to free the resources. */
NdisCopyFromPacketToPacket(newp, 0, size, packetp, 0, &xferred);
/* Copy the out-of-band data from the old packet to the new one. */
oobp = NDIS_OOB_DATA_FROM_PACKET(newp);
oobp->HeaderSize = ctxtp->buf_mac_hdr_len;
oobp->MediaSpecificInformation = NULL;
oobp->SizeMediaSpecificInfo = 0;
oobp->TimeSent = 0;
oobp->TimeReceived = 0;
/* Because packets marked as CTRL never pass above NLB in the
network stack, we can always use the ProtocolReserved field. */
resp = MAIN_PROTOCOL_FIELD(newp);
/* If the packet allocation indicated that we have reached the low
watermark for available packets, set STATUS_RESOURCES, so that
the adapter returns the packet to use as soon as possible (this
is the newly allocated remote control packet, which we will
respond to and send in a heartbeat timer handler. */
if (packet_lowmark) NDIS_SET_PACKET_STATUS(newp, NDIS_STATUS_RESOURCES);
/* Fill in the NLB private packet data. */
resp->type = MAIN_PACKET_TYPE_CTRL;
resp->miscp = bp;
resp->data = 0;
resp->group = PacketInfo.Group;
resp->len = PacketInfo.Length;
/* If the copy operation failed to copy the entire packet, return the
allocated resources and return NULL to drop the packet. */
if (xferred < size)
{
/* Note that although this IS a receive, Main_packet_put expects
all packets marked as MAIN_PACKET_TYPE_CTRL to specify TRUE
in the send parameter, as these packets are usually returned
in the send complete code path when the reply is sent. */
Main_packet_put(ctxtp, newp, TRUE, NDIS_STATUS_FAILURE);
TRACE_CRIT("%!FUNC! Copy remote control packet contents failed");
return NULL;
}
/* Because this is a remote control packet, MiniportReserved
should not contain a pointer to a private protocol buffer. */
UNIV_ASSERT(!MAIN_MINIPORT_FIELD(newp));
}
return newp;
}
ULONG Main_actions_alloc (
PMAIN_CTXT ctxtp)
{
PMAIN_ACTION actp;
ULONG size, index, i;
NDIS_STATUS status;
NdisAcquireSpinLock (& ctxtp -> act_lock);
if (ctxtp -> num_action_allocs >= CVY_MAX_ALLOCS)
{
if (! ctxtp -> actions_warned)
{
LOG_MSG1 (MSG_WARN_RESOURCES, CVY_NAME_NUM_ACTIONS, ctxtp -> num_actions);
ctxtp -> actions_warned = TRUE;
}
NdisReleaseSpinLock (& ctxtp -> act_lock);
TRACE_CRIT("%!FUNC! number of action allocs is too high = %u", ctxtp -> num_action_allocs);
return FALSE;
}
index = ctxtp -> num_action_allocs;
NdisReleaseSpinLock (& ctxtp -> act_lock);
size = ctxtp -> num_actions * ctxtp -> act_size; /* 64-bit -- ramkrish */
status = NdisAllocateMemoryWithTag (& (ctxtp -> act_buf [index]), size,
UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_actions_alloc: Error allocating actions %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating actions %d status=0x%x", size, status);
return FALSE;
}
NdisAcquireSpinLock (& ctxtp -> act_lock);
ctxtp -> num_action_allocs ++;
NdisReleaseSpinLock (& ctxtp -> act_lock);
for (i = 0; i < ctxtp -> num_actions; i++)
{
/* ensure that actp is aligned along 8-byte boundaries */
actp = (PMAIN_ACTION) ( (PUCHAR) (ctxtp -> act_buf [index]) + i * ctxtp -> act_size);
actp -> code = MAIN_ACTION_CODE;
actp -> ctxtp = ctxtp;
NdisInitializeEvent(&actp->op.request.event);
NdisInterlockedInsertTailList (& ctxtp -> act_list,
& actp -> link,
& ctxtp -> act_lock);
}
return TRUE;
} /* end Main_actions_alloc */
PMAIN_ACTION Main_action_get (
PMAIN_CTXT ctxtp)
{
PLIST_ENTRY entryp;
PMAIN_ACTION actp;
while (1)
{
NdisAcquireSpinLock (& ctxtp -> act_lock);
entryp = RemoveHeadList (& ctxtp -> act_list);
NdisReleaseSpinLock (& ctxtp -> act_lock);
if (entryp != & ctxtp -> act_list)
break;
UNIV_PRINT_VERB(("Main_action_get: Out of actions"));
TRACE_VERB("%!FUNC! Out of actions");
if (! Main_actions_alloc (ctxtp))
{
UNIV_PRINT_CRIT(("Main_action_get: Main_actions_alloc failed"));
TRACE_CRIT("%!FUNC! Main_actions_alloc failed");
return NULL;
}
}
actp = CONTAINING_RECORD (entryp, MAIN_ACTION, link);
UNIV_ASSERT (actp -> code == MAIN_ACTION_CODE);
UNIV_ASSERT (actp -> ctxtp == ctxtp);
actp->status = NDIS_STATUS_FAILURE;
actp->op.request.xferred = 0;
actp->op.request.needed = 0;
actp->op.request.external = FALSE;
actp->op.request.buffer_len = 0;
actp->op.request.buffer = NULL;
NdisResetEvent(&actp->op.request.event);
NdisZeroMemory(&actp->op.request.req, sizeof(NDIS_REQUEST));
return actp;
} /* end Main_action_get */
VOID Main_action_put (
PMAIN_CTXT ctxtp,
PMAIN_ACTION actp)
{
UNIV_ASSERT (actp -> code == MAIN_ACTION_CODE);
NdisAcquireSpinLock (& ctxtp -> act_lock);
InsertTailList (& ctxtp -> act_list, & actp -> link);
NdisReleaseSpinLock (& ctxtp -> act_lock);
} /* end Main_action_put */
VOID Main_action_slow_put (
PMAIN_CTXT ctxtp,
PMAIN_ACTION actp)
{
UNIV_ASSERT (actp -> code == MAIN_ACTION_CODE);
NdisAcquireSpinLock (& ctxtp -> act_lock);
InsertTailList (& ctxtp -> act_list, & actp -> link);
NdisReleaseSpinLock (& ctxtp -> act_lock);
} /* end Main_action_slow_put */
ULONG Main_bufs_alloc (
PMAIN_CTXT ctxtp)
{
PMAIN_BUFFER bp;
NDIS_STATUS status;
ULONG i, size, index;
NdisAcquireSpinLock (& ctxtp -> buf_lock);
if (ctxtp -> num_buf_allocs >= CVY_MAX_ALLOCS)
{
if (! ctxtp -> packets_warned)
{
LOG_MSG1 (MSG_WARN_RESOURCES, CVY_NAME_NUM_PACKETS, ctxtp -> num_packets);
ctxtp -> packets_warned = TRUE;
}
NdisReleaseSpinLock (& ctxtp -> buf_lock);
TRACE_CRIT("%!FUNC! num_buf_allocs=%u too large", ctxtp -> num_buf_allocs);
return FALSE;
}
index = ctxtp -> num_buf_allocs;
NdisReleaseSpinLock (& ctxtp -> buf_lock);
/* get twice as many buffer descriptors (one for entire buffer and one
just for the payload portion) */
size = 2 * ctxtp -> num_packets;
NdisAllocateBufferPool (& status, & (ctxtp -> buf_pool_handle [index]),
2 * ctxtp -> num_packets);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_bufs_alloc: Error allocating buffer pool %d %x", size, status));
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
TRACE_CRIT("%!FUNC! Error allocating buffer pool %d 0x%x", size, status);
return FALSE;
}
/* allocate memory for the payload */
size = ctxtp -> num_packets * ctxtp -> buf_size;
status = NdisAllocateMemoryWithTag (& (ctxtp -> buf_array [index]), size,
UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_bufs_alloc: Error allocating buffer space %d %x", size, status));
TRACE_CRIT("%!FUNC! Error allocating buffer space %d 0x%x", size, status);
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
goto error;
}
NdisZeroMemory (ctxtp -> buf_array [index], size);
for (i = 0; i < ctxtp -> num_packets; i ++)
{
bp = (PMAIN_BUFFER) (ctxtp -> buf_array [index] + i * ctxtp -> buf_size);
bp -> code = MAIN_BUFFER_CODE;
/* setup buffer descriptors to describe entire buffer and just the
payload */
size = ctxtp -> buf_mac_hdr_len + ctxtp -> max_frame_size;
NdisAllocateBuffer (& status, & bp -> full_bufp,
ctxtp -> buf_pool_handle [index],
bp -> data, size);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_bufs_alloc: Error allocating header buffer %d %x", i, status));
TRACE_CRIT("%!FUNC! Error allocating header buffer %d 0x%x", i, status);
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
goto error;
}
bp -> framep = bp -> data + ctxtp -> buf_mac_hdr_len;
size = ctxtp -> max_frame_size;
NdisAllocateBuffer (& status, & bp -> frame_bufp,
ctxtp -> buf_pool_handle [index],
bp -> framep, size);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_bufs_alloc: Error allocating frame buffer %d %x", i, status));
TRACE_CRIT("%!FUNC! Error allocating frame buffer %d 0x%x", i, status);
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
goto error;
}
NdisInterlockedInsertTailList (& ctxtp -> buf_list,
& bp -> link,
& ctxtp -> buf_lock);
}
NdisAcquireSpinLock (& ctxtp -> buf_lock);
ctxtp -> num_buf_allocs ++;
ctxtp->num_bufs_alloced += ctxtp->num_packets;
NdisReleaseSpinLock (& ctxtp -> buf_lock);
return TRUE;
error:
if (ctxtp -> buf_array [index] != NULL)
{
for (i = 0; i < ctxtp -> num_packets; i ++)
{
bp = (PMAIN_BUFFER) (ctxtp -> buf_array [index] + i * ctxtp -> buf_size);
if (bp -> full_bufp != NULL)
{
NdisAdjustBufferLength (bp -> full_bufp,
ctxtp -> buf_mac_hdr_len +
ctxtp -> max_frame_size);
NdisFreeBuffer (bp -> full_bufp);
}
if (bp -> frame_bufp != NULL)
{
NdisAdjustBufferLength (bp -> frame_bufp,
ctxtp -> max_frame_size);
NdisFreeBuffer (bp -> frame_bufp);
}
}
NdisFreeMemory (ctxtp -> buf_array [index],
ctxtp -> num_packets * ctxtp -> buf_size, 0);
}
if (ctxtp -> buf_pool_handle [index] != NULL)
NdisFreeBufferPool (ctxtp -> buf_pool_handle [index]);
return FALSE;
} /* end Main_bufs_alloc */
PNDIS_PACKET Main_frame_get (
PMAIN_CTXT ctxtp,
PULONG lenp,
USHORT frame_type)
{
PLIST_ENTRY entryp;
PMAIN_FRAME_DSCR dscrp;
NDIS_STATUS status;
PMAIN_PROTOCOL_RESERVED resp;
PNDIS_PACKET packet;
PNDIS_PACKET_STACK pktstk;
BOOLEAN stack_left;
NdisAllocatePacket (& status, & packet, ctxtp -> frame_pool_handle);
if (status != NDIS_STATUS_SUCCESS)
{
UNIV_PRINT_CRIT(("Main_frame_get: Out of PING packets"));
TRACE_CRIT("%!FUNC! Out of PING packets");
#if 0 /* V1.3.2b */
if (! ctxtp -> send_msgs_warned)
{
LOG_MSG1 (MSG_WARN_RESOURCES, CVY_NAME_NUM_SEND_MSGS, ctxtp -> num_send_msgs);
ctxtp -> send_msgs_warned = TRUE;
}
#endif
ctxtp->cntr_frame_no_buf++;
return NULL;
}
/* #ps# -- ramkrish */
pktstk = NdisIMGetCurrentPacketStack (packet, & stack_left);
if (pktstk)
{
MAIN_IMRESERVED_FIELD(pktstk) = NULL;
}
/* Make sure that the MiniportReserved field is initially NULL. */
MAIN_MINIPORT_FIELD(packet) = NULL;
NdisAcquireSpinLock (& ctxtp -> frame_lock);
entryp = RemoveHeadList (& ctxtp -> frame_list);
NdisReleaseSpinLock (& ctxtp -> frame_lock);
if (entryp == & ctxtp -> frame_list)
{
UNIV_PRINT_CRIT(("Main_frame_get: Out of PING messages"));
TRACE_CRIT("%!FUNC! Out of PING messages");
#if 0 /* V1.3.2b */
if (! ctxtp -> send_msgs_warned)
{
LOG_MSG1 (MSG_WARN_RESOURCES, CVY_NAME_NUM_SEND_MSGS, ctxtp -> num_send_msgs);
ctxtp -> send_msgs_warned = TRUE;
}
#endif
ctxtp->cntr_frame_no_buf++;
NdisFreePacket (packet);
return NULL;
}
dscrp = CONTAINING_RECORD (entryp, MAIN_FRAME_DSCR, link);
/* fill out the header. in both cases - chain
the necessary buffer descriptors to the packet */
{
PVOID address = NULL;
ULONG size = 0;
UNIV_ASSERT_VAL (frame_type == MAIN_PACKET_TYPE_PING ||
frame_type == MAIN_PACKET_TYPE_IGMP ||
frame_type == MAIN_PACKET_TYPE_IDHB,
frame_type);
/* V2.0.6, V2.2 moved here */
/* Differentiate between igmp and heartbeat messages and allocate buffers */
if (frame_type == MAIN_PACKET_TYPE_PING)
{
size = sizeof (PING_MSG);
address = (PVOID)(ctxtp -> load_msgp);
}
else if (frame_type == MAIN_PACKET_TYPE_IGMP)
{
size = sizeof (MAIN_IGMP_FRAME);
address = (PVOID) (& ctxtp -> igmp_frame);
}
else if (frame_type == MAIN_PACKET_TYPE_IDHB)
{
/* Not grabbing lock; if anything we screw up the size relative to the
content of the heartbeat and we send out a garbage packet. But the
memory is always valid. */
size = ctxtp->idhb_size;
address = (PVOID) (&ctxtp->idhb_msg);
}
else
{
UNIV_PRINT_CRIT(("Main_frame_get: Invalid frame type passed to Main_frame_get 0x%x", frame_type));
TRACE_CRIT("%!FUNC! Invalid frame type passed to Main_frame_get 0x%x", frame_type);
UNIV_ASSERT(0);
}
/* Allocate the buffer for sending the data */
if (size > 0)
{
NdisAllocateBuffer (& status, & dscrp -> send_data_bufp,
ctxtp -> frame_buf_pool_handle,
address, size);
}
else
{
status = NDIS_STATUS_FAILURE;
}
if (status != NDIS_STATUS_SUCCESS)
{
if (size > 0)
{
UNIV_PRINT_CRIT(("Main_frame_get: Failed to allocate buffer for 0x%x", frame_type));
TRACE_CRIT("%!FUNC! Failed to allocate buffer for 0x%x", frame_type);
}
dscrp -> send_data_bufp = NULL;
NdisAcquireSpinLock (& ctxtp -> frame_lock);
InsertTailList (& ctxtp -> frame_list, & dscrp -> link);
NdisReleaseSpinLock (& ctxtp -> frame_lock);
ctxtp->cntr_frame_no_buf++;
NdisFreePacket (packet);
return NULL;
}
/* since packet length is always the same, and so are destination
and source addresses - can use generic media header */
if (frame_type == MAIN_PACKET_TYPE_PING)
{
dscrp -> media_hdr = ctxtp -> media_hdr;
dscrp -> frame_hdr . code = MAIN_FRAME_CODE;
dscrp -> frame_hdr . version = CVY_VERSION_FULL;
dscrp -> frame_hdr . host = (UCHAR) ctxtp -> params . host_priority;
dscrp -> frame_hdr . cl_ip_addr = ctxtp -> cl_ip_addr;
dscrp -> frame_hdr . ded_ip_addr = ctxtp -> ded_ip_addr;
NdisChainBufferAtFront (packet, dscrp -> send_data_bufp); /* V1.1.4 */
NdisChainBufferAtFront (packet, dscrp -> frame_hdr_bufp);
}
else if (frame_type == MAIN_PACKET_TYPE_IGMP)
{
dscrp -> media_hdr = ctxtp -> media_hdr_igmp;
NdisChainBufferAtFront (packet, dscrp -> send_data_bufp); /* V1.1.4 */
}
else if (frame_type == MAIN_PACKET_TYPE_IDHB)
{
dscrp->media_hdr = ctxtp->media_hdr;
dscrp->frame_hdr.code = MAIN_FRAME_EX_CODE;
dscrp->frame_hdr.version = CVY_VERSION_FULL;
dscrp->frame_hdr.host = (UCHAR) ctxtp->params.host_priority;
dscrp->frame_hdr.cl_ip_addr = ctxtp->cl_ip_addr;
dscrp->frame_hdr.ded_ip_addr = ctxtp->ded_ip_addr;
NdisChainBufferAtFront(packet, dscrp->send_data_bufp);
NdisChainBufferAtFront(packet, dscrp->frame_hdr_bufp);
}
else
{
UNIV_PRINT_CRIT(("Main_frame_get: Invalid frame type passed to Main_frame_get 0x%x", frame_type));
TRACE_CRIT("%!FUNC! Invalid frame type passed to Main_frame_get 0x%x", frame_type);
UNIV_ASSERT(0);
}
NdisChainBufferAtFront (packet, dscrp -> media_hdr_bufp);
}
/* fill out protocol reserved fields */
/* Again, since these packets are hidden from upper layers, we should
be OK using the protocol reserved field regardless of send/receive. */
resp = MAIN_PROTOCOL_FIELD (packet);
resp -> type = frame_type;
resp -> miscp = dscrp;
resp -> data = 0;
resp -> len = 0;
resp -> group = 0;
* lenp = dscrp -> recv_len;
NdisInterlockedIncrement(&ctxtp->num_frames_out);
return packet;
} /* end Main_frame_get */
VOID Main_frame_put (
PMAIN_CTXT ctxtp,
PNDIS_PACKET packet,
PMAIN_FRAME_DSCR dscrp)
{
PNDIS_BUFFER bufp;
/* Again, since these packets are hidden from upper layers, we should
be OK using the protocol reserved field regardless of send/receive. */
PMAIN_PROTOCOL_RESERVED resp = MAIN_PROTOCOL_FIELD (packet);
UNIV_ASSERT_VAL (resp -> type == MAIN_PACKET_TYPE_PING ||
resp -> type == MAIN_PACKET_TYPE_IGMP ||
resp -> type == MAIN_PACKET_TYPE_IDHB,
resp -> type);
/* strip buffers from the packet buffer chain */
do
{
NdisUnchainBufferAtFront (packet, & bufp);
}
while (bufp != NULL);
/* recyle the packet */
NdisReinitializePacket (packet);
NdisFreePacket (packet);
/* If the send buffer is not null, free this buffer */
if (dscrp -> send_data_bufp != NULL)
{
NdisFreeBuffer (dscrp -> send_data_bufp);
dscrp -> send_data_bufp = NULL;
}
/* put frame descriptor back on the free list */
NdisAcquireSpinLock (& ctxtp -> frame_lock);
InsertTailList (& ctxtp -> frame_list, & dscrp -> link);
NdisReleaseSpinLock (& ctxtp -> frame_lock);
NdisInterlockedDecrement(&ctxtp->num_frames_out);
} /* end Main_frame_return */
PNDIS_PACKET Main_packet_alloc (
PMAIN_CTXT ctxtp,
ULONG send,
PULONG low)
{
PNDIS_PACKET newp = NULL;
PNDIS_HANDLE poolh;
ULONG i, max, size, start;
NDIS_STATUS status;
PNDIS_PACKET_STACK pktstk;
BOOLEAN stack_left;
/* !!! assume that recv and send paths are not re-entrant, otherwise need
to lock this. make sure that NdisAllocatePacket... routines are not
called holding a spin lock */
/* V1.1.2 */
*low = FALSE;
if (send)
{
poolh = ctxtp -> send_pool_handle;
NdisAcquireSpinLock (& ctxtp -> send_lock);
max = ctxtp -> num_send_packet_allocs;
start = ctxtp -> cur_send_packet_pool;
ctxtp -> cur_send_packet_pool = (start + 1) % max;
NdisReleaseSpinLock (& ctxtp -> send_lock);
}
else
{
poolh = ctxtp -> recv_pool_handle;
NdisAcquireSpinLock (& ctxtp -> recv_lock);
max = ctxtp -> num_recv_packet_allocs;
start = ctxtp -> cur_recv_packet_pool;
ctxtp -> cur_recv_packet_pool = (start + 1) % max;
NdisReleaseSpinLock (& ctxtp -> recv_lock);
}
/* Try to allocate a packet from the existing packet pools */
i = start;
do
{
NdisAllocatePacket (& status, & newp, poolh [i]);
if (status == NDIS_STATUS_SUCCESS)
{
/* #ps# -- ramkrish */
pktstk = NdisIMGetCurrentPacketStack (newp, & stack_left);
if (pktstk)
{
MAIN_IMRESERVED_FIELD (pktstk) = NULL;
}
/* Make sure that the MiniportReserved field is initially NULL. */
MAIN_MINIPORT_FIELD(newp) = NULL;
if (send)
{
NdisAcquireSpinLock (& ctxtp -> send_lock);
/* Because the decrement is interlocked, so should the increment. */
NdisInterlockedIncrement(& ctxtp -> num_sends_out);
if ((ctxtp -> num_sends_alloced - ctxtp -> num_sends_out)
< (ctxtp -> num_packets / 2))
{
NdisReleaseSpinLock (& ctxtp -> send_lock);
break;
}
NdisReleaseSpinLock (& ctxtp -> send_lock);
}
else
{
NdisAcquireSpinLock (& ctxtp -> recv_lock);
/* Because the decrement is interlocked, so should the increment. */
NdisInterlockedIncrement(& ctxtp -> num_recvs_out);
if ((ctxtp -> num_recvs_alloced - ctxtp -> num_recvs_out)
< (ctxtp -> num_packets / 2))
{
NdisReleaseSpinLock (& ctxtp -> recv_lock);
break;
}
NdisReleaseSpinLock (& ctxtp -> recv_lock);
}
return newp;
}
/* pick the next pool to improve number of tries until we get something */
i = (i + 1) % max;
} while (i != start);
/* At this point, the high level mark has been reached, so allocate a new packet pool */
if (send)
{
NdisAcquireSpinLock (& ctxtp -> send_lock);
if (ctxtp -> num_send_packet_allocs >= CVY_MAX_ALLOCS)
{
* low = TRUE;
NdisReleaseSpinLock (& ctxtp -> send_lock);
return newp;
}
if (ctxtp -> send_allocing)
{
* low = TRUE; /* do not know whether the allocation by another thread will succeed or not */
NdisReleaseSpinLock (& ctxtp -> send_lock);
return newp;
}
else
{
max = ctxtp -> num_send_packet_allocs;
ctxtp -> send_allocing = TRUE;
NdisReleaseSpinLock (& ctxtp -> send_lock);
}
}
else
{
NdisAcquireSpinLock (& ctxtp -> recv_lock);
if (ctxtp -> num_recv_packet_allocs >= CVY_MAX_ALLOCS)
{
* low = TRUE;
NdisReleaseSpinLock (& ctxtp -> recv_lock);
return newp;
}
if (ctxtp -> recv_allocing)
{
* low = TRUE; /* do not know whether the allocation by another thread will succeed or not */
NdisReleaseSpinLock (& ctxtp -> recv_lock);
return newp;
}
else
{
max = ctxtp -> num_recv_packet_allocs;
ctxtp -> recv_allocing = TRUE;
NdisReleaseSpinLock (& ctxtp -> recv_lock);
}
}
/* Due to the send_allocing and recv_allocing flag, at most 1 send or recv thread will be in this portion at any time */
size = ctxtp -> num_packets;
NdisAllocatePacketPool (& status, & (poolh [max]),
ctxtp -> num_packets,
sizeof (MAIN_PROTOCOL_RESERVED));
if (status != NDIS_STATUS_SUCCESS)
{
if (send)
{
UNIV_PRINT_CRIT(("Main_packet_alloc: Error allocating send packet pool %d %x", size, status));
TRACE_CRIT("%!FUNC! Error allocating send packet pool %d 0x%x", size, status);
}
else
{
UNIV_PRINT_CRIT(("Main_packet_alloc: Error allocating recv packet pool %d %x", size, status));
TRACE_CRIT("%!FUNC! Error allocating recv packet pool %d %x", size, status);
}
LOG_MSG2 (MSG_ERROR_MEMORY, MSG_NONE, size, status);
* low = TRUE;
}
else
{
if (send)
{
NdisAcquireSpinLock (& ctxtp -> send_lock);
ctxtp -> num_send_packet_allocs ++;
ctxtp -> num_sends_alloced += ctxtp -> num_packets;
ctxtp -> send_allocing = FALSE;
NdisReleaseSpinLock (& ctxtp -> send_lock);
}
else
{
NdisAcquireSpinLock (& ctxtp -> recv_lock);
ctxtp -> num_recv_packet_allocs ++;
ctxtp -> num_recvs_alloced += ctxtp -> num_packets;
ctxtp -> recv_allocing = FALSE;
NdisReleaseSpinLock (& ctxtp -> recv_lock);
}
* low = FALSE;
}
return newp;
} /* Main_packet_alloc */
PNDIS_PACKET Main_packet_get (
PMAIN_CTXT ctxtp,
PNDIS_PACKET packet,
ULONG send,
USHORT group,
ULONG len)
{
PNDIS_PACKET newp;
PMAIN_PROTOCOL_RESERVED resp = NULL;
ULONG packet_lowmark;
PNDIS_PACKET_STACK pktstk = NULL;
BOOLEAN stack_left;
pktstk = NdisIMGetCurrentPacketStack(packet, &stack_left);
if (stack_left)
{
resp = (PMAIN_PROTOCOL_RESERVED)NdisAllocateFromNPagedLookasideList(&ctxtp->resp_list);
MAIN_IMRESERVED_FIELD(pktstk) = resp;
if (resp)
{
resp->type = MAIN_PACKET_TYPE_PASS;
resp->miscp = packet;
resp->data = 0;
resp->group = group;
resp->len = len;
return packet;
}
}
/* If this is a receive, then we are using MiniportReserved and must allocate a
buffer to hold our private data. If it fails, bail out and dump the packet. */
if (!send) {
resp = (PMAIN_PROTOCOL_RESERVED) NdisAllocateFromNPagedLookasideList(&ctxtp->resp_list);
if (!resp)
{
return NULL;
}
}
/* Get a packet. */
newp = Main_packet_alloc(ctxtp, send, &packet_lowmark);
if (newp == NULL) {
/* If packet allocation fails, put the resp buffer back on the list if this is a receive. */
if (resp) NdisFreeToNPagedLookasideList(&ctxtp->resp_list, resp);
return NULL;
}
pktstk = NdisIMGetCurrentPacketStack(newp, &stack_left);
if (pktstk)
{
MAIN_IMRESERVED_FIELD(pktstk) = NULL;
}
/* Make sure that the MiniportReserved field is initially NULL. */
MAIN_MINIPORT_FIELD(newp) = NULL;
/* Make new packet resemble the outside one. */
if (send)
{
PVOID media_info = NULL;
ULONG media_info_size = 0;
newp->Private.Head = packet->Private.Head;
newp->Private.Tail = packet->Private.Tail;
NdisGetPacketFlags(newp) = NdisGetPacketFlags(packet);
NdisSetPacketFlags(newp, NDIS_FLAGS_DONT_LOOPBACK);
// Copy the OOB Offset from the original packet to the new packet.
NdisMoveMemory(NDIS_OOB_DATA_FROM_PACKET(newp),
NDIS_OOB_DATA_FROM_PACKET(packet),
sizeof(NDIS_PACKET_OOB_DATA));
// Copy the per packet info into the new packet
NdisIMCopySendPerPacketInfo(newp, packet);
// Copy the Media specific information
NDIS_GET_PACKET_MEDIA_SPECIFIC_INFO(packet, &media_info, &media_info_size);
if (media_info != NULL || media_info_size != 0)
{
NDIS_SET_PACKET_MEDIA_SPECIFIC_INFO(newp, media_info, media_info_size);
}
}
else
{
newp->Private.Head = packet->Private.Head;
newp->Private.Tail = packet->Private.Tail;
// Get the original packet(it could be the same packet as one received or a different one
// based on # of layered MPs) and set it on the indicated packet so the OOB stuff is visible
// correctly at the top.
NDIS_SET_ORIGINAL_PACKET(newp, NDIS_GET_ORIGINAL_PACKET(packet));
NDIS_SET_PACKET_HEADER_SIZE(newp, NDIS_GET_PACKET_HEADER_SIZE(packet));
NdisGetPacketFlags(newp) = NdisGetPacketFlags(packet);
if (packet_lowmark)
NDIS_SET_PACKET_STATUS(newp, NDIS_STATUS_RESOURCES);
else
NDIS_SET_PACKET_STATUS(newp, NDIS_GET_PACKET_STATUS(packet));
}
/* Fill out reserved field. */
/* Sends should use ProtocolReserved and receives should use MiniportReserved. Buffer
space for MiniportReserved is allocated further up in this function. */
if (send) {
resp = MAIN_PROTOCOL_FIELD(newp);
} else {
MAIN_MINIPORT_FIELD(newp) = resp;
}
resp->type = MAIN_PACKET_TYPE_PASS;
resp->miscp = packet;
resp->data = 0;
resp->group = group;
resp->len = len;
return newp;
} /* end Main_packet_get*/
PNDIS_PACKET Main_packet_put (
PMAIN_CTXT ctxtp,
PNDIS_PACKET packet,
ULONG send,
NDIS_STATUS status)
{
PNDIS_PACKET oldp = NULL;
PNDIS_PACKET_STACK pktstk;
BOOLEAN stack_left;
PMAIN_PROTOCOL_RESERVED resp;
PMAIN_BUFFER bp;
PNDIS_BUFFER bufp;
MAIN_RESP_FIELD(packet, stack_left, pktstk, resp, send);
UNIV_ASSERT(resp);
/* Because CTRL packets are actually allocated on the receive path,
we need to change the send flag to false to get the logic right. */
if (resp->type == MAIN_PACKET_TYPE_CTRL)
{
UNIV_ASSERT(send);
send = FALSE;
}
if (send)
{
if (status == NDIS_STATUS_SUCCESS)
{
ctxtp->cntr_xmit_ok++;
switch (resp->group)
{
case MAIN_FRAME_DIRECTED:
ctxtp->cntr_xmit_frames_dir++;
ctxtp->cntr_xmit_bytes_dir += (ULONGLONG)(resp->len);
break;
case MAIN_FRAME_MULTICAST:
ctxtp->cntr_xmit_frames_mcast++;
ctxtp->cntr_xmit_bytes_mcast += (ULONGLONG)(resp->len);
break;
case MAIN_FRAME_BROADCAST:
ctxtp->cntr_xmit_frames_bcast++;
ctxtp->cntr_xmit_bytes_bcast += (ULONGLONG)(resp->len);
break;
default:
break;
}
}
else
{
ctxtp->cntr_xmit_err++;
}
}
else
{
if (status == NDIS_STATUS_SUCCESS)
{
ctxtp->cntr_recv_ok++;
switch (resp->group)
{
case MAIN_FRAME_DIRECTED:
ctxtp->cntr_recv_frames_dir++;
ctxtp->cntr_recv_bytes_dir += (ULONGLONG)(resp->len);
break;
case MAIN_FRAME_MULTICAST:
ctxtp->cntr_recv_frames_mcast++;
ctxtp->cntr_recv_bytes_mcast += (ULONGLONG)(resp->len);
break;
case MAIN_FRAME_BROADCAST:
ctxtp->cntr_recv_frames_bcast++;
ctxtp->cntr_recv_bytes_bcast += (ULONGLONG)(resp->len);
break;
default:
break;
}
}
else
{
ctxtp->cntr_recv_err++;
}
}
/* If this is our packet and buffers. */
if (resp->type == MAIN_PACKET_TYPE_CTRL)
{
/* Strip buffers from the packet buffer chain. */
NdisUnchainBufferAtFront(packet, &bufp);
/* Grab the buffer pointer from the private packet data. */
bp = (PMAIN_BUFFER)resp->miscp;
UNIV_ASSERT(bp->code == MAIN_BUFFER_CODE);
NdisAcquireSpinLock(&ctxtp->buf_lock);
/* Decrement the number of outstanding buffers. */
ctxtp->num_bufs_out--;
/* Put the buffer back on our free buffer list. */
InsertTailList(&ctxtp->buf_list, &bp->link);
NdisReleaseSpinLock(&ctxtp->buf_lock);
}
else
{
UNIV_ASSERT_VAL(resp->type == MAIN_PACKET_TYPE_PASS || resp->type == MAIN_PACKET_TYPE_TRANSFER, resp->type);
oldp = (PNDIS_PACKET)resp->miscp;
/* If the old packet is the same as this packet, then we were using
NDIS packet stacking to hold our private data. In this case, we
ALWAYS need to free the resp buffer back to our list. */
if (oldp == packet)
{
/* Since we re-use these private data buffers, re-initialize the packet type. */
resp->type = MAIN_PACKET_TYPE_NONE;
NdisFreeToNPagedLookasideList(&ctxtp->resp_list, resp);
/* Return the packet. */
return packet;
}
if (send)
{
/* Copy the send complete information from the packet to the original packet. */
NdisIMCopySendCompletePerPacketInfo(oldp, packet);
}
/* It used to be that if a new packet was allocated, we always used
protocol reseved, so there was never a need to free our private
buffer (it was part of the packet itself). However, now in the
case where packet != oldp (we allocated a new packet) we may have
to free the private data buffer. If we allocate a packet on the
send path, we play the part of protocol and use the protocol
reserved field, which is the former behavior. However, if we
allocate a packet on the receive path, we pull a resp buffer off
of our lookaside list and store a pointer in the miniport reserved
field of the packet. Therefore, if this is the completion of a
receive, free the private buffer. */
else
{
/* Since we re-use these private data buffers, re-initialize the packet type. */
resp->type = MAIN_PACKET_TYPE_NONE;
NdisFreeToNPagedLookasideList(&ctxtp->resp_list, resp);
}
}
/* These conters ONLY count outstanding allocated packets - for resource tracking. */
if (send)
NdisInterlockedDecrement(&ctxtp->num_sends_out);
else
NdisInterlockedDecrement(&ctxtp->num_recvs_out);
/* Re-initialize our packet. */
NdisReinitializePacket(packet);
/* Free our packet back to the pool. */
NdisFreePacket(packet);
/* Return oldp, which should indicate to the caller (the fact that packet != oldp)
that oldp, which is the original packet, should be "returned" to miniport. */
return oldp;
}
/*
* Function: Main_purge_connection_state
* Desctription: This function is called as the result of a work item callback and
* is used to clean out potentially stale connection descriptors. This
* must be done in an NDIS work item because many operations herein
* MUST occur at <= PASSIVE_LEVEL, so we can't do this inline, where
* much of our code runs at DISPATCH_LEVEL.
* Parameters: pWorkItem - the NDIS work item pointer
* nlbctxt - the context for the callback; this is our MAIN_CTXT pointer
* Returns: Nothing
* Author: shouse, 10.4.01
* Notes: Note that the code that sets up this work item MUST increment the reference
* count on the adapter context BEFORE adding the work item to the queue. This
* ensures that when this callback is executed, the context will stiil be valid,
* even if an unbind operation is pending. This function must free the work
* item memory and decrement the reference count - both, whether this function
* can successfully complete its task OR NOT.
*/
VOID Main_purge_connection_state (PNDIS_WORK_ITEM pWorkItem, PVOID nlbctxt) {
KIRQL Irql;
ULONG ServerIP;
ULONG ServerPort;
ULONG ClientIP;
ULONG ClientPort;
USHORT Protocol;
BOOLEAN Success = FALSE;
ULONG Count = 0;
NTSTATUS Status;
UNICODE_STRING Driver;
OBJECT_ATTRIBUTES Attrib;
IO_STATUS_BLOCK IOStatusBlock;
HANDLE TCPHandle;
PMAIN_CTXT ctxtp = (PMAIN_CTXT)nlbctxt;
TRACE_VERB("%!FUNC! Enter: Cleaning out stale connection descriptors, ctxtp = %p", ctxtp);
/* Do a sanity check on the context - make sure that the MAIN_CTXT code is correct. */
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Might as well free the work item now - we don't need it. */
if (pWorkItem)
NdisFreeMemory(pWorkItem, sizeof(NDIS_WORK_ITEM), 0);
/* This shouldn't happen, but protect against it anyway - we cannot manipulate
the registry if we are at an IRQL > PASSIVE_LEVEL, so bail out. */
if ((Irql = KeGetCurrentIrql()) > PASSIVE_LEVEL) {
TRACE_CRIT("%!FUNC! Error: IRQL (%u) > PASSIVE_LEVEL (%u) ... Exiting...", Irql, PASSIVE_LEVEL);
goto exit;
}
/* Initialize the device driver device string. */
RtlInitUnicodeString(&Driver, DD_TCP_DEVICE_NAME);
InitializeObjectAttributes(&Attrib, &Driver, OBJ_CASE_INSENSITIVE, NULL, NULL);
/* Open a handle to the device. */
Status = ZwCreateFile(&TCPHandle, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, &Attrib, &IOStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0);
if (!NT_SUCCESS(Status)) {
/* We're running at PASSIVE_LEVEL, so %ls is ok. */
TRACE_CRIT("%!FUNC! Error: Unable to open %ls, error = 0x%08x", DD_TCP_DEVICE_NAME, Status);
goto exit;
}
/* Get the IP tuple information for the descriptor at the head of the recovery queue from the load module. */
Success = Main_conn_get(ctxtp, &ServerIP, &ServerPort, &ClientIP, &ClientPort, &Protocol);
/* As long as there are descriptors to check, at we have serviced the maximum number
allowed, continue to check descriptors for validity. */
while (Success && (Count < CVY_DEF_DSCR_PURGE_LIMIT)) {
switch (Protocol) {
case TCPIP_PROTOCOL_TCP:
case TCPIP_PROTOCOL_PPTP:
{
TCP_FINDTCB_REQUEST Request;
TCP_FINDTCB_RESPONSE Response;
TRACE_VERB("%!FUNC! Querying TCP for the state of: %u.%u.%u.%u:%u <-> %u.%u.%u.%u:%u",
IP_GET_OCTET(ServerIP, 0), IP_GET_OCTET(ServerIP, 1), IP_GET_OCTET(ServerIP, 2), IP_GET_OCTET(ServerIP, 3), ServerPort,
IP_GET_OCTET(ClientIP, 0), IP_GET_OCTET(ClientIP, 1), IP_GET_OCTET(ClientIP, 2), IP_GET_OCTET(ClientIP, 3), ClientPort);
/* If this is a TCP connection and we're not cleaning up TCP connection state, bail out now. */
if (!NLB_TCP_CLEANUP_ON()) {
/* We are not purging this type of descriptor, so move on. */
TRACE_VERB("%!FUNC! TCP connection purging disabled");
/* Sanction the existing descriptor by moving to the tail of the recovery queue. Note that this too can
fail if the connection has been torn-down since we got this information from the load module. */
Success = Main_conn_sanction(ctxtp, ServerIP, ServerPort, ClientIP, ClientPort, Protocol);
if (Success) {
TRACE_VERB("%!FUNC! Descriptor sanctioned by default");
} else {
TRACE_VERB("%!FUNC! Unable to sanction descriptor - not found or in time-out");
}
break;
}
/* Fill in the request structure - ports are USHORTs and must be converted to network byte order. */
Request.Src = ServerIP;
Request.Dest = ClientIP;
Request.SrcPort = HTONS(ServerPort);
Request.DestPort = HTONS(ClientPort);
/* Send an IOCTL to the TCP driver asking it for the TCB information for the associated IP tuple. */
Status = ZwDeviceIoControlFile(TCPHandle, NULL, NULL, NULL, &IOStatusBlock, IOCTL_TCP_FINDTCB, &Request, sizeof(Request), &Response, sizeof(Response));
switch (Status) {
case STATUS_NOT_FOUND:
/* TCP does not have state for this IP tuple - must be we've gotten out of SYNC. */
TRACE_VERB("%!FUNC! ZwDeviceIoControlFile returned STATUS_NOT_FOUND (%08x)", Status);
/* Destroy our descriptor if TCP has no state for this connection. Note that this CAN fail if the
descriptor has disappeared since we queried the load module for the tuple information because
we cannot hold any locks while we query TCP. Further, it is also possible, yet unlikely, that
the connection has gone away since we got the information from the load module AND re-established
itself since TCP told us it had no state for this tuple; in that case, we'll be destroying a valid
connection descriptor here, but since its VERY unlikely, we'll live with it. */
#if defined (NLB_TCP_NOTIFICATION)
if (NLB_NOTIFICATIONS_ON())
{
Success = Main_conn_down(ServerIP, ServerPort, ClientIP, ClientPort, Protocol, CVY_CONN_RESET);
}
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
Success = Main_conn_notify(ctxtp, ServerIP, ServerPort, ClientIP, ClientPort, Protocol, CVY_CONN_RESET, NLB_FILTER_HOOK_PROCEED_WITH_HASH);
#else
Success = Main_conn_notify(ctxtp, ServerIP, ServerPort, ClientIP, ClientPort, Protocol, CVY_CONN_RESET);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
if (Success) {
/* Increment the count of connections we've had to purge because we got out of sync with TCP/IP.
Now that we're getting explicit notifications from TCP/IP, we expect this number to remain
VERY close to zero. Because of timing conditions between getting the information from the
load module, querying TCP/IP and subsequently destroying the state, we only want to increment
this counter in cases where we actually SUCCEEDED in destroying TCP connection state. */
ctxtp->num_purged++;
TRACE_VERB("%!FUNC! Descriptor destroyed - no upper layer state exists");
} else {
TRACE_VERB("%!FUNC! Unable to destroy descriptor - not found");
}
break;
case STATUS_SUCCESS:
/* TCP has an active connection matching this IP tuple - this should be the overwhelmingly common case. */
TRACE_VERB("%!FUNC! ZwDeviceIoControlFile returned STATUS_SUCCESS (%08x)", Status);
/* Sanction the existing descriptor by moving to the tail of the recovery queue. Note that this too can
fail if the connection has been torn-down since we got this information from the load module. */
Success = Main_conn_sanction(ctxtp, ServerIP, ServerPort, ClientIP, ClientPort, Protocol);
if (Success) {
TRACE_VERB("%!FUNC! Descriptor sanctioned - upper layer state exists");
} else {
TRACE_VERB("%!FUNC! Unable to sanction descriptor - not found or in time-out");
}
break;
case STATUS_INVALID_PARAMETER:
/* There was a parameter error. Loop around and try again. */
TRACE_VERB("%!FUNC! ZwDeviceIoControlFile returned STATUS_INVALID_PARAMETER (%08x)", Status);
break;
default:
/* Something else went wrong. Loop around and try again. */
TRACE_VERB("%!FUNC! ZwDeviceIoControlFile returned UNKNOWN (%08x)", Status);
break;
}
break;
}
default:
/* We cannot purge this type of descriptor, so move on. */
TRACE_VERB("%!FUNC! Cannot purge protocol %u descriptors", Protocol);
/* Sanction the existing descriptor by moving to the tail of the recovery queue. Note that this too can
fail if the connection has been torn-down since we got this information from the load module. */
Success = Main_conn_sanction(ctxtp, ServerIP, ServerPort, ClientIP, ClientPort, Protocol);
if (Success) {
TRACE_VERB("%!FUNC! Descriptor sanctioned by default");
} else {
TRACE_VERB("%!FUNC! Unable to sanction descriptor - not found or in time-out");
}
break;
}
/* Increment the number of descriptors serviced in this work item. */
Count++;
/* Get the IP tuple information for the descriptor at the head of the recovery queue from the load module. */
Success = Main_conn_get(ctxtp, &ServerIP, &ServerPort, &ClientIP, &ClientPort, &Protocol);
}
/* Close the TCP device handle. */
ZwClose(TCPHandle);
exit:
/* Release the reference on the context - this reference count was incremented
by the code that setup this work item callback. */
Main_release_reference(ctxtp);
TRACE_VERB("%!FUNC! Exit");
return;
}
#if defined (NLB_HOOK_TEST_ENABLE)
VOID Main_deregister_callback1 (PWCHAR hook, HANDLE registrar, ULONG flags)
{
TRACE_INFO("Deregistering test hook 1...");
return;
}
VOID Main_deregister_callback2 (PWCHAR hook, HANDLE registrar, ULONG flags)
{
TRACE_INFO("Deregistering test hook 2...");
return;
}
NLB_FILTER_HOOK_DIRECTIVE Main_test_hook1 (
const WCHAR * pAdapter, /* The GUID of the adapter on which the packet was sent/received. */
const NDIS_PACKET * pPacket, /* A pointer to the Ndis packet, which CAN be NULL if not available. */
const UCHAR * pMediaHeader, /* A pointer to the media header (ethernet, since NLB supports only ethernet). */
ULONG cMediaHeaderLength, /* The length of contiguous memory accessible from the media header pointer. */
const UCHAR * pPayload, /* A pointer to the payload of the packet. */
ULONG cPayloadLength, /* The length of contiguous memory accesible from the payload pointer. */
ULONG Flags /* Hook-related flags including whether or not the cluster is stopped. */
)
{
TRACE_INFO("%!FUNC! Inside test hook 1");
if (Flags & NLB_FILTER_HOOK_FLAGS_DRAINING)
{
TRACE_INFO("%!FUNC! This host is DRAINING...");
}
else if (Flags & NLB_FILTER_HOOK_FLAGS_STOPPED)
{
TRACE_INFO("%!FUNC! This host is STOPPED...");
}
return NLB_FILTER_HOOK_PROCEED_WITH_HASH;
}
NLB_FILTER_HOOK_DIRECTIVE Main_test_hook2 (
const WCHAR * pAdapter, /* The GUID of the adapter on which the packet was sent/received. */
const NDIS_PACKET * pPacket, /* A pointer to the Ndis packet, which CAN be NULL if not available. */
const UCHAR * pMediaHeader, /* A pointer to the media header (ethernet, since NLB supports only ethernet). */
ULONG cMediaHeaderLength, /* The length of contiguous memory accessible from the media header pointer. */
const UCHAR * pPayload, /* A pointer to the payload of the packet. */
ULONG cPayloadLength, /* The length of contiguous memory accesible from the payload pointer. */
ULONG Flags /* Hook-related flags including whether or not the cluster is stopped. */
)
{
TRACE_INFO("%!FUNC! Inside test hook 2");
if (Flags & NLB_FILTER_HOOK_FLAGS_DRAINING)
{
TRACE_INFO("%!FUNC! This host is DRAINING...");
}
else if (Flags & NLB_FILTER_HOOK_FLAGS_STOPPED)
{
TRACE_INFO("%!FUNC! This host is STOPPED...");
}
return NLB_FILTER_HOOK_PROCEED_WITH_HASH;
}
NLB_FILTER_HOOK_DIRECTIVE Main_test_query_hook (
const WCHAR * pAdapter, /* The GUID of the adapter on which the packet was sent/received. */
ULONG ServerIPAddress, /* The server IP address of the "packet" in NETWORK byte order. */
USHORT ServerPort, /* The server port of the "packet" (if applicable to the Protocol) in HOST byte order. */
ULONG ClientIPAddress, /* The client IP address of the "packet" in NETWORK byte order. */
USHORT ClientPort, /* The client port of the "packet" (if applicable to the Protocol) in HOST byte order. */
UCHAR Protocol, /* The IP protocol of the "packet"; TCP, UDP, ICMP, GRE, etc. */
BOOLEAN ReceiveContext, /* A boolean to indicate whether the packet is being processed in send or receive context. */
ULONG Flags /* Hook-related flags including whether or not the cluster is stopped. */
)
{
TRACE_INFO("%!FUNC! Inside test query hook");
if (Flags & NLB_FILTER_HOOK_FLAGS_DRAINING)
{
TRACE_INFO("%!FUNC! This host is DRAINING...");
}
else if (Flags & NLB_FILTER_HOOK_FLAGS_STOPPED)
{
TRACE_INFO("%!FUNC! This host is STOPPED...");
}
return NLB_FILTER_HOOK_PROCEED_WITH_HASH;
}
VOID Main_test_hook_register (HANDLE NLBHandle, IO_STATUS_BLOCK IOBlock, NLBReceiveFilterHook pfnHook, NLBHookDeregister pfnDereg, BOOLEAN deregister)
{
NLB_IOCTL_REGISTER_HOOK_REQUEST Request;
NLB_FILTER_HOOK_TABLE HookTable;
NTSTATUS Status;
static ULONG Failure = 0;
/* Set the hook identifier to the filter hook GUID. */
NdisMoveMemory(Request.HookIdentifier, NLB_FILTER_HOOK_INTERFACE, sizeof(WCHAR) * wcslen(NLB_FILTER_HOOK_INTERFACE));
Request.HookIdentifier[NLB_HOOK_IDENTIFIER_LENGTH] = 0;
/* Set the owner to our open handle on the device driver. */
Request.RegisteringEntity = NLBHandle;
/* Just print some debug information. */
if (pfnHook == Main_test_hook1) {
if (deregister) {
TRACE_INFO("%!FUNC! De-registering test hook 1");
} else {
TRACE_INFO("%!FUNC! Registering test hook 1");
}
} else {
if (deregister) {
TRACE_INFO("%!FUNC! De-registering test hook 2");
} else {
TRACE_INFO("%!FUNC! Registering test hook 2");
}
}
/* Set function pointers appropriately. */
if (deregister) {
/* If this is a de-register operation, we need to set the hook
function table pointer to NULL. */
Request.DeregisterCallback = NULL;
Request.HookTable = NULL;
} else {
/* Otherwise, we need to set the hook table to the address of a hook
table that we've allocated and fill in the function pointers. */
Request.DeregisterCallback = pfnDereg;
Request.HookTable = &HookTable;
/* Alternate between send and receive. */
if (Failure % 2) {
HookTable.SendHook = NULL;
HookTable.ReceiveHook = pfnHook;
HookTable.QueryHook = Main_test_query_hook;
} else {
HookTable.SendHook = pfnHook;
HookTable.ReceiveHook = NULL;
HookTable.QueryHook = Main_test_query_hook;
}
}
switch (Failure) {
case 0:
case 1:
case 3:
case 4:
case 6:
case 9:
case 12:
case 11: /* No failure - proceed as per usual. */
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), NULL, 0);
break;
case 2: /* Invalid GUID. */
NdisMoveMemory(Request.HookIdentifier, L"{57321019-f4d9-42bc-a651-15a0e1d259ac}", sizeof(WCHAR) * wcslen(L"{57321019-f4d9-42bc-a651-15a0e1d259ac}"));
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), NULL, 0);
UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
case 5: /* No de-register callback function, if a register operation. */
if (!deregister) Request.DeregisterCallback = NULL;
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), NULL, 0);
if (!deregister) UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
case 7: /* No hooks provided, if a register operation. */
if (!deregister) { HookTable.ReceiveHook = NULL; HookTable.SendHook = NULL; }
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), NULL, 0);
if (!deregister) UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
case 8: /* Invalid input buffer size. */
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &HookTable, sizeof(HookTable), NULL, 0);
UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
case 10: /* Invalid output buffer size. */
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), &Request, sizeof(Request));
UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
case 13: /* No query hook provided, if a register operation. */
if (!deregister) HookTable.QueryHook = NULL;
/* Send the request to the NLB device driver. */
Status = ZwDeviceIoControlFile(NLBHandle, NULL, NULL, NULL, &IOBlock, NLB_IOCTL_REGISTER_HOOK, &Request, sizeof(Request), NULL, 0);
if (!deregister) UNIV_ASSERT(Status == STATUS_INVALID_PARAMETER);
break;
}
Failure = (Failure++) % 14;
/* Print the return code. */
switch (Status) {
case STATUS_SUCCESS:
TRACE_INFO("%!FUNC! Success");
break;
case STATUS_INVALID_PARAMETER:
TRACE_INFO("%!FUNC! Failed - invalid parameter");
break;
case STATUS_ACCESS_DENIED:
TRACE_INFO("%!FUNC! Failed - access denied");
break;
default:
TRACE_INFO("%!FUNC! Unknown status");
break;
}
}
VOID Main_hook_thread1 (PNDIS_WORK_ITEM pWorkItem, PVOID nlbctxt)
{
static ULONG job1 = 0;
static BOOLEAN bOpen1 = FALSE;
static HANDLE NLBHandle1;
static IO_STATUS_BLOCK IOStatusBlock1;
PMAIN_CTXT ctxtp = (PMAIN_CTXT)nlbctxt;
KIRQL Irql;
/* Do a sanity check on the context - make sure that the MAIN_CTXT code is correct. */
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Might as well free the work item now - we don't need it. */
if (pWorkItem)
NdisFreeMemory(pWorkItem, sizeof(NDIS_WORK_ITEM), 0);
/* This shouldn't happen, but protect against it anyway - we cannot manipulate
the registry if we are at an IRQL > PASSIVE_LEVEL, so bail out. */
if ((Irql = KeGetCurrentIrql()) > PASSIVE_LEVEL) {
UNIV_PRINT_CRIT(("Main_hook_thread1: Error: IRQL (%u) > PASSIVE_LEVEL (%u) ... Exiting...", Irql, PASSIVE_LEVEL));
TRACE_CRIT("%!FUNC! Error: IRQL (%u) > PASSIVE_LEVEL (%u) ... Exiting...", Irql, PASSIVE_LEVEL);
goto exit;
}
/* If we haven't yet successfully opened a handle to the NLB device driver, do so now. */
if (!bOpen1) {
NTSTATUS Status;
UNICODE_STRING NLBDriver;
OBJECT_ATTRIBUTES Attrib;
/* Initialize the NLB driver device string, \Device\WLBS. */
RtlInitUnicodeString(&NLBDriver, NLB_DEVICE_NAME);
InitializeObjectAttributes(&Attrib, &NLBDriver, OBJ_CASE_INSENSITIVE, NULL, NULL);
/* Open a handle to the NLB device. */
Status = ZwCreateFile(&NLBHandle1, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, &Attrib, &IOStatusBlock1, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0);
/* If it failed, then bail out - something's wrong. */
if (!NT_SUCCESS(Status)) {
UNIV_PRINT_CRIT(("Main_hook_thread1: Error: Unable to open \\Device\\WLBS, status = 0x%08x", Status));
TRACE_CRIT("%!FUNC! Error: Unable to open \\Device\\WLBS, status = 0x%08x", Status);
goto exit;
}
/* Note that we succeeded so we don't try again later. */
bOpen1 = TRUE;
}
/* Attempt one of 5 operations - either registrations or de-registrations. */
switch (job1) {
case 0:
/* De-register. */
Main_test_hook_register(NLBHandle1, IOStatusBlock1, Main_test_hook1, Main_deregister_callback1, TRUE);
break;
case 1:
/* Register. */
Main_test_hook_register(NLBHandle1, IOStatusBlock1, Main_test_hook1, Main_deregister_callback1, FALSE);
break;
case 2:
/* Register. */
Main_test_hook_register(NLBHandle1, IOStatusBlock1, Main_test_hook1, Main_deregister_callback1, FALSE);
break;
case 3:
/* De-register. */
Main_test_hook_register(NLBHandle1, IOStatusBlock1, Main_test_hook1, Main_deregister_callback1, TRUE);
break;
case 4:
/* Register. */
Main_test_hook_register(NLBHandle1, IOStatusBlock1, Main_test_hook1, Main_deregister_callback1, FALSE);
break;
}
job1 = (job1++) % 5;
exit:
/* Release the reference on the context - this reference count was incremented
by the code that setup this work item callback. */
Main_release_reference(ctxtp);
return;
}
VOID Main_hook_thread2 (PNDIS_WORK_ITEM pWorkItem, PVOID nlbctxt)
{
static ULONG job2 = 0;
static HANDLE NLBHandle2;
static BOOLEAN bOpen2 = FALSE;
static IO_STATUS_BLOCK IOStatusBlock2;
PMAIN_CTXT ctxtp = (PMAIN_CTXT)nlbctxt;
KIRQL Irql;
/* Do a sanity check on the context - make sure that the MAIN_CTXT code is correct. */
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* Might as well free the work item now - we don't need it. */
if (pWorkItem)
NdisFreeMemory(pWorkItem, sizeof(NDIS_WORK_ITEM), 0);
/* This shouldn't happen, but protect against it anyway - we cannot manipulate
the registry if we are at an IRQL > PASSIVE_LEVEL, so bail out. */
if ((Irql = KeGetCurrentIrql()) > PASSIVE_LEVEL) {
UNIV_PRINT_CRIT(("Main_hook_thread2: Error: IRQL (%u) > PASSIVE_LEVEL (%u) ... Exiting...", Irql, PASSIVE_LEVEL));
TRACE_CRIT("%!FUNC! Error: IRQL (%u) > PASSIVE_LEVEL (%u) ... Exiting...", Irql, PASSIVE_LEVEL);
goto exit;
}
/* If we haven't yet successfully opened a handle to the NLB device driver, do so now. */
if (!bOpen2) {
NTSTATUS Status;
UNICODE_STRING NLBDriver;
OBJECT_ATTRIBUTES Attrib;
/* Initialize the NLB driver device string, \Device\WLBS. */
RtlInitUnicodeString(&NLBDriver, NLB_DEVICE_NAME);
InitializeObjectAttributes(&Attrib, &NLBDriver, OBJ_CASE_INSENSITIVE, NULL, NULL);
/* Open a handle to the NLB device. */
Status = ZwCreateFile(&NLBHandle2, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, &Attrib, &IOStatusBlock2, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0);
/* If it failed, then bail out - something's wrong. */
if (!NT_SUCCESS(Status)) {
UNIV_PRINT_CRIT(("Main_hook_thread2: Error: Unable to open \\Device\\WLBS, status = 0x%08x", Status));
TRACE_CRIT("%!FUNC! Error: Unable to open \\Device\\WLBS, status = 0x%08x", Status);
goto exit;
}
/* Note that we succeeded so we don't try again later. */
bOpen2 = TRUE;
}
/* Attempt one of 5 operations - either registrations or de-registrations. */
switch (job2) {
case 0:
/* De-register. */
Main_test_hook_register(NLBHandle2, IOStatusBlock2, Main_test_hook2, Main_deregister_callback2, TRUE);
break;
case 1:
/* De-register. */
Main_test_hook_register(NLBHandle2, IOStatusBlock2, Main_test_hook2, Main_deregister_callback2, TRUE);
break;
case 2:
/* Register. */
Main_test_hook_register(NLBHandle2, IOStatusBlock2, Main_test_hook2, Main_deregister_callback2, FALSE);
break;
case 3:
/* Register. */
Main_test_hook_register(NLBHandle2, IOStatusBlock2, Main_test_hook2, Main_deregister_callback2, FALSE);
break;
case 4:
/* De-register. */
Main_test_hook_register(NLBHandle2, IOStatusBlock2, Main_test_hook2, Main_deregister_callback2, TRUE);
break;
}
job2 = (job2++) % 5;
exit:
/* Release the reference on the context - this reference count was incremented
by the code that setup this work item callback. */
Main_release_reference(ctxtp);
return;
}
VOID Main_test_hook (PMAIN_CTXT ctxtp)
{
/* Schedule a couple of threads to stress the hook registration and de-registration paths. */
(VOID)Main_schedule_work_item(ctxtp, Main_hook_thread1);
(VOID)Main_schedule_work_item(ctxtp, Main_hook_thread2);
}
#endif
VOID Main_age_identity_cache(
PMAIN_CTXT ctxtp)
{
ULONG i = 0;
for (i=0; i < CVY_MAX_HOSTS; i++)
{
ULONG dip = NULL_VALUE;
BOOL fSetDip = (BOOL) ctxtp->params.identity_enabled;
if (ctxtp->identity_cache[i].ttl > 0)
{
UNIV_ASSERT(i == ctxtp->identity_cache[i].host_id);
if (ctxtp->identity_cache[i].ttl <= ctxtp->curr_tout)
{
UNIV_PRINT_INFO(("Main_age_identity_cache: cached entry for host_id [0-31] %u expired", i));
TRACE_INFO("%!FUNC! cached entry for host_id [0-31] %u expired", i);
ctxtp->identity_cache[i].ttl = 0;
fSetDip = TRUE;
}
else
{
ctxtp->identity_cache[i].ttl -= ctxtp->curr_tout;
dip = ctxtp->identity_cache[i].ded_ip_addr;
}
}
if (fSetDip)
{
/* Always set the dip if caching is currently enabled. Also set it if the ttl was valid when we began aging the cache entries. */
DipListSetItem(&ctxtp->dip_list, ctxtp->identity_cache[i].host_id, dip);
}
}
}
#ifdef PERIODIC_RESET
static ULONG countdown = 0;
ULONG resetting = FALSE;
#endif
VOID Main_ping (
PMAIN_CTXT ctxtp,
PULONG toutp)
{
ULONG len, conns;
BOOLEAN converging = FALSE;
PNDIS_PACKET packet;
NDIS_STATUS status;
BOOLEAN send_heartbeat = TRUE;
#ifdef PERIODIC_RESET /* enable this to simulate periodic resets */
if (countdown++ == 15)
{
NDIS_STATUS status;
resetting = TRUE;
NdisReset (& status, ctxtp -> mac_handle);
if (status == NDIS_STATUS_SUCCESS)
resetting = FALSE;
countdown = 0;
}
#endif
/* If unbind handler has been called, return here */
if (ctxtp -> unbind_handle)
{
return;
}
#if defined (NLB_HOOK_TEST_ENABLE)
/* Aritifically test the hook by spawning a couple of "threads" to
do register and de-register operations on the filter hook. */
Main_test_hook(ctxtp);
#endif
/* The master adapter must check the consistency of its team's configuration
and appropriately activate/de-activate the team once per timeout. */
Main_teaming_check_consistency(ctxtp);
/* count down time left for blocking ARPs */
NdisAcquireSpinLock (& ctxtp -> load_lock); /* V1.0.3 */
/* Age out the local cache of identities. This must run even if identity
heartbeats are disabled so we can age out entries after we turn it
off. */
Main_age_identity_cache(ctxtp);
if (*toutp > univ_changing_ip)
univ_changing_ip = 0;
else
univ_changing_ip -= *toutp;
converging = Load_timeout (& ctxtp -> load, toutp, & conns);
/* moving release to the end of the function locks up one of the testbeds.
guessing because of some reentrancy problem with NdisSend that is being
called by ctxtp_frame_send */
if (! ctxtp -> convoy_enabled && ! ctxtp -> stopping)
{
UNIV_ASSERT (! ctxtp -> draining);
NdisReleaseSpinLock (& ctxtp -> load_lock); /* V1.0.3 */
send_heartbeat = FALSE;
}
/* V2.1 if draining and no more connections - stop cluster mode */
if (send_heartbeat)
{
if (ctxtp->draining)
{
/* We check whether or not we are teaming without grabbing the global teaming
lock in an effort to minimize the common case - teaming is a special mode
of operation that is only really useful in a clustered firewall scenario.
So, if we don't think we're teaming, don't bother to check for sure, just
use our own load module and go with it. */
if (ctxtp->bda_teaming.active)
{
/* For BDA teaming, initialize load pointer, lock pointer, reverse hashing flag and teaming flag
assuming that we are not teaming. Main_teaming_acquire_load will change them appropriately. */
PLOAD_CTXT pLoad = &ctxtp->load;
PNDIS_SPIN_LOCK pLock = &ctxtp->load_lock;
BOOLEAN bRefused = FALSE;
BOOLEAN bTeaming = FALSE;
/* Temporarily release our load lock. Since we're draining, the only possible
side effect is that the connection count may decrease before we reacquire
the lock. This is not a big deal - we'll correct any discrepencies the
next time this timer expires. */
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Check the teaming configuration and add a reference to the load module before consulting the load module. */
bTeaming = Main_teaming_acquire_load(&ctxtp->bda_teaming, &pLoad, &pLock, &bRefused);
/* If we're part of a BDA team, we need to make sure that the number of active
connections we use when making a draining decision is based on the number of
active connections on the master, on which ALL connections for the team are
maintained. */
if (bTeaming)
{
/* If we are the team's master, then we don't need to squirrel around with
the connection count - continue to do things the way they always have
regarding draining. */
if (pLoad != &ctxtp->load)
{
/* If we're a slave, we depend on the master to know when to complete
draining; so long as the master is active, continue to drain.
NOTE: we are checking the "active" flag on the load module without
holding the load lock. In the case where all members of a team
are being drainstopped or stopped at roughly the same time (which
is a reasonable assumption), this shouldn't be a problem - we may
miss our chance to stop this time around, but we'll check again
during the next periodic timeout and we'll take care of it then. */
if (pLoad->active)
conns = 1;
/* Otherwise, if the master is stopped, we can transition to stopped as well. */
else
conns = 0;
}
/* If we were teaming, master or otherwise, release the reference on the
master's load module. */
Main_teaming_release_load(pLoad, pLock, bTeaming);
}
NdisAcquireSpinLock(&ctxtp->load_lock);
}
}
if (ctxtp -> draining && conns == 0 && ! converging)
{
IOCTL_CVY_BUF buf;
ctxtp -> draining = FALSE;
NdisReleaseSpinLock (& ctxtp -> load_lock);
Main_ctrl (ctxtp, IOCTL_CVY_CLUSTER_OFF, &buf, NULL, NULL, NULL);
}
else
NdisReleaseSpinLock (& ctxtp -> load_lock); /* V1.0.3 */
/* V2.1 clear stopping flag here since we are going to send out the frame
that will initiate convergence */
ctxtp -> stopping = FALSE;
}
/* Add the elapsed time to the descriptor cleanup timeout. */
ctxtp->conn_purge += ctxtp->curr_tout;
/* If its time to cleanup potentially stale connection descriptors, schedule a work item to do so. */
if (ctxtp->conn_purge >= CVY_DEF_DSCR_PURGE_INTERVAL) {
/* Reset the elapsed time since the last cleanup. */
ctxtp->conn_purge = 0;
/* Schedule an NDIS work item to clean out stale connection state. */
(VOID)Main_schedule_work_item(ctxtp, Main_purge_connection_state);
}
/* V1.3.2b */
if (! ctxtp -> media_connected || ! MAIN_PNP_DEV_ON(ctxtp))
{
return;
}
/* V1.1.2 do not send pings if the card below is resetting */
if (ctxtp -> reset_state != MAIN_RESET_NONE)
{
return;
}
if (send_heartbeat)
{
packet = Main_frame_get (ctxtp, & len, MAIN_PACKET_TYPE_PING);
if (packet == NULL)
{
UNIV_PRINT_CRIT(("Main_ping: Error getting frame packet"));
TRACE_CRIT("%!FUNC! Error getting frame packet failed");
}
else
{
NdisSend (& status, ctxtp -> mac_handle, packet);
if (status != NDIS_STATUS_PENDING)
Main_send_done (ctxtp, packet, status);
}
}
/* Check to see if igmp message needs to be sent out. If the cluster IP address is 0.0.0.0, we
don't want to join the multicast IGMP group. Likewise, in multicast or unicast mode. */
if (ctxtp -> params . mcast_support && ctxtp -> params . igmp_support && ctxtp -> params . cl_ip_addr != 0)
{
ctxtp -> igmp_sent += ctxtp -> curr_tout;
if (ctxtp -> igmp_sent >= CVY_DEF_IGMP_INTERVAL)
{
ctxtp -> igmp_sent = 0;
packet = Main_frame_get (ctxtp, & len, MAIN_PACKET_TYPE_IGMP);
if (packet == NULL)
{
UNIV_PRINT_CRIT(("Main_ping: Error getting igmp packet"));
TRACE_CRIT("%!FUNC! Error getting igmp packet failed");
}
else
{
NdisSend (& status, ctxtp -> mac_handle, packet);
if (status != NDIS_STATUS_PENDING)
Main_send_done (ctxtp, packet, status);
}
}
}
if (ctxtp->params.identity_enabled)
{
/* Check to see if it is time to send an identity heartbeat */
ctxtp->idhb_sent += ctxtp->curr_tout;
if (ctxtp->idhb_sent >= ctxtp->params.identity_period)
{
ctxtp->idhb_sent = 0;
packet = Main_frame_get (ctxtp, & len, MAIN_PACKET_TYPE_IDHB);
if (packet == NULL)
{
UNIV_PRINT_CRIT(("Main_ping: Error getting identity heartbeat packet"));
TRACE_CRIT("%!FUNC! Error getting identity heartbeat failed");
}
else
{
NdisSend (& status, ctxtp -> mac_handle, packet);
if (status != NDIS_STATUS_PENDING)
Main_send_done (ctxtp, packet, status);
}
}
}
} /* Main_ping */
VOID Main_send_done (
PVOID cp,
PNDIS_PACKET packetp,
NDIS_STATUS status)
{
PMAIN_CTXT ctxtp = (PMAIN_CTXT) cp;
PMAIN_FRAME_DSCR dscrp;
/* This function is only called for ping and IGMP messages, so
we can continue to allow it to access the protocol reserved field. */
PMAIN_PROTOCOL_RESERVED resp = MAIN_PROTOCOL_FIELD (packetp);
UNIV_ASSERT_VAL (resp -> type == MAIN_PACKET_TYPE_PING ||
resp -> type == MAIN_PACKET_TYPE_IGMP ||
resp -> type == MAIN_PACKET_TYPE_IDHB,
resp -> type);
/* attempt to see if this packet is part of our frame descriptor */
dscrp = (PMAIN_FRAME_DSCR) resp -> miscp;
if (status != NDIS_STATUS_SUCCESS)
UNIV_PRINT_CRIT(("Main_send_done: Error sending %x error 0x%x", resp -> type, status));
Main_frame_put (ctxtp, packetp, dscrp);
} /* end Main_send_done */
/*
* Function: Main_spoof_mac
* Description: This function spoofs the source/destination MAC address(es) of incoming
* and/or outgoing packets. Incoming packets in multicast mode must change
* the cluster multicast MAC to the NIC's permanent MAC address before sending
* the packet up the protocol stack. Outgoing packets in unicast mode must
* mask the source MAC address to prevent switches from learning the cluster
* MAC address and associating it with a particular switch port.
* Parameters: ctxtp - pointer the the main NLB context structure for this adapter.
* pPacketInfo - the previously parse packet information structure, which,
* among other things, contains a pointer to the MAC header.
* send - boolean indication of send v. receive.
* Returns: Nothing.
* Author: shouse, 3.4.02
* Notes:
*/
VOID Main_spoof_mac (PMAIN_CTXT ctxtp, PMAIN_PACKET_INFO pPacketInfo, ULONG send)
{
/* Cast the MAC header to a PUCHAR. */
PUCHAR memp = (PUCHAR)pPacketInfo->Ethernet.pHeader;
/* Be paranoid about the network medium. */
UNIV_ASSERT(ctxtp->medium == NdisMedium802_3);
UNIV_ASSERT(pPacketInfo->Medium == NdisMedium802_3);
/* If this cluster is in multicast mode, then check to see whether its necessary
to overwrite the cluster multicast MAC address in the packet. */
if (ctxtp->params.mcast_support)
{
/* Get the destination MAC address offset, in bytes. */
ULONG offset = CVY_MAC_DST_OFF(ctxtp->medium);
/* If this is a receive and the destination MAC address is the cluster
multicast MAC address, then replace it with the NIC's permanent MAC. */
if (!send && CVY_MAC_ADDR_COMP(ctxtp->medium, memp + offset, &ctxtp->cl_mac_addr))
CVY_MAC_ADDR_COPY(ctxtp->medium, memp + offset, &ctxtp->ded_mac_addr);
}
/* Otherwise, if the cluster is in unicast mode, and we're sending out a
packet, then check to see whether or not its necessary to mask the
cluster MAC address. */
else if (send && ctxtp->params.mask_src_mac)
{
/* Get the source MAC address offset, in bytes. */
ULONG offset = CVY_MAC_SRC_OFF(ctxtp->medium);
/* If the source MAC address is the cluster unicast MAC address, then we
have to mask the source MAC address, which we do by altering one byte
of the MAC address: 02-bf-xx-xx-xx-xx to 02-ID-xx-xx-xx-xx, where ID
is this host's host priority. Note: This really should be computed at
init time and simply copied here. */
if (CVY_MAC_ADDR_COMP(ctxtp->medium, memp + offset, &ctxtp->cl_mac_addr))
{
ULONG byte[4];
/* Set the LAA bit in the MAC address. */
CVY_MAC_ADDR_LAA_SET(ctxtp->medium, memp + offset);
/* Change the second byte to the host priority. */
*((PUCHAR)(memp + offset + 1)) = (UCHAR)ctxtp->params.host_priority;
/* Break the cluster IP address up in octets. */
IP_GET_ADDR(ctxtp->cl_ip_addr, &byte[0], &byte[1], &byte[2], &byte[3]);
/* Copy the cluster IP address octects into the other four bytes
of the source MAC address. */
*((PUCHAR)(memp + offset + 2)) = (UCHAR)byte[0];
*((PUCHAR)(memp + offset + 3)) = (UCHAR)byte[1];
*((PUCHAR)(memp + offset + 4)) = (UCHAR)byte[2];
*((PUCHAR)(memp + offset + 5)) = (UCHAR)byte[3];
// ctxtp->mac_modified++;
}
}
}
/*
* Function: Main_recv_frame_parse
* Description: This function parses an NDIS_PACKET and carefully extracts the information
* necessary to handle the packet. The information extracted includes pointers
* to all relevant headers and payloads as well as the packet type (EType), IP
* protocol (if appropriate), etc. This function does all necessary validation
* to ensure that all pointers are accessible for at least the specified number
* of bytes; i.e., if this function returns successfully, the pointer to the IP
* header is guaranteed to be accessible for at LEAST the length of the IP header.
* No contents inside headers or payloads is validated, except for header lengths,
* and special cases, such as NLB heartbeats or remote control packets. If this
* function returns unsuccessfully, the contents of MAIN_PACKET_INFO cannot be
* trusted and the packet should be discarded. See the definition of MAIN_PACKET_INFO
* in main.h for more specific indications of what fields are filled in and under
* what circumstances.
* Parameters: ctxtp - pointer to the main NLB context structure for this adapter.
* pPacket - pointer to an NDIS_PACKET.
* pPacketInfo - pointer to a MAIN_PACKET_INFO structure to hold the information
* parsed from the packet.
* Returns: BOOLEAN - TRUE if successful, FALSE if not.
* Author: shouse, 3.4.02
* Notes:
*/
BOOLEAN Main_recv_frame_parse (
PMAIN_CTXT ctxtp, /* the context for this adapter */
IN PNDIS_PACKET pPacket, /* pointer to an NDIS_PACKET */
OUT PMAIN_PACKET_INFO pPacketInfo /* pointer to an NLB packet information structure to hold the output */
)
{
PNDIS_BUFFER bufp = NULL;
PUCHAR memp = NULL;
PUCHAR hdrp = NULL;
ULONG len = 0;
UINT buf_len = 0;
UINT packet_len = 0;
ULONG curr_len = 0;
ULONG hdr_len = 0;
ULONG offset = 0;
UNIV_ASSERT(pPacket);
UNIV_ASSERT(pPacketInfo);
/* Store a pointer to the original packet. */
pPacketInfo->pPacket = pPacket;
/* By default, this packet does not require post filtering special attention. */
pPacketInfo->Operation = MAIN_FILTER_OP_NONE;
/* Ask NDIS for the first buffer (bufp), the virtual address of the beginning of that buffer,
(memp) the length of that buffer (buf_len) and the length of the entire packet (packet_len). */
NdisGetFirstBufferFromPacket(pPacket, &bufp, &memp, &buf_len, &packet_len);
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NdisGetFirstBufferFromPacket returned NULL!"));
TRACE_CRIT("%!FUNC! NdisGetFirstBufferFromPacket returned NULL!");
return FALSE;
}
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
UNIV_ASSERT(ctxtp->medium == NdisMedium802_3);
/* Get the destination MAC address offset, in bytes. */
offset = CVY_MAC_DST_OFF(ctxtp->medium);
/* The Ethernet header is memp, the beginning of the buffer and the length of contiguous
memory accessible from that pointer is buf_len - the entire buffer. */
pPacketInfo->Ethernet.pHeader = (PCVY_ETHERNET_HDR)memp;
pPacketInfo->Ethernet.Length = buf_len;
/* Note: NDIS will ensure that the buf_len is at least the size of an ethernet header,
so we don't have to check that here. Assert it, just in case. */
UNIV_ASSERT(buf_len >= sizeof(CVY_ETHERNET_HDR));
/* This variable accumulates the lengths of the headers that we've successfully "found". */
hdr_len = sizeof(CVY_ETHERNET_HDR);
/* Set the medium and retrieve the Ethernet packet type from the header. */
pPacketInfo->Medium = NdisMedium802_3;
pPacketInfo->Type = CVY_ETHERNET_ETYPE_GET(pPacketInfo->Ethernet.pHeader);
/* Categorize this packet as unicast, multicast or broadcast by looking at the destination
MAC address and store the "group" in the packet information structure. This is used
for statistical purposes later. */
if (!CVY_MAC_ADDR_MCAST(ctxtp->medium, (PUCHAR)pPacketInfo->Ethernet.pHeader + offset))
{
pPacketInfo->Group = MAIN_FRAME_DIRECTED;
}
else
{
if (CVY_MAC_ADDR_BCAST(ctxtp->medium, (PUCHAR)pPacketInfo->Ethernet.pHeader + offset))
pPacketInfo->Group = MAIN_FRAME_BROADCAST;
else
pPacketInfo->Group = MAIN_FRAME_MULTICAST;
}
/* A length indication from an NDIS_PACKET includes the MAC header,
so subtract that to get the length of the payload. */
pPacketInfo->Length = packet_len - hdr_len;
/* Hmmmm... If the packet length is larger than we expect, print a trace statement.
This is peculiar, but not impossible with hardware accelleration and IpLargeXmit.
This should probably NOT happen on a receive, however - just a send. */
if (pPacketInfo->Length > ctxtp->max_frame_size)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the packet (%u) is greater than the maximum size of a frame (%u)", pPacketInfo->Length, ctxtp->max_frame_size));
TRACE_CRIT("%!FUNC! Length of the packet (%u) is greater than the maximum size of a frame (%u)", pPacketInfo->Length, ctxtp->max_frame_size);
}
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, we expect to be able to successfully find the offset we're
looking for, so if we've run out of buffers, fail. */
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NdisGetNextBuffer returned NULL when more data was expected!"));
TRACE_CRIT("%!FUNC! NdisGetNextBuffer returned NULL when more data was expected!");
return FALSE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
}
/* The pointer to the header we're looking for is the beginning of the buffer,
plus the offset of the header within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the header begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Based on the packet type, enforce some restrictions and setup any further
information we need to find in the packet. */
switch (pPacketInfo->Type)
{
case TCPIP_IP_SIG: /* IP packets. */
/* If the contiguous memory accessible from the IP header is not at
LEAST the minimum length of an IP header, bail out now. */
if (len < sizeof(IP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the IP buffer (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR)));
TRACE_CRIT("%!FUNC! Length of the IP buffer (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR));
return FALSE;
}
/* Save a pointer to the IP header and its "length". */
pPacketInfo->IP.pHeader = (PIP_HDR)hdrp;
pPacketInfo->IP.Length = len;
/* Extract the IP protocol from the IP header. */
pPacketInfo->IP.Protocol = IP_GET_PROT(pPacketInfo->IP.pHeader);
/* Calculate the actual IP header length by extracting the hlen field
from the IP header and multiplying by the size of a DWORD. */
len = sizeof(ULONG) * IP_GET_HLEN(pPacketInfo->IP.pHeader);
/* If this calculated header length is not at LEAST the minimum IP
header length, bail out now. */
if (len < sizeof(IP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Calculated IP header length (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR)));
TRACE_CRIT("%!FUNC! Calculated IP header length (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR));
return FALSE;
}
#if 0 /* Because the options can be in separate buffers (at least in sends), don't bother to
enforce this condition; NLB never looks at the options anyway, so we don't really care. */
/* If the contiguous memory accessible from the IP header is not at
LEAST the calculated size of the IP header, bail out now. */
if (pPacketInfo->IP.Length < len)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the IP buffer (%u) is less than the size of the IP header (%u)", pPacketInfo->IP.Length, len));
TRACE_CRIT("%!FUNC! Length of the IP buffer (%u) is less than the size of the IP header (%u)", pPacketInfo->IP.Length, len);
return FALSE;
}
#endif
/* The total packet length, in bytes, specified in the header, which includes
both the IP header and payload, can be no more than the packet length that
NDIS told us, which is the entire network packet, minus the media header. */
if (IP_GET_PLEN(pPacketInfo->IP.pHeader) > pPacketInfo->Length)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: IP packet length (%u) is greater than the indicated packet length (%u)", IP_GET_PLEN(pPacketInfo->IP.pHeader), pPacketInfo->Length));
TRACE_CRIT("%!FUNC! IP packet length (%u) is greater than the indicated packet length (%u)", IP_GET_PLEN(pPacketInfo->IP.pHeader), pPacketInfo->Length);
return FALSE;
}
/* If this packet is a subsequent IP fragment, note that in the packet
information structure and leave now, successfully. */
if (IP_GET_FRAG_OFF(pPacketInfo->IP.pHeader) != 0)
{
pPacketInfo->IP.bFragment = TRUE;
return TRUE;
}
/* Otherwise, mark the packet as NOT a subsequent fragment and continue. */
else
{
pPacketInfo->IP.bFragment = FALSE;
}
/* Add the length of the IP header to the offset we're now looking
for in the packet; in this case, the TCP/UDP/etc. header. */
hdr_len += len;
break;
case MAIN_FRAME_SIG:
case MAIN_FRAME_SIG_OLD: /* Heartbeats. */
/* If the contiguous memory accessible from the heartbeat header is not at
LEAST the length of an NLB heartbeat header, bail out now. */
if (len < sizeof(MAIN_FRAME_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the PING buffer (%u) is less than the size of an PING header (%u)", len, sizeof(MAIN_FRAME_HDR)));
TRACE_CRIT("%!FUNC! Length of the PING buffer (%u) is less than the size of an PING header (%u)", len, sizeof(MAIN_FRAME_HDR));
return FALSE;
}
/* Save a pointer to the heartbeat header and its "length". */
pPacketInfo->Heartbeat.pHeader = (PMAIN_FRAME_HDR)hdrp;
pPacketInfo->Heartbeat.Length = len;
/* Verify the "magic number" in the heartbeat header. If it's corrupt,
bail out now. */
if (pPacketInfo->Heartbeat.pHeader->code != MAIN_FRAME_CODE &&
pPacketInfo->Heartbeat.pHeader->code != MAIN_FRAME_EX_CODE)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Wrong code found (%u) in PING header (%u)", pPacketInfo->Heartbeat.pHeader->code, MAIN_FRAME_CODE));
TRACE_CRIT("%!FUNC! Wrong code found (%u) in PING header (%u)", pPacketInfo->Heartbeat.pHeader->code, MAIN_FRAME_CODE);
return FALSE;
}
/* Old frame types don't support extended heartbeats */
if (pPacketInfo->Heartbeat.pHeader->code == MAIN_FRAME_EX_CODE &&
pPacketInfo->Type == MAIN_FRAME_SIG_OLD)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Extended heartbeats are not supported for the old frame type"));
TRACE_CRIT("%!FUNC! Extended heartbeats are not supported for the old frame type");
return FALSE;
}
/* Add the length of the heartbeat header to the offset we're now looking
for in the packet; in this case, the heartbeat payload. */
hdr_len += sizeof(MAIN_FRAME_HDR);
break;
case TCPIP_ARP_SIG: /* ARPs. */
/* If the contiguous memory accessible from the ARP header is not at
LEAST the length of an ARP header, bail out now. */
if (len < sizeof(ARP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the ARP buffer (%u) is less than the size of an ARP header (%u)", len, sizeof(ARP_HDR)));
TRACE_CRIT("%!FUNC! Length of the ARP buffer (%u) is less than the size of an ARP header (%u)", len, sizeof(ARP_HDR));
return FALSE;
}
/* Save a pointer to the ARP header and its "length". */
pPacketInfo->ARP.pHeader = (PARP_HDR)hdrp;
pPacketInfo->ARP.Length = len;
/* Nothing more to look for in an ARP. Leave now, successfully. */
return TRUE;
default: /* Any Ethernet type other than IP, NLB Heartbeat and ARP. */
/* Store a pointer to the unknown header and its "length". */
pPacketInfo->Unknown.pHeader = hdrp;
pPacketInfo->Unknown.Length = len;
/* Nothing more to look for in this packet. Leave now, successfully. */
return TRUE;
}
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, we expect to be able to successfully find the offset we're
looking for, so if we've run out of buffers, fail. */
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NdisGetNextBuffer returned NULL when more data was expected!"));
TRACE_CRIT("%!FUNC! NdisGetNextBuffer returned NULL when more data was expected!");
return FALSE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
}
/* The pointer to the header we're looking for is the beginning of the buffer,
plus the offset of the header within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the header begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Based on the packet type, enforce some restrictions and setup any further
information we need to find in the packet. */
switch (pPacketInfo->Type)
{
case MAIN_FRAME_SIG:
case MAIN_FRAME_SIG_OLD: /* Heartbeats. */
/* Make sure that the length of the buffer is at LEAST as large as an NLB heartbeat payload. */
if (pPacketInfo->Heartbeat.pHeader->code == MAIN_FRAME_CODE)
{
if (len < sizeof(PING_MSG))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the PING buffer (%u) is less than the size of a PING message (%u)", len, sizeof(PING_MSG)));
TRACE_CRIT("%!FUNC! Length of the PING buffer (%u) is less than the size of a PING message (%u)", len, sizeof(PING_MSG));
return FALSE;
}
/* Save a pointer to the heartbeat payload and its "length". */
pPacketInfo->Heartbeat.Payload.pPayload = (PPING_MSG)hdrp;
pPacketInfo->Heartbeat.Payload.Length = len;
}
else if (pPacketInfo->Heartbeat.pHeader->code == MAIN_FRAME_EX_CODE)
{
/* PPING_MSG_EX is a variable length structure so we compare this size against the smallest
allowed value which is sizeof(TLV_HEADER). */
if (len < sizeof(TLV_HEADER))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the received buffer (%u) is less than the size of an extended heartbeat message (%u)", len, sizeof(TLV_HEADER)));
TRACE_CRIT("%!FUNC! Length of the received buffer (%u) is less than the size of an extended heartbeat message (%u)", len, sizeof(TLV_HEADER));
return FALSE;
}
/* Save a pointer to the identity heartbeat payload and its "length". */
pPacketInfo->Heartbeat.Payload.pPayloadEx = (PTLV_HEADER) hdrp;
pPacketInfo->Heartbeat.Payload.Length = len;
}
/* Nothing more to look for in heartbeats. Leave now, successfully. */
return TRUE;
case TCPIP_IP_SIG: /* IP packets. */
/* For some protocols, we have more to look for in the packet. */
switch (pPacketInfo->IP.Protocol)
{
case TCPIP_PROTOCOL_TCP: /* TCP. */
/* If the contiguous memory accessible from the TCP header is not at
LEAST the minimum length of a TCP header, bail out now. */
if (len < sizeof(TCP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the TCP buffer (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR)));
TRACE_CRIT("%!FUNC! Length of the TCP buffer (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR));
return FALSE;
}
/* Save a pointer to the TCP header and its "length". */
pPacketInfo->IP.TCP.pHeader = (PTCP_HDR)hdrp;
pPacketInfo->IP.TCP.Length = len;
/* Calculate the actual TCP header length by extracting the hlen field
from the TCP header and multiplying by the size of a DWORD. */
len = sizeof(ULONG) * TCP_GET_HLEN(pPacketInfo->IP.TCP.pHeader);
/* If this calculated header length is not at LEAST the minimum TCP
header length, bail out now. */
if (len < sizeof(TCP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Calculated TCP header length (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR)));
TRACE_CRIT("%!FUNC! Calculated TCP header length (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR));
return FALSE;
}
#if 0 /* Because the options can be in separate buffers (at least in sends), don't bother to
enforce this condition; NLB never looks at the options anyway, so we don't really care. */
/* If the contiguous memory accessible from the TCP header is not at
LEAST the calculated size of the TCP header, bail out now. */
if (pPacketInfo->IP.TCP.Length < len)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the TCP buffer (%u) is less than the size of the TCP header (%u)", pPacketInfo->IP.TCP.Length, len));
TRACE_CRIT("%!FUNC! Length of the TCP buffer (%u) is less than the size of the TCP header (%u)", pPacketInfo->IP.TCP.Length, len);
return FALSE;
}
#endif
/* Add the length of the TCP header to the offset we're now looking
for in the packet; in this case, the TCP payload. */
hdr_len += len;
break;
case TCPIP_PROTOCOL_UDP:
/* If the contiguous memory accessible from the UDP header is not at
LEAST the length of a UDP header, bail out now. */
if (len < sizeof(UDP_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the UDP buffer (%u) is less than the size of an UDP header (%u)", len, sizeof(UDP_HDR)));
TRACE_CRIT("%!FUNC! Length of the UDP buffer (%u) is less than the size of an UDP header (%u)", len, sizeof(UDP_HDR));
return FALSE;
}
/* Save a pointer to the UDP header and its "length". */
pPacketInfo->IP.UDP.pHeader = (PUDP_HDR)hdrp;
pPacketInfo->IP.UDP.Length = len;
/* Add the length of the UDP header to the offset we're now looking
for in the packet; in this case, the UDP payload. */
hdr_len += sizeof(UDP_HDR);
break;
case TCPIP_PROTOCOL_GRE:
case TCPIP_PROTOCOL_IPSEC1:
case TCPIP_PROTOCOL_IPSEC2:
case TCPIP_PROTOCOL_ICMP:
default:
/* For any other IP protocol, we have nothing in particular to do.
Leave now, successfully. */
return TRUE;
}
break;
default:
return TRUE;
}
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, it is OK if we can't get to the payload of the packet. Not
all TCP/UDP packets will actually HAVE a payload (such as TCP SYNs), so if
we can't find it, leave successfully now, and the calling function will have
to check the pointer value for NULL before accessing the payload if necessary. */
if (bufp == NULL)
{
/* If this is a TCP packet, note the absence of the TCP payload. */
if (pPacketInfo->IP.Protocol == TCPIP_PROTOCOL_TCP)
{
pPacketInfo->IP.TCP.Payload.pPayload = NULL;
pPacketInfo->IP.TCP.Payload.Length = 0;
pPacketInfo->IP.TCP.Payload.pPayloadBuffer = NULL;
}
/* If this is a UDP packet, note the absence of the UDP payload. */
else if (pPacketInfo->IP.Protocol == TCPIP_PROTOCOL_UDP)
{
pPacketInfo->IP.UDP.Payload.pPayload = NULL;
pPacketInfo->IP.UDP.Payload.Length = 0;
pPacketInfo->IP.UDP.Payload.pPayloadBuffer = NULL;
}
return TRUE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
}
/* The pointer to the payload we're looking for is the beginning of the buffer,
plus the offset of the payload within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the payload begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Some special UDP and TCP packets require identification, so check to see whether
this particular packet is one of those special types; NLB remote control or NetBT. */
switch (pPacketInfo->IP.Protocol)
{
case TCPIP_PROTOCOL_TCP: /* TCP. */
/* If NetBT support is not enabled, then this is not an interesting NetBT packet
for sure. Otherwise, look to see if it is. */
if (ctxtp->params.nbt_support)
{
/* If this is a TCP data packet and its to the NBT session TCP port, mark it for
post-processing. Since this is a receive, the server information is the
destination. NBT packets will be non-control (SYN, FIN, RST) packets. */
if (!(TCP_GET_FLAGS(pPacketInfo->IP.TCP.pHeader) & (TCP_FLAG_SYN | TCP_FLAG_FIN | TCP_FLAG_RST)) &&
(TCP_GET_DST_PORT(pPacketInfo->IP.TCP.pHeader) == NBT_SESSION_PORT))
{
UNIV_ASSERT(len > 0);
/* We need to check the NetBT packet type field to make sure this is a
packet we're interested. The type is the first byte of a NetBT packet,
so if the payload length is AT LEAST one byte, check the NetBT packet
type and mark the packet if its a session request. If it is not a
session request, or there is no payload, do NOT mark the packet and
let TCP/IP deal with it. */
if ((NBT_GET_PKT_TYPE((PNBT_HDR)hdrp) == NBT_SESSION_REQUEST))
{
/* Found a NetBT session request. */
pPacketInfo->Operation = MAIN_FILTER_OP_NBT;
UNIV_PRINT_VERB(("Main_recv_frame_parse: Found an NBT packet - NBT session packet"));
TRACE_VERB("%!FUNC! Found an NBT packet - NBT session packet");
}
}
}
/* If we did find an interesting NetBT packet, make sure that any restrictions
on the packet contents are satisfied. */
if (pPacketInfo->Operation == MAIN_FILTER_OP_NBT)
{
/* We require that the entire NetBT header is contiguously accessible, or we will
not process it. Further, in the lookahead case, if not enough lookahead is
available to see the entire NetBT header, we will fail here and improperly
handle this NetBT packet. This CAN BE FIXED if necessary, but for now, it
appears that TCP/IPs lookahead requirement of 128 bytes will ensure that this
won't happen for now. */
if (len < sizeof(NBT_HDR))
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the NBT buffer (%u) is less than the size of an NBT header (%u)", len, sizeof(NBT_HDR)));
TRACE_CRIT("%!FUNC! Length of the NBT buffer (%u) is less than the size of an NBT header (%u)", len, sizeof(NBT_HDR));
return FALSE;
}
}
/* Save a pointer to the TCP payload and its "length". */
pPacketInfo->IP.TCP.Payload.pPayload = hdrp;
pPacketInfo->IP.TCP.Payload.Length = len;
/* Store a pointer to the buffer in which the payload resides in case
it becomes necessary to retrieve subsequent buffers later. */
pPacketInfo->IP.TCP.Payload.pPayloadBuffer = bufp;
break;
case TCPIP_PROTOCOL_UDP: /* UDP. */
{
ULONG clt_addr;
ULONG svr_addr;
ULONG clt_port;
ULONG svr_port;
/* Server address is the destination IP and client address is the source IP. */
svr_addr = IP_GET_DST_ADDR_64(pPacketInfo->IP.pHeader);
clt_addr = IP_GET_SRC_ADDR_64(pPacketInfo->IP.pHeader);
/* If this is a receieve, then the server information is in the destination. */
clt_port = UDP_GET_SRC_PORT(pPacketInfo->IP.UDP.pHeader);
svr_port = UDP_GET_DST_PORT(pPacketInfo->IP.UDP.pHeader);
/* Check for remote control responses. */
if (clt_port == ctxtp->params.rct_port || clt_port == CVY_DEF_RCT_PORT_OLD)
{
/* Only check for the magic word if the buffer is long enough to do so.
THIS SHOULD BE GUARANTEED ON THE PACKET RECEIVE PATH, except that it
COULD be in a subsequent buffer (so its there, but we might not be
looking far enough into the packet)!!! */
if (len >= NLB_MIN_RCTL_PAYLOAD_LEN)
{
PIOCTL_REMOTE_HDR rct_hdrp = (PIOCTL_REMOTE_HDR)hdrp;
/* Check the remote control "magic word". */
if (rct_hdrp->code == IOCTL_REMOTE_CODE)
{
/* Found a potential incoming remote control response. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_RESPONSE;
UNIV_PRINT_VERB(("Main_recv_frame_parse: Found a remote control packet - incoming remote control response"));
TRACE_VERB("%!FUNC! Found a remote control packet - incoming remote control response");
}
}
else
{
/* Found a potential incoming remote control response. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_RESPONSE;
UNIV_PRINT_VERB(("Main_recv_frame_parse: Unable to verify remote control code - assuming this is a remote control packet"));
TRACE_VERB("%!FUNC! Unable to verify remote control code - assuming this is a remote control packet");
}
}
/* Check for remote control requests ONLY if remote control is turned ON. */
else if (ctxtp->params.rct_enabled &&
(svr_port == ctxtp->params.rct_port || svr_port == CVY_DEF_RCT_PORT_OLD) &&
(svr_addr == ctxtp->cl_ip_addr || svr_addr == TCPIP_BCAST_ADDR))
{
/* Only check for the magic word if the buffer is long enough to do so.
THIS SHOULD BE GUARANTEED ON THE PACKET RECEIVE PATH, except that it
COULD be in a subsequent buffer (so its there, but we might not be
looking far enough into the packet)!!! */
if (len >= NLB_MIN_RCTL_PAYLOAD_LEN)
{
PIOCTL_REMOTE_HDR rct_hdrp = (PIOCTL_REMOTE_HDR)hdrp;
/* Check the remote control "magic word". */
if (rct_hdrp->code == IOCTL_REMOTE_CODE)
{
/* Found a potential incoming remote control request. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_REQUEST;
UNIV_PRINT_VERB(("Main_recv_frame_parse: Found a remote control packet - incoming remote control request"));
TRACE_VERB("%!FUNC! Found a remote control packet - incoming remote control request");
}
}
else
{
/* Found a potential incoming remote control request. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_REQUEST;
UNIV_PRINT_VERB(("Main_recv_frame_parse: Unable to verify remote control code - assuming this is a remote control packet"));
TRACE_VERB("%!FUNC! Unable to verify remote control code - assuming this is a remote control packet");
}
}
/* If we did find an NLB remote control packet, make sure that any restrictions
on the packet contents are satisfied. */
if ((pPacketInfo->Operation == MAIN_FILTER_OP_CTRL_REQUEST) || (pPacketInfo->Operation == MAIN_FILTER_OP_CTRL_RESPONSE))
{
/* Make sure that the payload is at LEAST as long as the minimum remote control packet. */
if (len < NLB_MIN_RCTL_PAYLOAD_LEN)
{
UNIV_PRINT_CRIT(("Main_recv_frame_parse: Length of the remote control buffer (%u) is less than the size of the minimum remote control packet (%u)", len, NLB_MIN_RCTL_PAYLOAD_LEN));
TRACE_CRIT("%!FUNC! Length of the remote control buffer (%u) is less than the size of the minimum remote control packet (%u)", len, NLB_MIN_RCTL_PAYLOAD_LEN);
return FALSE;
}
}
if (pPacketInfo->Operation == MAIN_FILTER_OP_CTRL_REQUEST)
{
/* Note: It might be a good idea to further verify a remote control packet here,
rather than in Main_ctrl_recv, because in cases of failure, we would avoid
the overhead of creating a new packet. Some cases, such as a bad password,
require creating the new packet anyway, because we reply with a "bad password"
message. The following are cases where we DROP a remote controln request:
o Bad IP or UDP checksums
o Bad remote control code (magic number)
o Request not destined for this cluster
o Remote control disabled
o Request not destinted for this host
o VR remote code not enabled (legacy crap that should be pulled out anyway)
o Unsupported operations
o Invalid request format (perhaps packet lengths, etc.)
Many of these are not uncommon and we would be wise to reduce our overhead
and risk for attack by not allocating resources in these cases. */
}
/* Save a pointer to the UDP payload and its "length". */
pPacketInfo->IP.UDP.Payload.pPayload = hdrp;
pPacketInfo->IP.UDP.Payload.Length = len;
/* Store a pointer to the buffer in which the payload resides in case
it becomes necessary to retrieve subsequent buffers later. */
pPacketInfo->IP.UDP.Payload.pPayloadBuffer = bufp;
break;
}
default:
/* Nothing to verify. Leave successfully now. */
return TRUE;
}
/* Leave succesfully. */
return TRUE;
}
/*
* Function: Main_send_frame_parse
* Description: This function parses an NDIS_PACKET and carefully extracts the information
* necessary to handle the packet. The information extracted includes pointers
* to all relevant headers and payloads as well as the packet type (EType), IP
* protocol (if appropriate), etc. This function does all necessary validation
* to ensure that all pointers are accessible for at least the specified number
* of bytes; i.e., if this function returns successfully, the pointer to the IP
* header is guaranteed to be accessible for at LEAST the length of the IP header.
* No contents inside headers or payloads is validated, except for header lengths,
* and special cases, such as NLB heartbeats or remote control packets. If this
* function returns unsuccessfully, the contents of MAIN_PACKET_INFO cannot be
* trusted and the packet should be discarded. See the definition of MAIN_PACKET_INFO
* in main.h for more specific indications of what fields are filled in and under
* what circumstances.
* Parameters: ctxtp - pointer to the main NLB context structure for this adapter.
* pPacket - pointer to an NDIS_PACKET.
* pPacketInfo - pointer to a MAIN_PACKET_INFO structure to hold the information
* parsed from the packet.
* Returns: BOOLEAN - TRUE if successful, FALSE if not.
* Author: shouse, 3.4.02
* Notes:
*/
BOOLEAN Main_send_frame_parse (
PMAIN_CTXT ctxtp, /* the context for this adapter */
IN PNDIS_PACKET pPacket, /* pointer to an NDIS_PACKET */
OUT PMAIN_PACKET_INFO pPacketInfo /* pointer to an NLB packet information structure to hold the output */
)
{
PNDIS_BUFFER bufp = NULL;
PUCHAR memp = NULL;
PUCHAR hdrp = NULL;
ULONG len = 0;
UINT buf_len = 0;
UINT packet_len = 0;
ULONG curr_len = 0;
ULONG hdr_len = 0;
ULONG offset = 0;
UNIV_ASSERT(pPacket);
UNIV_ASSERT(pPacketInfo);
/* Store a pointer to the original packet. */
pPacketInfo->pPacket = pPacket;
/* By default, this packet does not require post filtering special attention. */
pPacketInfo->Operation = MAIN_FILTER_OP_NONE;
/* Ask NDIS for the first buffer (bufp), the virtual address of the beginning of that buffer,
(memp) the length of that buffer (buf_len) and the length of the entire packet (packet_len). */
NdisGetFirstBufferFromPacketSafe(pPacket, &bufp, &memp, &buf_len, &packet_len, NormalPagePriority);
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NdisGetFirstBufferFromPacket returned NULL!"));
TRACE_CRIT("%!FUNC! NdisGetFirstBufferFromPacket returned NULL!");
return FALSE;
}
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
UNIV_ASSERT(ctxtp->medium == NdisMedium802_3);
/* Get the destination MAC address offset, in bytes. */
offset = CVY_MAC_DST_OFF(ctxtp->medium);
/* The Ethernet header is memp, the beginning of the buffer and the length of contiguous
memory accessible from that pointer is buf_len - the entire buffer. */
pPacketInfo->Ethernet.pHeader = (PCVY_ETHERNET_HDR)memp;
pPacketInfo->Ethernet.Length = buf_len;
/* If, somehow, that length is not at least the size of an Ethernet header, bail out. */
if (buf_len < sizeof(CVY_ETHERNET_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of ethernet buffer (%u) is less than the size of an ethernet header (%u)", buf_len, sizeof(CVY_ETHERNET_HDR)));
TRACE_CRIT("%!FUNC! Length of ethernet buffer (%u) is less than the size of an ethernet header (%u)", buf_len, sizeof(CVY_ETHERNET_HDR));
return FALSE;
}
/* This variable accumulates the lengths of the headers that we've successfully "found". */
hdr_len = sizeof(CVY_ETHERNET_HDR);
/* Set the medium and retrieve the Ethernet packet type from the header. */
pPacketInfo->Medium = NdisMedium802_3;
pPacketInfo->Type = CVY_ETHERNET_ETYPE_GET(pPacketInfo->Ethernet.pHeader);
/* Categorize this packet as unicast, multicast or broadcast by looking at the destination
MAC address and store the "group" in the packet information structure. This is used
for statistical purposes later. */
if (!CVY_MAC_ADDR_MCAST(ctxtp->medium, (PUCHAR)pPacketInfo->Ethernet.pHeader + offset))
{
pPacketInfo->Group = MAIN_FRAME_DIRECTED;
}
else
{
if (CVY_MAC_ADDR_BCAST(ctxtp->medium, (PUCHAR)pPacketInfo->Ethernet.pHeader + offset))
pPacketInfo->Group = MAIN_FRAME_BROADCAST;
else
pPacketInfo->Group = MAIN_FRAME_MULTICAST;
}
/* A length indication from an NDIS_PACKET includes the MAC header,
so subtract that to get the length of the payload. */
pPacketInfo->Length = packet_len - hdr_len;
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, we expect to be able to successfully find the offset we're
looking for, so if we've run out of buffers, fail. */
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NdisGetNextBuffer returned NULL when more data was expected!"));
TRACE_CRIT("%!FUNC! NdisGetNextBuffer returned NULL when more data was expected!");
return FALSE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
}
/* The pointer to the header we're looking for is the beginning of the buffer,
plus the offset of the header within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the header begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Based on the packet type, enforce some restrictions and setup any further
information we need to find in the packet. */
switch (pPacketInfo->Type)
{
case TCPIP_IP_SIG: /* IP packets. */
/* If the contiguous memory accessible from the IP header is not at
LEAST the minimum length of an IP header, bail out now. */
if (len < sizeof(IP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the IP buffer (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR)));
TRACE_CRIT("%!FUNC! Length of the IP buffer (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR));
return FALSE;
}
/* Save a pointer to the IP header and its "length". */
pPacketInfo->IP.pHeader = (PIP_HDR)hdrp;
pPacketInfo->IP.Length = len;
/* Extract the IP protocol from the IP header. */
pPacketInfo->IP.Protocol = IP_GET_PROT(pPacketInfo->IP.pHeader);
/* Calculate the actual IP header length by extracting the hlen field
from the IP header and multiplying by the size of a DWORD. */
len = sizeof(ULONG) * IP_GET_HLEN(pPacketInfo->IP.pHeader);
/* If this calculated header length is not at LEAST the minimum IP
header length, bail out now. */
if (len < sizeof(IP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Calculated IP header length (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR)));
TRACE_CRIT("%!FUNC! Calculated IP header length (%u) is less than the size of an IP header (%u)", len, sizeof(IP_HDR));
return FALSE;
}
#if 0 /* Because the options can be in separate buffers (at least in sends), don't bother to
enforce this condition; NLB never looks at the options anyway, so we don't really care. */
/* If the contiguous memory accessible from the IP header is not at
LEAST the calculated size of the IP header, bail out now. */
if (pPacketInfo->IP.Length < len)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the IP buffer (%u) is less than the size of the IP header (%u)", pPacketInfo->IP.Length, len));
TRACE_CRIT("%!FUNC! Length of the IP buffer (%u) is less than the size of the IP header (%u)", pPacketInfo->IP.Length, len);
return FALSE;
}
#endif
/* The total packet length, in bytes, specified in the header, which includes
both the IP header and payload, can be no more than the packet length that
NDIS told us, which is the entire network packet, minus the media header. */
if (IP_GET_PLEN(pPacketInfo->IP.pHeader) > pPacketInfo->Length)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: IP packet length (%u) is greater than the indicated packet length (%u)", IP_GET_PLEN(pPacketInfo->IP.pHeader), pPacketInfo->Length));
TRACE_CRIT("%!FUNC! IP packet length (%u) is greater than the indicated packet length (%u)", IP_GET_PLEN(pPacketInfo->IP.pHeader), pPacketInfo->Length);
return FALSE;
}
/* If this packet is a subsequent IP fragment, note that in the packet
information structure and leave now, successfully. */
if (IP_GET_FRAG_OFF(pPacketInfo->IP.pHeader) != 0)
{
pPacketInfo->IP.bFragment = TRUE;
return TRUE;
}
/* Otherwise, mark the packet as NOT a subsequent fragment and continue. */
else
{
pPacketInfo->IP.bFragment = FALSE;
}
/* Add the length of the IP header to the offset we're now looking
for in the packet; in this case, the TCP/UDP/etc. header. */
hdr_len += len;
break;
case TCPIP_ARP_SIG: /* ARPs. */
/* If the contiguous memory accessible from the ARP header is not at
LEAST the length of an ARP header, bail out now. */
if (len < sizeof(ARP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the ARP buffer (%u) is less than the size of an ARP header (%u)", len, sizeof(ARP_HDR)));
TRACE_CRIT("%!FUNC! Length of the ARP buffer (%u) is less than the size of an ARP header (%u)", len, sizeof(ARP_HDR));
return FALSE;
}
/* Save a pointer to the ARP header and its "length". */
pPacketInfo->ARP.pHeader = (PARP_HDR)hdrp;
pPacketInfo->ARP.Length = len;
/* Nothing more to look for in an ARP. Leave now, successfully. */
return TRUE;
default: /* Any Ethernet type other than IP and ARP. */
/* Store a pointer to the unknown header and its "length". */
pPacketInfo->Unknown.pHeader = hdrp;
pPacketInfo->Unknown.Length = len;
/* Nothing more to look for in this packet. Leave now, successfully. */
return TRUE;
}
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, we expect to be able to successfully find the offset we're
looking for, so if we've run out of buffers, fail. */
if (bufp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NdisGetNextBuffer returned NULL when more data was expected!"));
TRACE_CRIT("%!FUNC! NdisGetNextBuffer returned NULL when more data was expected!");
return FALSE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: NDIS buffer virtual memory address is NULL!"));
TRACE_CRIT("%!FUNC! NDIS buffer virtual memory address is NULL!");
return FALSE;
}
}
/* The pointer to the header we're looking for is the beginning of the buffer,
plus the offset of the header within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the header begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Based on the packet type, enforce some restrictions and setup any further
information we need to find in the packet. */
switch (pPacketInfo->Type)
{
case TCPIP_IP_SIG: /* IP packets. */
/* For some protocols, we have more to look for in the packet. */
switch (pPacketInfo->IP.Protocol)
{
case TCPIP_PROTOCOL_TCP: /* TCP. */
/* If the contiguous memory accessible from the TCP header is not at
LEAST the minimum length of a TCP header, bail out now. */
if (len < sizeof(TCP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the TCP buffer (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR)));
TRACE_CRIT("%!FUNC! Length of the TCP buffer (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR));
return FALSE;
}
/* Save a pointer to the TCP header and its "length". */
pPacketInfo->IP.TCP.pHeader = (PTCP_HDR)hdrp;
pPacketInfo->IP.TCP.Length = len;
/* Calculate the actual TCP header length by extracting the hlen field
from the TCP header and multiplying by the size of a DWORD. */
len = sizeof(ULONG) * TCP_GET_HLEN(pPacketInfo->IP.TCP.pHeader);
/* If this calculated header length is not at LEAST the minimum TCP
header length, bail out now. */
if (len < sizeof(TCP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Calculated TCP header length (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR)));
TRACE_CRIT("%!FUNC! Calculated TCP header length (%u) is less than the size of an TCP header (%u)", len, sizeof(TCP_HDR));
return FALSE;
}
#if 0 /* Because the options can be in separate buffers (at least in sends), don't bother to
enforce this condition; NLB never looks at the options anyway, so we don't really care. */
/* If the contiguous memory accessible from the TCP header is not at
LEAST the calculated size of the TCP header, bail out now. */
if (pPacketInfo->IP.TCP.Length < len)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the TCP buffer (%u) is less than the size of the TCP header (%u)", pPacketInfo->IP.TCP.Length, len));
TRACE_CRIT("%!FUNC! Length of the TCP buffer (%u) is less than the size of the TCP header (%u)", pPacketInfo->IP.TCP.Length, len);
return FALSE;
}
#endif
/* Since we never look into TCP payloads on the packet send path,
note in the packet information structure that the TCP payload
has not been parsed, and exit now. */
pPacketInfo->IP.TCP.Payload.pPayload = NULL;
pPacketInfo->IP.TCP.Payload.Length = 0;
pPacketInfo->IP.TCP.Payload.pPayloadBuffer = NULL;
/* Nothing more to look for in this packet. Leave now, successfully. */
return TRUE;
case TCPIP_PROTOCOL_UDP:
/* If the contiguous memory accessible from the UDP header is not at
LEAST the length of a UDP header, bail out now. */
if (len < sizeof(UDP_HDR))
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the UDP buffer (%u) is less than the size of an UDP header (%u)", len, sizeof(UDP_HDR)));
TRACE_CRIT("%!FUNC! Length of the UDP buffer (%u) is less than the size of an UDP header (%u)", len, sizeof(UDP_HDR));
return FALSE;
}
/* Save a pointer to the UDP header and its "length". */
pPacketInfo->IP.UDP.pHeader = (PUDP_HDR)hdrp;
pPacketInfo->IP.UDP.Length = len;
/* Add the length of the UDP header to the offset we're now looking
for in the packet; in this case, the UDP payload. */
hdr_len += sizeof (UDP_HDR);
break;
case TCPIP_PROTOCOL_GRE:
case TCPIP_PROTOCOL_IPSEC1:
case TCPIP_PROTOCOL_IPSEC2:
case TCPIP_PROTOCOL_ICMP:
default:
/* For any other IP protocol, we have nothing in particular to do.
Leave now, successfully. */
return TRUE;
}
break;
default:
return TRUE;
}
/* As long as the byte offset we're looking for is NOT in the current buffer,
keep looping through the available NDIS buffers. */
while (curr_len + buf_len <= hdr_len)
{
/* Before we get the next buffer, accumulate the length of the buffer
we're done with by adding its length to curr_len. */
curr_len += buf_len;
/* Get the next buffer in the chain. */
NdisGetNextBuffer(bufp, &bufp);
/* At this point, it is OK if we can't get to the payload of the packet. Not
all TCP/UDP packets will actually HAVE a payload (such as TCP SYNs), so if
we can't find it, leave successfully now, and the calling function will have
to check the pointer value for NULL before accessing the payload if necessary. */
if (bufp == NULL)
{
if (pPacketInfo->IP.Protocol == TCPIP_PROTOCOL_UDP)
{
/* If this is a UDP packet, note the absence of the UDP payload. */
pPacketInfo->IP.UDP.Payload.pPayload = NULL;
pPacketInfo->IP.UDP.Payload.Length = 0;
pPacketInfo->IP.UDP.Payload.pPayloadBuffer = NULL;
}
return TRUE;
}
/* Query the buffer for the virtual address of the buffer and its length. */
NdisQueryBufferSafe(bufp, &memp, &buf_len, NormalPagePriority);
if (memp == NULL)
{
/* At this point, it is OK if we can't get to the payload of the packet. Not
all TCP/UDP packets will actually HAVE a payload (such as TCP SYNs), so if
we can't find it, leave successfully now, and the calling function will have
to check the pointer value for NULL before accessing the payload if necessary. */
if (pPacketInfo->IP.Protocol == TCPIP_PROTOCOL_UDP)
{
/* If this is a UDP packet, note the absence of the UDP payload. */
pPacketInfo->IP.UDP.Payload.pPayload = NULL;
pPacketInfo->IP.UDP.Payload.Length = 0;
pPacketInfo->IP.UDP.Payload.pPayloadBuffer = NULL;
}
return TRUE;
}
}
/* The pointer to the header we're looking for is the beginning of the buffer,
plus the offset of the header within this buffer. Likewise, the contiguous
memory accessible from this pointer is the length of this buffer, minus
the byte offset at which the header begins. */
hdrp = memp + (hdr_len - curr_len);
len = buf_len - (hdr_len - curr_len);
/* Some special UDP and TCP packets require identification, so check to see whether
this particular packet is one of those special types; NLB remote control or NetBT. */
switch (pPacketInfo->IP.Protocol)
{
case TCPIP_PROTOCOL_UDP: /* UDP. */
{
ULONG clt_addr;
ULONG clt_port;
/* Note that the only outgoing remote control traffic seen here is outgoing requests.
Outgoing replies do NOT traverse the normal packet send path. */
/* If this is a send request, then the client (peer) is in the destination. */
clt_addr = IP_GET_DST_ADDR_64(pPacketInfo->IP.pHeader);
clt_port = UDP_GET_DST_PORT(pPacketInfo->IP.UDP.pHeader);
/* IP broadcast UDP packets generated by wlbs.exe. */
if ((clt_port == ctxtp->params.rct_port || clt_port == CVY_DEF_RCT_PORT_OLD) && clt_addr == TCPIP_BCAST_ADDR)
{
/* Only check for the magic word if the buffer is long enough to do so.
THIS SHOULD BE GUARANTEED ON THE SEND PATH, except that it COULD
be in a subsequent buffer (so its there, but we might not be looking
far enough into the packet)!!! */
if (len >= NLB_MIN_RCTL_PAYLOAD_LEN)
{
PIOCTL_REMOTE_HDR rct_hdrp = (PIOCTL_REMOTE_HDR)hdrp;
/* Check the remote control "magic word". */
if (rct_hdrp->code == IOCTL_REMOTE_CODE)
{
/* Found an outgoing remote control request. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_REQUEST;
UNIV_PRINT_VERB(("Main_send_frame_parse: Found a remote control packet - outgoing remote control request"));
TRACE_VERB("%!FUNC! Found a remote control packet - outgoing remote control request");
}
}
else
{
/* Found a potential outgoing remote control request. */
pPacketInfo->Operation = MAIN_FILTER_OP_CTRL_REQUEST;
UNIV_PRINT_VERB(("Main_send_frame_parse: Unable to verify remote control code - assuming this is a remote control packet"));
TRACE_VERB("%!FUNC! Unable to verify remote control code - assuming this is a remote control packet");
}
}
/* If we did find an NLB remote control packet, make sure that any restrictions
on the packet contents are satisfied. */
if (pPacketInfo->Operation == MAIN_FILTER_OP_CTRL_REQUEST)
{
/* Make sure that the payload is at LEAST as long as the minimum remote control packet. */
if (len < NLB_MIN_RCTL_PAYLOAD_LEN)
{
UNIV_PRINT_CRIT(("Main_send_frame_parse: Length of the remote control buffer (%u) is less than the size of the minimum remote control packet (%u)", len, NLB_MIN_RCTL_PAYLOAD_LEN));
TRACE_CRIT("%!FUNC! Length of the remote control buffer (%u) is less than the size of the minimum remote control packet (%u)", len, NLB_MIN_RCTL_PAYLOAD_LEN);
return FALSE;
}
}
/* Save a pointer to the UDP payload and its "length". */
pPacketInfo->IP.UDP.Payload.pPayload = hdrp;
pPacketInfo->IP.UDP.Payload.Length = len;
/* Store a pointer to the buffer in which the payload resides in case
it becomes necessary to retrieve subsequent buffers later. */
pPacketInfo->IP.UDP.Payload.pPayloadBuffer = bufp;
break;
}
default:
/* Nothing to verify. Leave successfully now. */
return TRUE;
}
/* Leave succesfully. */
return TRUE;
}
/*
* Function: Main_query_params
* Desctription: This function queries the current state of the NLB registry parameters
* in the driver, which can be useful to ensure that the driver is properly
* receiving parameter changes made in user-space. Under normal circumstances,
* the registry parameters retrievable from user-space (WlbsReadReg) will be
* identical to those retrieved here. Possible exceptions include when the
* registry parameters are invalid, in which case they will be rejected by a
* "wlbs reload" operation and the registry and driver will be out of sync.
* Parameters: ctxtp - a pointer to the appropriate NLB context structure.
* pParams - a pointer to a buffer into which the parameters are placed.
* Returns: Nothing.
* Author: shouse, 5.18.01
* Notes: Of course, this function should NOT change parameters, just copy them into the buffer.
*/
VOID Main_query_params (PMAIN_CTXT ctxtp, PNLB_OPTIONS_PARAMS pParams)
{
ULONG index;
UNIV_ASSERT(ctxtp);
UNIV_ASSERT(pParams);
/* copy the parameters from this NLB instance's params strucutre into the IOCTL buffer. */
pParams->Version = ctxtp->params.parms_ver;
pParams->EffectiveVersion = ctxtp->params.effective_ver;
pParams->HostPriority = ctxtp->params.host_priority;
pParams->HeartbeatPeriod = ctxtp->params.alive_period;
pParams->HeartbeatLossTolerance = ctxtp->params.alive_tolerance;
pParams->NumActionsAlloc = ctxtp->params.num_actions;
pParams->NumPacketsAlloc = ctxtp->params.num_packets;
pParams->NumHeartbeatsAlloc = ctxtp->params.num_send_msgs;
pParams->InstallDate = ctxtp->params.install_date;
pParams->RemoteMaintenancePassword = ctxtp->params.rmt_password;
pParams->RemoteControlPassword = ctxtp->params.rct_password;
pParams->RemoteControlPort = ctxtp->params.rct_port;
pParams->RemoteControlEnabled = ctxtp->params.rct_enabled;
pParams->NumPortRules = ctxtp->params.num_rules;
pParams->ConnectionCleanUpDelay = ctxtp->params.cleanup_delay;
pParams->ClusterModeOnStart = ctxtp->params.cluster_mode;
pParams->HostState = ctxtp->params.init_state;
pParams->PersistedStates = ctxtp->params.persisted_states;
pParams->DescriptorsPerAlloc = ctxtp->params.dscr_per_alloc;
pParams->MaximumDescriptorAllocs = ctxtp->params.max_dscr_allocs;
pParams->TCPConnectionTimeout = ctxtp->params.tcp_dscr_timeout;
pParams->IPSecConnectionTimeout = ctxtp->params.ipsec_dscr_timeout;
pParams->FilterICMP = ctxtp->params.filter_icmp;
pParams->ScaleClient = ctxtp->params.scale_client;
pParams->NBTSupport = ctxtp->params.nbt_support;
pParams->MulticastSupport = ctxtp->params.mcast_support;
pParams->MulticastSpoof = ctxtp->params.mcast_spoof;
pParams->IGMPSupport = ctxtp->params.igmp_support;
pParams->MaskSourceMAC = ctxtp->params.mask_src_mac;
pParams->NetmonReceiveHeartbeats = ctxtp->params.netmon_alive;
pParams->ClusterIPToMAC = ctxtp->params.convert_mac;
pParams->IPChangeDelay = ctxtp->params.ip_chg_delay;
pParams->IdentityHeartbeatPeriod = ctxtp->params.identity_period;
pParams->IdentityHeartbeatEnabled = ctxtp->params.identity_enabled;
/* Copy the strings into the IOCTL buffer. */
NdisMoveMemory(pParams->ClusterIPAddress, ctxtp->params.cl_ip_addr, (CVY_MAX_CL_IP_ADDR + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->ClusterNetmask, ctxtp->params.cl_net_mask, (CVY_MAX_CL_NET_MASK + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->DedicatedIPAddress, ctxtp->params.ded_ip_addr, (CVY_MAX_DED_IP_ADDR + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->DedicatedNetmask, ctxtp->params.ded_net_mask, (CVY_MAX_DED_NET_MASK + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->ClusterMACAddress, ctxtp->params.cl_mac_addr, (CVY_MAX_NETWORK_ADDR + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->DomainName, ctxtp->params.domain_name, (CVY_MAX_DOMAIN_NAME + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->IGMPMulticastIPAddress, ctxtp->params.cl_igmp_addr, (CVY_MAX_CL_IGMP_ADDR + 1) * sizeof(WCHAR));
NdisMoveMemory(pParams->HostName, ctxtp->params.hostname, (CVY_MAX_FQDN + 1) * sizeof(WCHAR));
/* Copy the BDA teaming parameters into the IOCTL buffer. */
NdisMoveMemory(pParams->BDATeaming.TeamID, ctxtp->params.bda_teaming.team_id, (CVY_MAX_BDA_TEAM_ID + 1) * sizeof(WCHAR));
pParams->BDATeaming.Active = ctxtp->params.bda_teaming.active;
pParams->BDATeaming.Master = ctxtp->params.bda_teaming.master;
pParams->BDATeaming.ReverseHash = ctxtp->params.bda_teaming.reverse_hash;
/* Loop throgh and copy all port rules into the IOCTL buffer. */
for (index = 0; index < ctxtp->params.num_rules; index++) {
pParams->PortRules[index].Valid = ctxtp->params.port_rules[index].valid;
pParams->PortRules[index].Code = ctxtp->params.port_rules[index].code;
pParams->PortRules[index].VirtualIPAddress = ctxtp->params.port_rules[index].virtual_ip_addr;
pParams->PortRules[index].StartPort = ctxtp->params.port_rules[index].start_port;
pParams->PortRules[index].EndPort = ctxtp->params.port_rules[index].end_port;
pParams->PortRules[index].Protocol = ctxtp->params.port_rules[index].protocol;
pParams->PortRules[index].Mode = ctxtp->params.port_rules[index].mode;
switch (ctxtp->params.port_rules[index].mode) {
case CVY_SINGLE:
pParams->PortRules[index].SingleHost.Priority = ctxtp->params.port_rules[index].mode_data.single.priority;
break;
case CVY_MULTI:
pParams->PortRules[index].MultipleHost.Equal = ctxtp->params.port_rules[index].mode_data.multi.equal_load;
pParams->PortRules[index].MultipleHost.Affinity = ctxtp->params.port_rules[index].mode_data.multi.affinity;
pParams->PortRules[index].MultipleHost.LoadWeight = ctxtp->params.port_rules[index].mode_data.multi.load;
break;
case CVY_NEVER:
default:
break;
}
}
/* Query the load module for some relevant statistics. Don't bother to lock the load module - its not strictly necessary. */
if (!Load_query_statistics(&ctxtp->load, &pParams->Statistics.ActiveConnections, &pParams->Statistics.DescriptorsAllocated)) {
/* This function returns FALSE if the load module is inactive. In that case, return
zero for both the number of active connections and descriptors allocated. */
pParams->Statistics.ActiveConnections = 0;
pParams->Statistics.DescriptorsAllocated = 0;
}
}
/*
* Function: Main_query_bda_teaming
* Desctription: This function retrieves the state of a given BDA team, including the
* current state of operation, the configuration and a list of the team
* members. Because the teaming information is global, this function
* can be run in the context (MAIN_CTXT) of ANY adapter - it doesn't
* even need to be a member of the team being queried. User level
* applications should choose a cluster on which to perform the IOCTL,
* which can be an arbitrary decision. This function attempts to find
* the specified team, and if successful then loops through the global
* array of adapters attempting to find the members of the team. This
* is necessary because there is no "link" from the global teaming state
* back to the members - only from members to their teams. Because this
* state is global, this function MUST grab the global teaming lock and
* hold it until it returns.
* Parameters: ctxtp - a pointer to an NLB instance (which instance is irrelevant).
* pTeam - a pointer to a buffer into which the results are placed.
* Returns: ULONG - IOCTL_CVY_NOT_FOUND if the specified team cannot be found.
* - IOCTL_CVY_GENERIC_FAILURE if we can't find all of the team members.
* - IOCTL_CVY_OK if all goes well.
* Author: shouse, 5.18.01
* Notes: This function grabs the global teaming lock. Because it is a query operation,
* NO changes are made to the actual state of NLB or BDA teaming.
*/
ULONG Main_query_bda_teaming (PMAIN_CTXT ctxtp, PNLB_OPTIONS_BDA_TEAMING pTeam)
{
PBDA_MEMBER memberp;
PBDA_TEAM teamp;
ULONG index;
ULONG count;
UNIV_ASSERT(ctxtp);
UNIV_ASSERT(pTeam);
/* Because the teaming state can change at any time, we have to grab the global
team lock first and hold until we're done to prevent ourselves from running
off the end of linked lists, or accessing invalid memory, etc. */
NdisAcquireSpinLock(&univ_bda_teaming_lock);
/* Try to find the specified team. If it's not in the list, bail out. */
if (!(teamp = Main_find_team(ctxtp, pTeam->TeamID))) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return IOCTL_CVY_NOT_FOUND;
}
/* If we found it, fill in the team's state and configuration. */
pTeam->Team.Active = teamp->active;
pTeam->Team.MembershipCount = teamp->membership_count;
pTeam->Team.MembershipFingerprint = teamp->membership_fingerprint;
pTeam->Team.MembershipMap = teamp->membership_map;
pTeam->Team.ConsistencyMap = teamp->consistency_map;
/* Because the team structure does not contain a "list" of members, we need to
loop through all NLB instances and try to match members to their teams. */
for (index = 0, count = 0; index < CVY_MAX_ADAPTERS; index++) {
/* If the global adapter structure is not in used, or isn't initialized, no need to do anything. */
if (univ_adapters[index].used && univ_adapters[index].bound && univ_adapters[index].inited && univ_adapters[index].ctxtp) {
/* If this adapter is in use, then grab a pointer to its teaming configuration. */
memberp = &(univ_adapters[index].ctxtp->bda_teaming);
/* If the member is active, then we need to check what team it belongs to.
If its not active, then we can skip it. */
if (memberp->active) {
/* If this adapter is actively teaming, it HAD BETTER have a pointer to its team. */
UNIV_ASSERT(memberp->bda_team);
/* Check to see if the team we are querying is the same team that this adapter belongs to. */
if (memberp->bda_team == teamp) {
/* If it is, fill in the state and configuration of this member. */
pTeam->Team.Members[count].ClusterIPAddress = univ_adapters[index].ctxtp->cl_ip_addr;
pTeam->Team.Members[count].ReverseHash = memberp->reverse_hash;
pTeam->Team.Members[count].MemberID = memberp->member_id;
pTeam->Team.Members[count].Master = memberp->master;
/* If this team member is the team's master, fill in the master field
with the cluster IP address of this adapter. */
if (memberp->master)
pTeam->Team.Master = univ_adapters[index].ctxtp->cl_ip_addr;
/* Increment the number of members that we have found so far. */
count++;
}
}
}
}
/* If we were unable to find all of the members, or if we found to many, then
return failure. There is a small time window during which this can happen,
but that's not worth worrying about - users can simply try again. For
instance, if a member is joining its team during a bind operation, it could
grab the global lock and update its configuration and that of the team, before
the adapter.init flag is set. Therefore, if we execute in between those two
events, we will miss a member when we loop through the adapter strucutres
(because the init flag is FALSE). So what - we fail and they try again, at
which point the call will (should) succeed. */
if (count != teamp->membership_count) {
NdisReleaseSpinLock(&univ_bda_teaming_lock);
TRACE_CRIT("%!FUNC! expected team membership count=%u, but counted %u", teamp->membership_count, count);
return IOCTL_CVY_GENERIC_FAILURE;
}
/* Now that we're done, we can release the global teaming lock. */
NdisReleaseSpinLock(&univ_bda_teaming_lock);
return IOCTL_CVY_OK;
}
NDIS_STATUS Main_dispatch (PVOID DeviceObject, PVOID Irp)
{
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
PIRP pIrp = Irp;
PIO_STACK_LOCATION pIrpStack;
UNIV_PRINT_VERB(("Main_dispatch: Device=%p, Irp=%p \n", DeviceObject, Irp));
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
switch (pIrpStack->MajorFunction) {
case IRP_MJ_CREATE:
/* Security fix: Verify that the user mode app is NOT attempting to do a "file" open (ie. \\.\Wlbs\)
as opposed to a "device" open (ie. \\.\Wlbs). On a "file" open, note that there is a trailing
backslash ("\"). Only devices that have namespaces support "file" open. NLB does not.
*/
if (pIrpStack->FileObject->FileName.Length != 0)
{
UNIV_PRINT_CRIT(("Main_dispatch: Attempt to open wlbs device object as a file (instead of as a device), File name length (%d) is non-zero, Returning STATUS_ACCESS_DENIED\n", pIrpStack->FileObject->FileName.Length));
TRACE_CRIT("%!FUNC! Attempt to open wlbs device object as a file (instead of as a device), File name length (%d) is non-zero, Returning STATUS_ACCESS_DENIED\n", pIrpStack->FileObject->FileName.Length);
status = STATUS_ACCESS_DENIED;
}
break;
case IRP_MJ_DEVICE_CONTROL:
status = Main_ioctl(DeviceObject, Irp);
break;
case IRP_MJ_SYSTEM_CONTROL:
return (NDIS_STATUS)NlbWmi_System_Control(DeviceObject, pIrp);
break;
default:
break;
}
pIrp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
#if defined (NLB_HOOK_ENABLE)
/*
* Function: Main_register_hook
* Desctription: This function handles hook register and de-register requests from other
* components. It processes the input buffer received via an IOCTL and
* performs the requested operation GLOBALLY.
* Parameters:
* Returns: NDIS_STATUS - indicates whether or not the operation succeeded. See ntddnlb.h
* for a list of return values and their meanings.
* Author: shouse, 12.10.01
* Notes: This function may sleep as a result of waiting for references on a registered
* hook to be relinquished.
*/
NDIS_STATUS Main_register_hook (PVOID DeviceObject, PVOID Irp)
{
PNLB_IOCTL_REGISTER_HOOK_REQUEST pRequest;
PIO_STACK_LOCATION pIrpStack;
PIRP pIrp = Irp;
ULONG ilen, olen = 0;
ULONG ioctl;
ULONG index;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
ioctl = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
/* This function handles ONLY the hook (de)register IOCTL. */
UNIV_ASSERT(ioctl == NLB_IOCTL_REGISTER_HOOK);
/* This IOCTL does not return a buffer, only a status code. */
pIrp->IoStatus.Information = 0;
/* Make sure that the entity requesting this hook came from kernel-mode. */
if (pIrp->RequestorMode != KernelMode) {
UNIV_PRINT_CRIT(("Main_register_hook: User-level entities may NOT register hooks with NLB"));
TRACE_CRIT("%!FUNC! User-level entities may NOT register hooks with NLB");
return STATUS_ACCESS_DENIED;
}
ilen = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
olen = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
pRequest = pIrp->AssociatedIrp.SystemBuffer;
/* Check the lengths of the input buffer specified by the kernel-space application. */
if ((ilen < sizeof(NLB_IOCTL_REGISTER_HOOK_REQUEST)) || (olen != 0) || !pRequest) {
UNIV_PRINT_CRIT(("Main_register_hook: Buffer is missing or not the (expected) size: input=%d (%d), output=%d (%d)", ilen, sizeof(NLB_IOCTL_REGISTER_HOOK_REQUEST), olen, 0));
TRACE_CRIT("%!FUNC! Buffer is missing or not the (expected) size: input=%d (%d), output=%d (%d)", ilen, sizeof(NLB_IOCTL_REGISTER_HOOK_REQUEST), olen, 0);
return STATUS_INVALID_PARAMETER;
}
UNIV_PRINT_VERB(("Main_register_hook: Processing Ioctl 0x%08x globally on wlbs.sys", ioctl));
TRACE_VERB("%!FUNC! Processing Ioctl 0x%08x globally on wlbs.sys", ioctl);
/* Forcefully null terminate HookIdentifier, just in case */
pRequest->HookIdentifier[(sizeof(pRequest->HookIdentifier)/sizeof(WCHAR)) - 1] = UNICODE_NULL;
/* Is this a (de)registration for the NLB packet filter hook? */
if (Univ_equal_unicode_string(pRequest->HookIdentifier, NLB_FILTER_HOOK_INTERFACE, wcslen(NLB_FILTER_HOOK_INTERFACE))) {
PFILTER_HOOK_TABLE pFilterHook = &univ_hooks.FilterHook;
/* Is this a register operation? */
if (pRequest->HookTable) {
NLB_FILTER_HOOK_TABLE RequestTable;
/* Copy the hook table into local memory, just in case the memory the application is
using to hold this table is in pageable memory (once we grab the spinlock, we're
at DISPATCH_LEVEL, and we can't touch memory that's paged out). */
NdisMoveMemory(&RequestTable, pRequest->HookTable, sizeof(NLB_FILTER_HOOK_TABLE));
UNIV_PRINT_VERB(("Main_register_hook: Attempting to register the NLB_FILTER_HOOK_INTERFACE"));
TRACE_VERB("%!FUNC! Attempting to register the NLB_FILTER_HOOK_INTERFACE");
/* If this is a registration, but no de-register callback was provided, return failure. */
if (!pRequest->DeregisterCallback) {
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) Required de-register callback is missing"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) Required de-register callback is missing");
return STATUS_INVALID_PARAMETER;
}
/* If this is a registration, but no hook callbacks were provided, return failure. Since
we don't allow registration of the query hook without at least one of send or receive,
we don't need to include the query hook in this check. */
if (!RequestTable.SendHook && !RequestTable.ReceiveHook) {
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) No hook callbacks provided"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) No hook callbacks provided");
return STATUS_INVALID_PARAMETER;
}
/* If this is a registration, but no query hook callback was provided, return failure. */
if (!RequestTable.QueryHook) {
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) No query hook callback provided"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) No query hook callback provided");
return STATUS_INVALID_PARAMETER;
}
/* Grab the filter hook spin lock to protect access to the filter hook. */
NdisAcquireSpinLock(&pFilterHook->Lock);
/* Make sure that another (de)register operation isn't in progress before proceeding. */
while (pFilterHook->Operation != HOOK_OPERATION_NONE) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
/* Sleep while some other operation is in progress. */
Nic_sleep(10);
/* Grab the filter hook spin lock to protect access to the filter hook. */
NdisAcquireSpinLock(&pFilterHook->Lock);
}
/* If this hook interface has already been registered (by this entity or otherwise),
this hook registration request must be failed. Only one component can own the
NLB filter hook at any given time. */
if (pFilterHook->Interface.Registered) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) This hook interface is already registered"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) This hook interface is already registered");
return STATUS_ACCESS_DENIED;
}
/* If the hook is unregistered, but has an owner, then we forcefully de-registered a
filter hook owner, but presumably they did not close their IOCTL handle in time for
us to destroy the NLB IOCTL interface, resulting in our driver NOT being properly
unloaded. If that SAME entity tries to come back and register, they will NOT be
allowed to do so. Only new IOCTL handles will be allowed to register now. */
if ((pFilterHook->Interface.Owner != 0) && (pRequest->RegisteringEntity == pFilterHook->Interface.Owner)) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) Registering entity attempting to re-use IOCTL interface after a forceful de-register"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) Registering entity attempting to re-use IOCTL interface after a forceful de-register");
return STATUS_ACCESS_DENIED;
}
/* Set the state to registering. */
pFilterHook->Operation = HOOK_OPERATION_REGISTERING;
/* Set the hook interface information which includes the identity of the component
registering the hook, the callback context and the de-register callback function. */
pFilterHook->Interface.Registered = TRUE;
pFilterHook->Interface.References = 0;
pFilterHook->Interface.Owner = pRequest->RegisteringEntity;
pFilterHook->Interface.Deregister = pRequest->DeregisterCallback;
/* If a send filter hook has been provided, note that it has been registered
and store the callback function pointer. */
if (RequestTable.SendHook) {
pFilterHook->SendHook.Registered = TRUE;
pFilterHook->SendHook.Hook.SendHookFunction = RequestTable.SendHook;
}
/* If a receive filter hook has been provided, note that it has been registered
and store the callback function pointer. */
if (RequestTable.ReceiveHook) {
pFilterHook->ReceiveHook.Registered = TRUE;
pFilterHook->ReceiveHook.Hook.ReceiveHookFunction = RequestTable.ReceiveHook;
}
/* Note the registration of the query hook, which is REQUIRED, and store the
callback function pointer. */
pFilterHook->QueryHook.Registered = TRUE;
pFilterHook->QueryHook.Hook.QueryHookFunction = RequestTable.QueryHook;
/* Set the state to none. */
pFilterHook->Operation = HOOK_OPERATION_NONE;
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_VERB(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) This hook interface was successfully registered"));
TRACE_VERB("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) This hook interface was successfullly registered");
return STATUS_SUCCESS;
/* Or is this a de-register operation? */
} else {
UNIV_PRINT_VERB(("Main_register_hook: Attempting to de-register the NLB_FILTER_HOOK_INTERFACE"));
TRACE_VERB("%!FUNC! Attempting to de-register the NLB_FILTER_HOOK_INTERFACE");
/* Grab the filter hook spin lock to protect access to the filter hook. */
NdisAcquireSpinLock(&pFilterHook->Lock);
/* Make sure that another (de)register operation isn't in progress before proceeding. */
while (pFilterHook->Operation != HOOK_OPERATION_NONE) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
/* Sleep while some other operation is in progress. */
Nic_sleep(10);
/* Grab the filter hook spin lock to protect access to the filter hook. */
NdisAcquireSpinLock(&pFilterHook->Lock);
}
/* If this hook interface has not been registered (by this entity or otherwise),
this hook registration request must be failed. NLB filter hooks can only be
de-registered if they have been previously registered. */
if (!pFilterHook->Interface.Registered) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) This hook interface is not registered"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) This hook interface is not registered");
return STATUS_INVALID_PARAMETER;
}
/* If a component other than the one that originally registered this interface
is trying to de-register it, do NOT allow the operation to succeed. */
if (pRequest->RegisteringEntity != pFilterHook->Interface.Owner) {
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_CRIT(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) This hook interface is not owned by this component"));
TRACE_CRIT("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) This hook interface is not owned by this component");
return STATUS_ACCESS_DENIED;
}
/* Set the state to de-registering. */
pFilterHook->Operation = HOOK_OPERATION_DEREGISTERING;
/* Mark these hooks as NOT registered to keep any more references from accumulating. */
pFilterHook->SendHook.Registered = FALSE;
pFilterHook->QueryHook.Registered = FALSE;
pFilterHook->ReceiveHook.Registered = FALSE;
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
#if defined (NLB_HOOK_TEST_ENABLE)
Nic_sleep(500);
#endif
/* Wait for all references on the filter hook interface to disappear. */
while (pFilterHook->Interface.References) {
/* Sleep while there are references on the interface we're de-registering. */
Nic_sleep(10);
}
/* Assert that the de-register callback MUST be non-NULL. */
UNIV_ASSERT(pFilterHook->Interface.Deregister);
/* Call the provided de-register callback to notify the de-registering component
that we have indeed de-registered the specified hook. */
(*pFilterHook->Interface.Deregister)(NLB_FILTER_HOOK_INTERFACE, pFilterHook->Interface.Owner, 0);
/* Grab the filter hook spin lock to protect access to the filter hook. */
NdisAcquireSpinLock(&pFilterHook->Lock);
/* Reset the send filter hook information. */
Main_hook_init(&univ_hooks.FilterHook.SendHook);
/* Reset the query filter hook information. */
Main_hook_init(&univ_hooks.FilterHook.QueryHook);
/* Reset the receive filter hook information. */
Main_hook_init(&univ_hooks.FilterHook.ReceiveHook);
/* Reset the hook interface information. */
Main_hook_interface_init(&univ_hooks.FilterHook.Interface);
/* Set the state to none. */
pFilterHook->Operation = HOOK_OPERATION_NONE;
/* Release the filter hook spin lock. */
NdisReleaseSpinLock(&pFilterHook->Lock);
UNIV_PRINT_VERB(("Main_register_hook: (NLB_FILTER_HOOK_INTERFACE) This hook interface was successfully de-registered"));
TRACE_VERB("%!FUNC! (NLB_FILTER_HOOK_INTERFACE) This hook interface was successfullly de-registered");
return STATUS_SUCCESS;
}
/* This (de)registration is for an unknown hook. Exit. */
} else {
/* We should be at PASSIVE_LEVEL - %ls is OK. However, be extra paranoid, just in case. If
we're at DISPATCH_LEVEL, then just log an unknown adapter print - don't specify the GUID. */
if (KeGetCurrentIrql() <= PASSIVE_LEVEL) {
UNIV_PRINT_CRIT(("Main_register_hook: Unknown hook -> %ls", pRequest->HookIdentifier));
} else {
UNIV_PRINT_CRIT(("Main_register_hook: Unknown hook -> IRQ level too high to print hook GUID"));
}
TRACE_CRIT("%!FUNC! Unknown hook -> %ls", pRequest->HookIdentifier);
return STATUS_INVALID_PARAMETER;
}
}
#endif
NDIS_STATUS Main_ioctl (PVOID DeviceObject, PVOID Irp)
{
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
PIRP pIrp = Irp;
PIO_STACK_LOCATION pIrpStack;
ULONG ilen, olen = 0;
ULONG ioctl;
PMAIN_CTXT ctxtp;
PIOCTL_LOCAL_HDR pLocalHeader;
ULONG adapter_index;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
ioctl = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
#if defined (NLB_HOOK_ENABLE)
/* If this IOCTL is our public interface for hook registration, which uses
different (public) data strucutres and return values, handle it separately
in another IOCTL function. */
if (ioctl == NLB_IOCTL_REGISTER_HOOK)
return Main_register_hook(DeviceObject, Irp);
#endif
ilen = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
olen = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
pLocalHeader = pIrp->AssociatedIrp.SystemBuffer;
/* Check the lengths of the input and output buffers specified by the user-space application. */
if (ilen != sizeof (IOCTL_LOCAL_HDR) || olen != sizeof (IOCTL_LOCAL_HDR) || !pLocalHeader) {
UNIV_PRINT_CRIT(("Main_ioctl: Buffer is missing or not the expected (%d) size: input=%d, output=%d", sizeof(IOCTL_LOCAL_HDR), ilen, olen));
TRACE_CRIT("%!FUNC! Buffer is missing or not the expected (%d) size: input=%d, output=%d", sizeof(IOCTL_LOCAL_HDR), ilen, olen);
return STATUS_INVALID_PARAMETER;
}
/* Fill in the number of bytes written. This IOCTL always writes the same number of bytes that it
reads. That is, the input and output buffers should be identical structures. */
pIrp->IoStatus.Information = sizeof(IOCTL_LOCAL_HDR);
/* Security fix: Forcefully null terminate device_name, just in case */
pLocalHeader->device_name[(sizeof(pLocalHeader->device_name)/sizeof(WCHAR)) - 1] = UNICODE_NULL;
NdisAcquireSpinLock(&univ_bind_lock);
/* Map from the GUID to the adapter index. */
if ((adapter_index = Main_adapter_get(pLocalHeader->device_name)) == MAIN_ADAPTER_NOT_FOUND) {
NdisReleaseSpinLock(&univ_bind_lock);
pLocalHeader->ctrl.ret_code = IOCTL_CVY_NOT_FOUND;
/* Since we just released the spinlock, we should be at PASSIVE_LEVEL - %ls is OK.
However, be extra paranoid, just in case. If we're at DISPATCH_LEVEL, then
just log an unknown adapter print - don't specify the GUID. */
if (KeGetCurrentIrql() <= PASSIVE_LEVEL) {
UNIV_PRINT_INFO(("Main_ioctl: Unknown adapter: %ls", pLocalHeader->device_name));
} else {
UNIV_PRINT_INFO(("Main_ioctl: Unknown adapter: IRQ level too high to print adapter GUID"));
}
TRACE_INFO("%!FUNC! Unknown adapter: %ls", pLocalHeader->device_name);
return NDIS_STATUS_SUCCESS;
}
/* Retrieve the context pointer from the global arrary of adapters. */
ctxtp = univ_adapters[adapter_index].ctxtp;
/* Add a reference on this context structure while we hold the bind/unbind lock. This
will make sure that this context cannot go away until after we are done with it.
The adapter MUST be inited at this point, or Main_adapter_get would not have
returned a valid adapter, so its OK to increment this reference count. */
Main_add_reference(ctxtp);
NdisReleaseSpinLock(&univ_bind_lock);
/* Since we just released the spinlock, we should be at PASSIVE_LEVEL - %ls is OK.
However, be extra paranoid, just in case. If we're at DISPATCH_LEVEL, then
just log an unknown adapter print - don't specify the GUID. */
if (KeGetCurrentIrql() <= PASSIVE_LEVEL) {
UNIV_PRINT_VERB(("Main_ioctl: Ioctl %08x for adapter: %ls", ioctl, pLocalHeader->device_name));
} else {
UNIV_PRINT_VERB(("Main_ioctl: Ioctl %08x for adapter: IRQ level too high to print adapter GUID"));
}
TRACE_VERB("%!FUNC! Ioctl 0x%08x for adapter %ls", ioctl, pLocalHeader->device_name);
switch (ioctl) {
case IOCTL_CVY_CLUSTER_ON:
case IOCTL_CVY_CLUSTER_OFF:
case IOCTL_CVY_PORT_ON:
case IOCTL_CVY_PORT_OFF:
case IOCTL_CVY_QUERY:
case IOCTL_CVY_RELOAD:
case IOCTL_CVY_PORT_DRAIN:
case IOCTL_CVY_CLUSTER_DRAIN:
case IOCTL_CVY_CLUSTER_SUSPEND:
case IOCTL_CVY_CLUSTER_RESUME:
case IOCTL_CVY_QUERY_FILTER:
case IOCTL_CVY_QUERY_PORT_STATE:
case IOCTL_CVY_QUERY_PARAMS:
case IOCTL_CVY_QUERY_BDA_TEAMING:
case IOCTL_CVY_CONNECTION_NOTIFY:
case IOCTL_CVY_QUERY_MEMBER_IDENTITY:
/* Process the IOCTL. The information for most IOCTLs is in the IOCTL_CVY_BUF, including
the return code, but for backward compatability reasons, new IOCTL information is in a
separate buffer - the options buffer. */
status = Main_ctrl(ctxtp, ioctl, &(pLocalHeader->ctrl), &(pLocalHeader->options.common), &(pLocalHeader->options), NULL);
break;
default:
UNIV_PRINT_CRIT(("Main_ioctl: Unknown Ioctl code: %x", ioctl));
TRACE_CRIT("%!FUNC! Unknown Ioctl code: 0x%x", ioctl);
status = STATUS_INVALID_PARAMETER;
break;
}
/* Release the reference on the context block. */
Main_release_reference(ctxtp);
return status;
}
/*
* Function: Main_set_host_state
* Desctription: This function queues a work item to set the current host
* state registry key, HostState. This MUST be done in a work
* item, rather than inline because if the state of the host
* changes as a result of the reception of a remote control
* request, that code will be running at DISPATCH_LEVEL;
* registry manipulation MUST be done at PASSIVE_LEVEL. Work
* items are complete at PASSIVE_LEVEL.
* Parameters: ctxtp - a pointer to the main context structure for this adapter.
* state - the new host state; one of started, stopped, suspended.
* Returns: Nothing
* Author: shouse, 7.13.01
* Notes: Because this callback will occur at some later time in the context
* of a kernel worker thread, there is a possibility that the adapter
* context (ctxtp) could disappear in the meantime, as the result of
* an unbind for instance. To prevent this from happening, we will
* increment the reference count on the context to keep it from being
* freed until we are done. The unbind code will sleep until this
* reference count reaches zero, so the unbind will be certain NOT to
* complete until all callbacks are processed. Other checking is in
* place to prevent this reference count from continuing to increase
* once an unbind has started. Note that that callback function is
* responsible for both freeing the memory we allocate here, and
* releasing the reference on the context.
*/
VOID Main_set_host_state (PMAIN_CTXT ctxtp, ULONG state) {
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
UNIV_ASSERT(ctxtp->code == MAIN_CTXT_CODE);
/* If the host state is already correct, don't bother to do anything. */
if (ctxtp->params.init_state == state)
return;
/* Set the desired state - this is the "cached" value. */
ctxtp->cached_state = state;
/* Schedule and NDIS work item to set the host state in the registry. */
status = Main_schedule_work_item(ctxtp, Params_set_host_state);
/* If we can't schedule the work item, bail out. */
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_set_host_state: Error 0x%08x -> Unable to allocate work item", status));
LOG_MSG(MSG_WARN_HOST_STATE_UPDATE, CVY_NAME_HOST_STATE);
}
return;
}
/*
* Function: Main_find_first_in_cache
* Description: Traverses cached identity information, to find the host with the smallest host_id.
* Parameters: PMAIN_CTXT ctxtp - the context for this NLB instance.
* Returns: The host_id of the found host or IOCTL_NO_SUCH_HOST if there was no valid entry in the cache.
* Author: ChrisDar - 20 May 2002
* Notes:
*/
ULONG Main_find_first_in_cache(
PMAIN_CTXT ctxtp
)
{
ULONG host_id = IOCTL_NO_SUCH_HOST;
ULONG i = 0;
/* Find the host in the cache with the smallest host_id */
for (i = 0; i < CVY_MAX_HOSTS; i++)
{
if (ctxtp->identity_cache[i].ttl > 0)
{
UNIV_ASSERT(i == ctxtp->identity_cache[i].host_id);
host_id = ctxtp->identity_cache[i].host_id;
break;
}
}
return host_id;
}
/*
* Function: Main_get_cached_hostmap
* Description: Builds a bitmap of the hosts with valid entries in the identity cache.
* Parameters: PMAIN_CTXT ctxtp - the context for this NLB instance.
* Returns: 32-bit bitmap of the host ids with valid identity cache entries.
* Author: ChrisDar - 20 May 2002
* Notes:
*/
ULONG Main_get_cached_hostmap(
PMAIN_CTXT ctxtp
)
{
ULONG host_map = 0;
ULONG i = 0;
for (i = 0; i < CVY_MAX_HOSTS; i++)
{
if (ctxtp->identity_cache[i].ttl > 0)
{
UNIV_ASSERT(i == ctxtp->identity_cache[i].host_id);
host_map |= (1<<i);
}
}
return host_map;
}
/*
* Function: Main_ctrl
* Description: This function performs a control function on a given NLB instance, such as
* RELOAD, STOP, START, etc.
* Parameters: ctxtp - a pointer to the adapter context for the operation.
* ioctl - the operation to be performed.
* pBuf - the (legacy) control buffer (input and output in some cases).
* pCommon - the input/output buffer for operations common to both local and remote control.
* pLocal - the input/output buffer for operations that can be executed locally only.
* pRemote - the input/output buffer for operations that can be executed remotely only.
* Returns: NDIS_STATUS - the status of the operation; NDIS_STATUS_SUCCESS if successful.
* Author: shouse, 3.29.01
* Notes:
*/
NDIS_STATUS Main_ctrl (
PMAIN_CTXT ctxtp, /* The context for the adapter on which to operate. */
ULONG ioctl, /* The IOCTL code. */
PIOCTL_CVY_BUF pBuf, /* Pointer to the CVY buf - should NEVER be NULL. */
PIOCTL_COMMON_OPTIONS pCommon, /* Pointer to the COMMON buf - CAN be NULL, but only for internal cluster control. */
PIOCTL_LOCAL_OPTIONS pLocal, /* Pointer to the LOCAL options - CAN be NULL if this is a remote control operation. */
PIOCTL_REMOTE_OPTIONS pRemote) /* Pointer to the REMOTE options - CAN be NULL if this is a local control operation. */
{
WCHAR num[20];
PMAIN_ADAPTER pAdapter;
BOOLEAN bConvergenceInitiated = FALSE;
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
UNIV_ASSERT(ctxtp);
UNIV_ASSERT(pBuf);
UNIV_ASSERT(!(pLocal && pRemote));
TRACE_VERB("->%!FUNC! Enter, ctxtp=%p, IOCTL=0x%x", ctxtp, ioctl);
/* Extract the MAIN_ADAPTER structure given the MAIN_CTXT. */
pAdapter = &(univ_adapters[ctxtp->adapter_id]);
NdisAcquireSpinLock(&univ_bind_lock);
/* Make sure that the adapter has been initialized before executing any control operations. */
if (!pAdapter->inited) {
NdisReleaseSpinLock(&univ_bind_lock);
UNIV_PRINT_CRIT(("Main_ctrl: Unbound from all NICs"));
TRACE_CRIT("%!FUNC! Unbound from all NICs");
TRACE_VERB("<-%!FUNC! return=NDIS_STATUS_FAILURE");
return NDIS_STATUS_FAILURE;
}
/* Check to make sure another control operation is not already in progress. If so, then we
must fail; only one control operation can be in progress at a time. If this is remote
control, we will assume the next request to arrive (five are sent) will succeed. */
if (ctxtp->ctrl_op_in_progress) {
NdisReleaseSpinLock(&univ_bind_lock);
UNIV_PRINT_CRIT(("Main_ctrl: Another control operation is in progress"));
TRACE_CRIT("%!FUNC! Another control operation is in progress");
TRACE_VERB("<-%!FUNC! return=NDIS_STATUS_FAILURE");
return NDIS_STATUS_FAILURE;
}
/* Mark the operation in progress flag. This MUST be re-set upon exit. */
ctxtp->ctrl_op_in_progress = TRUE;
NdisReleaseSpinLock(&univ_bind_lock);
/* Make sure data is written into pBuf/pCommon/pLocal/pRemote AFTER taking everything we need out of it. */
switch (ioctl) {
/* Reload the NLB parameters from the registry on a live cluster. */
case IOCTL_CVY_RELOAD:
{
PCVY_PARAMS old_params;
PCVY_PARAMS new_params;
ULONG old_ip;
ULONG mode;
CVY_MAC_ADR old_mac_addr;
BOOLEAN bHostPriorityChanged, bClusterIPChanged;
if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
KIRQL currentIrql = KeGetCurrentIrql();
UNIV_PRINT_CRIT(("Main_ctrl: IRQL (%u) > PASSIVE_LEVEL (%u)", currentIrql, PASSIVE_LEVEL));
TRACE_CRIT("%!FUNC! IRQL (%u) > PASSIVE_LEVEL (%u)", currentIrql, PASSIVE_LEVEL);
status = NDIS_STATUS_FAILURE;
break;
}
/* Allocate memory to hold a cached copy of our current parameters. */
status = NdisAllocateMemoryWithTag(&old_params, sizeof(CVY_PARAMS), UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_ctrl: Unable to allocate memory"));
TRACE_CRIT("%!FUNC! Unable to allocate memory");
LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, sizeof(CVY_PARAMS), status);
status = NDIS_STATUS_FAILURE;
break;
}
/* Take a snapshot of the old parameter set for later comparison. */
RtlCopyMemory(old_params, &ctxtp->params, sizeof(CVY_PARAMS));
/* Allocate memory to hold the new parameters - we don't want to clobber
our current parameters, just in case the new ones are invalid. */
status = NdisAllocateMemoryWithTag(&new_params, sizeof(CVY_PARAMS), UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_ctrl: Unable to allocate memory"));
TRACE_CRIT("%!FUNC! Unable to allocate memory");
LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, sizeof(CVY_PARAMS), status);
/* Free the memory we allocated for the cached parameters. */
NdisFreeMemory((PUCHAR)old_params, sizeof(CVY_PARAMS), 0);
status = NDIS_STATUS_FAILURE;
break;
}
/* Spin locks can not be acquired when we are calling Params_init
since it will be doing registry access operations that depend on
running at PASSIVEL_IRQL_LEVEL. Therefore, we gather the new
parameters into a temporary structure and copy them in the NLB
context protected by a spin lock. */
if (Params_init(ctxtp, univ_reg_path, pAdapter->device_name + 8, new_params) != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_ctrl: Error retrieving registry parameters"));
TRACE_CRIT("%!FUNC! Error retrieving registry parameters");
/* Alert the user to the bad parameters, but do not mark our parameters
as invalid because we are keeping the old set, which may or may not
be invalid, but regardless, there is no need to mark them - they should
have already been properly marked valid or invalid. */
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
/* Free the memory we allocated for the cached parameters. */
NdisFreeMemory((PUCHAR)old_params, sizeof(CVY_PARAMS), 0);
/* Free the memory we allocated for the new parameters. */
NdisFreeMemory((PUCHAR)new_params, sizeof(CVY_PARAMS), 0);
break;
}
NdisAcquireSpinLock(&ctxtp->load_lock);
/* At this point, we have verified that the new parameters are valid,
so copy them into the permanent parameters structure. */
RtlCopyMemory(&ctxtp->params, new_params, sizeof(CVY_PARAMS));
ctxtp->params_valid = TRUE;
/* Extract the host priority to a string for the purpose of event logging. */
Univ_ulong_to_str(ctxtp->params.host_priority, num, 10);
/* Check to see, by comparing the old parameters and the new parameters, whether
or not we can apply these changes without stopping and starting the load module. */
if (ctxtp->convoy_enabled && Main_apply_without_restart(ctxtp, old_params, &ctxtp->params)) {
NdisReleaseSpinLock(&ctxtp->load_lock);
pBuf->ret_code = IOCTL_CVY_OK;
/* Free the memory we allocated for the cached parameters. */
NdisFreeMemory((PUCHAR)old_params, sizeof(CVY_PARAMS), 0);
/* Free the memory we allocated for the new parameters. */
NdisFreeMemory((PUCHAR)new_params, sizeof(CVY_PARAMS), 0);
break;
}
/* Has the host priority changed ? */
bHostPriorityChanged = (new_params->host_priority != old_params->host_priority);
/* Free the memory we allocated for the cached parameters. */
NdisFreeMemory((PUCHAR)old_params, sizeof(CVY_PARAMS), 0);
/* Free the memory we allocated for the new parameters. */
NdisFreeMemory((PUCHAR)new_params, sizeof(CVY_PARAMS), 0);
/* Note the current on/off status of NLB. */
mode = ctxtp->convoy_enabled;
/* At this point, set the return value assuming success. */
pBuf->ret_code = IOCTL_CVY_OK;
/* If the cluster is currently running, we need to stop the load module. */
if (ctxtp->convoy_enabled) {
/* Turn off NLB. */
ctxtp->convoy_enabled = FALSE;
/* Stop the load module. Note: this destroys all connections in service. */
Load_stop(&ctxtp->load);
/* Take care of the case where we've interrupted draining. */
if (ctxtp->draining) {
/* If we were in the process of draining, stop. */
ctxtp->draining = FALSE;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Connection draining interrupted"));
TRACE_VERB("%!FUNC! Connection draining interrupted");
LOG_MSG(MSG_INFO_DRAINING_STOPPED, MSG_NONE);
/* Notify the user that we have interrupted draining. */
pBuf->ret_code = IOCTL_CVY_DRAINING_STOPPED;
} else {
NdisReleaseSpinLock(&ctxtp->load_lock);
}
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode stopped"));
TRACE_VERB("%!FUNC! Cluster mode stopped");
LOG_MSG(MSG_INFO_STOPPED, MSG_NONE);
} else {
NdisReleaseSpinLock(&ctxtp->load_lock);
}
/* Note our current cluster IP address. */
old_ip = ctxtp->cl_ip_addr;
/* Note our current cluster MAC address. */
CVY_MAC_ADDR_COPY(ctxtp->medium, &old_mac_addr, &ctxtp->cl_mac_addr);
/* Initialize the IP addresses and MAC addresses. If either of these fail,
consider this a parameter error and do NOT restart the cluster. */
if (!Main_ip_addr_init(ctxtp) || !Main_mac_addr_init(ctxtp)) {
/* Mark the parameters invalid. */
ctxtp->params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_ctrl: Parameter error: Main_ip_addr_init and/or Main_mac_addr_init failed"));
TRACE_CRIT("%!FUNC! Parameter error: Main_ip_addr_init and/or Main_mac_addr_init failed");
LOG_MSG(MSG_ERROR_DISABLED, MSG_NONE);
/* Notify the user that the supplied parameters are invalid. */
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
break;
}
#if defined (NLB_TCP_NOTIFICATION)
/* Now that the cluster IP address is set, try to map this adapter to its IP interface index. */
Main_set_interface_index(NULL, ctxtp);
#endif
/* Turn reverse-hashing off. If we're part of a BDA team, the teaming configuration
that is setup by the call to Main_teaming_init will over-write this setting. */
ctxtp->reverse_hash = FALSE;
/* Initialize BDA teaming. If this fails, consider this a parameter error and do NOT restart the cluster. */
if (!Main_teaming_init(ctxtp)) {
/* Mark the parameters invalid. */
ctxtp->params_valid = FALSE;
UNIV_PRINT_CRIT(("Main_ctrl: Parameter error: Main_teaming_init failed"));
TRACE_CRIT("parameter error: Main_teaming_init failed");
LOG_MSG(MSG_ERROR_DISABLED, MSG_NONE);
/* Notify the user that the supplied parameters are invalid. */
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
break;
}
/* If this cluster is operating in IGMP multicast mode, initialize IGMP. */
if (ctxtp->params.mcast_support && ctxtp->params.igmp_support)
Main_igmp_init(ctxtp, TRUE);
/* Look for changes to cluster name, machine name */
Tcpip_init (& ctxtp -> tcpip, & ctxtp -> params);
/* If our cluster IP address has changed, we block ARPs for a short period of
time until TCP/IP gets updated and has the correct information in order to
avoid ARP conflicts. */
if (old_ip != ctxtp->cl_ip_addr) {
bClusterIPChanged = TRUE;
NdisAcquireSpinLock(&ctxtp->load_lock);
/* Set the timer, which is decremented by each heartbeat timer. */
univ_changing_ip = ctxtp->params.ip_chg_delay;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Changing IP address with delay %d", univ_changing_ip));
TRACE_VERB("%!FUNC! Changing IP address with delay %d", univ_changing_ip);
}
else
{
bClusterIPChanged = FALSE;
}
LOG_MSG(MSG_INFO_RELOADED, MSG_NONE);
// If enabled, fire wmi event indicating reloading of nlb on this node
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_RELOADED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_RELOADED 'cos NodeControlEvent generation disabled");
}
/* If NLB was initially running, re-start the load module. */
if (mode) {
NdisAcquireSpinLock(&ctxtp->load_lock);
/* Start the load module. */
bConvergenceInitiated = Load_start(&ctxtp->load);
/* Turn on NLB. */
ctxtp->convoy_enabled = TRUE;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode started"));
TRACE_VERB("%!FUNC! Cluster mode started");
LOG_MSG(MSG_INFO_STARTED, num);
if (bConvergenceInitiated)
{
if (NlbWmiEvents[ConvergingEvent].Enable)
{
// If the host priority or cluster ip or cluster mac (among other things) has changed,
// fire a new member event, else fire a modified params event
if (bHostPriorityChanged
|| bClusterIPChanged
|| !CVY_MAC_ADDR_COMP(ctxtp->medium, &old_mac_addr, &ctxtp->cl_mac_addr)
)
{
NlbWmi_Fire_ConvergingEvent(ctxtp,
NLB_EVENT_CONVERGING_NEW_MEMBER,
ctxtp->params.ded_ip_addr,
ctxtp->params.host_priority);
}
else // Host priority, Cluster IP, Cluster MAC have not changed
{
NlbWmi_Fire_ConvergingEvent(ctxtp,
NLB_EVENT_CONVERGING_MODIFIED_PARAMS,
ctxtp->params.ded_ip_addr,
ctxtp->params.host_priority);
}
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_CONVERGING_NEW_MEMBER/MODIFIED_PARAMS, Convergnce NOT Initiated by Load_start() OR ConvergingEvent generation disabled");
}
}
}
/* Repopulate this host's identity heartbeat information */
NdisAcquireSpinLock(&ctxtp->load_lock);
Main_idhb_init(ctxtp);
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Turn off the bad host ID warning, in case it was turned on. */
ctxtp->bad_host_warned = FALSE;
UNIV_PRINT_VERB(("Main_ctrl: Parameters reloaded"));
break;
}
/* Resume a suspended cluster. */
case IOCTL_CVY_CLUSTER_RESUME:
{
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is not currently suspended, then this is a no-op. */
if (!ctxtp->suspended) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode already resumed"));
/* Tell the user that the cluster is alrady resumed. */
pBuf->ret_code = IOCTL_CVY_ALREADY;
break;
}
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
/* Resume the cluster by turning the suspend flag off. */
ctxtp->suspended = FALSE;
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Update the intial state registry key to STOPPED. */
Main_set_host_state(ctxtp, CVY_HOST_STATE_STOPPED);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode resumed"));
TRACE_VERB("%!FUNC! Cluster mode resumed");
LOG_MSG(MSG_INFO_RESUMED, MSG_NONE);
// If enabled, fire wmi event indicating resuming of nlb on this node
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_RESUMED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_RESUMED 'cos NodeControlEvent generation disabled");
}
/* Return success. */
pBuf->ret_code = IOCTL_CVY_OK;
break;
}
/* Start a stopped or draining cluster. */
case IOCTL_CVY_CLUSTER_ON:
{
/* Store the host priority in a string for the purpose of event logging. */
Univ_ulong_to_str(ctxtp->params.host_priority, num, 10);
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is suspended, it ignores all control operations except resume. */
if (ctxtp->suspended) {
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode is suspended"));
/* Alert the user that the cluster is currently suspended. */
pBuf->ret_code = IOCTL_CVY_SUSPENDED;
break;
}
/* Otherwise, if the cluster is draining, stop draining connections. */
if (ctxtp->draining) {
UNIV_ASSERT(ctxtp->convoy_enabled);
UNIV_PRINT_VERB(("Main_ctrl: START: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", IOCTL_ALL_VIPS, IOCTL_ALL_PORTS, 0));
/* Call Load_port_change to "plug" draining - that is, restore the original load
balancing policy on ALL port rules (ALL VIPs, ALL ports). */
pBuf->ret_code = Load_port_change(&ctxtp->load, IOCTL_ALL_VIPS, IOCTL_ALL_PORTS, IOCTL_CVY_CLUSTER_PLUG, 0);
/* Turn draining off. */
ctxtp->draining = FALSE;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Connection draining interrupted"));
TRACE_VERB("%!FUNC! Connection draining interrupted");
LOG_MSG(MSG_INFO_DRAINING_STOPPED, MSG_NONE);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode started"));
TRACE_VERB("%!FUNC! Cluster mode started");
LOG_MSG(MSG_INFO_STARTED, num);
/* Alert the user that draining was interrupted. */
pBuf->ret_code = IOCTL_CVY_DRAINING_STOPPED;
break;
}
/* Otherwise, if the cluster is running, this is a no-op. */
if (ctxtp->convoy_enabled) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode already started"));
/* Alert the user that the cluster is already started. */
pBuf->ret_code = IOCTL_CVY_ALREADY;
break;
}
/* Otherwise, if the cluster is stopped because of invalid parameters, we WILL
NOT restart the cluster until the parameters are fixed and reloaded. */
if (!ctxtp->params_valid) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Parameter error"));
TRACE_VERB("%!FUNC! Parameter error");
LOG_MSG(MSG_ERROR_DISABLED, MSG_NONE);
/* Alert the user that the parameters are bad. */
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
break;
}
/* Otherwise, we are starting a stopped cluster - start the load module. */
bConvergenceInitiated = Load_start(&ctxtp->load);
/* Turn on NLB. */
ctxtp->convoy_enabled = TRUE;
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Update the intial state registry key to STARTED. */
Main_set_host_state(ctxtp, CVY_HOST_STATE_STARTED);
/* Turn off the bad host ID warning, in case it was turned on. */
ctxtp->bad_host_warned = FALSE;
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode started"));
TRACE_VERB("%!FUNC! Cluster mode started");
LOG_MSG(MSG_INFO_STARTED, num);
// If enabled, fire wmi event indicating "start" nlb
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_STARTED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_STARTED 'cos NodeControlEvent generation disabled");
}
if (bConvergenceInitiated && NlbWmiEvents[ConvergingEvent].Enable)
{
NlbWmi_Fire_ConvergingEvent(ctxtp,
NLB_EVENT_CONVERGING_NEW_MEMBER,
ctxtp->params.ded_ip_addr,
ctxtp->params.host_priority);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_CONVERGING_NEW_MEMBER, Convergnce NOT Initiated by Load_start() OR ConvergingEvent generation disabled");
}
/* Return success. */
pBuf->ret_code = IOCTL_CVY_OK;
break;
}
/* Suspend a running cluster. Suspend differs from stop in that suspended cluster will
further ignore ALL control message (remote and local) until a resume is issued. */
case IOCTL_CVY_CLUSTER_SUSPEND:
{
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is already suspended, this is a no-op. */
if (ctxtp->suspended) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode already suspended"));
/* Alert the user that the cluster is already suspended. */
pBuf->ret_code = IOCTL_CVY_ALREADY;
break;
}
/* Set the suspended flag, effectively suspending the cluster operations. */
ctxtp->suspended = TRUE;
/* If the cluster is running, stop it. */
if (ctxtp->convoy_enabled) {
/* Turn NLB off. */
ctxtp->convoy_enabled = FALSE;
/* Set the stopping flag, which is used to allow one last heartbeat to go
out before a host stops in order to expedite convergence. */
ctxtp->stopping = TRUE;
/* Stop the load module and release all connections. */
Load_stop(&ctxtp->load);
/* If the cluster was draining, stop draining - we've already kill all
connection state anyway (in Load_stop). */
if (ctxtp->draining) {
/* Turn off the draining flag. */
ctxtp->draining = FALSE;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Connection draining interrupted"));
TRACE_VERB("%!FUNC! Connection draining interrupted");
LOG_MSG(MSG_INFO_DRAINING_STOPPED, MSG_NONE);
/* Alert the user that draining was interrupted. */
pBuf->ret_code = IOCTL_CVY_DRAINING_STOPPED;
} else {
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Alert the user that the cluster was stopped. */
pBuf->ret_code = IOCTL_CVY_STOPPED;
}
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode stopped"));
TRACE_VERB("%!FUNC! Cluster mode stopped");
LOG_MSG(MSG_INFO_STOPPED, MSG_NONE);
} else {
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Return success - the cluster was already stopped. */
pBuf->ret_code = IOCTL_CVY_OK;
}
/* Update the intial state registry key to SUSPENDED. */
Main_set_host_state(ctxtp, CVY_HOST_STATE_SUSPENDED);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode suspended"));
TRACE_VERB("%!FUNC! Cluster mode suspended");
LOG_MSG(MSG_INFO_SUSPENDED, MSG_NONE);
// If enabled, fire wmi event indicating suspending of nlb on this node
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_SUSPENDED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_SUSPENDED 'cos NodeControlEvent generation disabled");
}
break;
}
/* Stop a running cluster. */
case IOCTL_CVY_CLUSTER_OFF:
{
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is suspended, ignore this request. */
if (ctxtp->suspended) {
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode is suspended"));
/* Alert the user that the cluster is in the suspended state. */
pBuf->ret_code = IOCTL_CVY_SUSPENDED;
break;
}
/* If the cluster is already stopped, this is a no-op. */
if (!ctxtp->convoy_enabled) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode already stopped"));
/* Alert the user that the cluster is already stopped. */
pBuf->ret_code = IOCTL_CVY_ALREADY;
break;
}
/* Turn NLB off. */
ctxtp->convoy_enabled = FALSE;
/* Set the stopping flag, which is used to allow one last heartbeat to go
out before a host stops in order to expedite convergence. */
ctxtp->stopping = TRUE;
/* Stop the load module. */
Load_stop(&ctxtp->load);
/* If we were in the process of draining, stop draining because all
existing connection state was just trashed by Load_stop. */
if (ctxtp->draining) {
/* Turn off the draining flag. */
ctxtp->draining = FALSE;
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Connection draining interrupted"));
TRACE_VERB("%!FUNC! Connection draining interrupted");
LOG_MSG(MSG_INFO_DRAINING_STOPPED, MSG_NONE);
/* Alert the user that we have interrupted draining. */
pBuf->ret_code = IOCTL_CVY_DRAINING_STOPPED;
} else {
NdisReleaseSpinLock(&ctxtp->load_lock);
/* Return success. */
pBuf->ret_code = IOCTL_CVY_OK;
}
/* Update the intial state registry key to STOPPED. */
Main_set_host_state(ctxtp, CVY_HOST_STATE_STOPPED);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode stopped"));
TRACE_VERB("%!FUNC! Cluster mode stopped");
LOG_MSG(MSG_INFO_STOPPED, MSG_NONE);
// If enabled, fire wmi event indicating stopping of nlb
if (NlbWmiEvents[NodeControlEvent].Enable)
{
NlbWmi_Fire_NodeControlEvent(ctxtp, NLB_EVENT_NODE_STOPPED);
}
else
{
TRACE_VERB("%!FUNC! NOT generating NLB_EVENT_NODE_STOPPED 'cos NodeControlEvent generation disabled");
}
break;
}
/* Drain a running cluster. Draining differs from a stop in that the cluster
will continue to server existing connections (TCP and IPSec), but not take
any new connections. */
case IOCTL_CVY_CLUSTER_DRAIN:
{
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is suspended, ignore this request. */
if (ctxtp->suspended) {
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode is suspended"));
/* Alert the user that the cluster is in a suspended state. */
pBuf->ret_code = IOCTL_CVY_SUSPENDED;
break;
}
/* If the cluster is already stopped, this is a no-op. */
if (!ctxtp->convoy_enabled) {
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cannot drain connections while cluster is stopped"));
/* Alert the user that the cluster is already stopped. */
pBuf->ret_code = IOCTL_CVY_STOPPED;
break;
}
/* If the cluster is already draining, then this is a no-op. */
if (ctxtp->draining) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Already draining"));
/* Alert the user the the cluster is in the process of draining. */
pBuf->ret_code = IOCTL_CVY_ALREADY;
break;
}
/* Turn on the draining flag, which is used during Main_ping to
turn the cluster off once all connections have been completed. */
ctxtp->draining = TRUE;
UNIV_PRINT_VERB(("Main_ctrl: DRAIN: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", IOCTL_ALL_VIPS, IOCTL_ALL_PORTS, 0));
/* Change the load weight of ALL port rules to zero, so that this
host will not accept any NEW connections. Existing connections
will be serviced because this host will have descriptors for
those connections in the load module. */
pBuf->ret_code = Load_port_change(&ctxtp->load, IOCTL_ALL_VIPS, IOCTL_ALL_PORTS, ioctl, 0);
NdisReleaseSpinLock(&ctxtp->load_lock);
/* If the load module successfully drained the port rules, log an event. */
if (pBuf->ret_code == IOCTL_CVY_OK) {
UNIV_PRINT_VERB(("Main_ctrl: Connection draining started"));
TRACE_VERB("%!FUNC! Connection draining started");
LOG_MSG(MSG_INFO_CLUSTER_DRAINED, MSG_NONE);
}
break;
}
/* Restore a port rule to its original load balancing scheme (enable) or
specifically set the load weight of a port rule. Note: _SET is no
longer supported by user-level NLB applications, but the notification
to the load module is still used internally. */
case IOCTL_CVY_PORT_ON:
case IOCTL_CVY_PORT_SET:
{
/* Extract this host's ID for the sake of event logging. */
Univ_ulong_to_str(pBuf->data.port.num, num, 10);
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is suspended, then all control operations are ignored. */
if (ctxtp->suspended) {
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode is suspended"));
/* Alert the user that the cluster is in the suspended state. */
pBuf->ret_code = IOCTL_CVY_SUSPENDED;
break;
}
/* If the cluster is stopped, then the load module is turned off,
so we should ignore this request as well. */
if (!ctxtp->convoy_enabled) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cannot enable/disable port while cluster is stopped"));
TRACE_VERB("%!FUNC! Cannot enable/disable port while cluster is stopped");
LOG_MSG(MSG_WARN_CLUSTER_STOPPED, MSG_NONE);
/* Alert the user that the cluster is stopped. */
pBuf->ret_code = IOCTL_CVY_STOPPED;
break;
}
/* If there is no common buffer, which can happen if this is a remote control request from a down-level
client, then we hard-code the VIP to 0xffffffff. If the cluster is operating in a mode that is
compatible with down-level clients, i.e. no per-VIP port rules, no BDA, etc, then hardcoding this
parameter will not matter (the affect is not altered by its value), otherwise we shouldn't get to
here because Main_ctrl_recv is supposed to block those requests. */
if (!pCommon) {
UNIV_PRINT_VERB(("Main_ctrl: ENABLE: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", IOCTL_ALL_VIPS, pBuf->data.port.num, pBuf->data.port.load));
pBuf->ret_code = Load_port_change(&ctxtp->load, IOCTL_ALL_VIPS, pBuf->data.port.num, ioctl, pBuf->data.port.load);
/* If there is a common buffer, then we can extract the VIP from there. */
} else {
UNIV_PRINT_VERB(("Main_ctrl: ENABLE: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", pCommon->port.vip, pBuf->data.port.num, pBuf->data.port.load));
pBuf->ret_code = Load_port_change(&ctxtp->load, pCommon->port.vip, pBuf->data.port.num, ioctl, pBuf->data.port.load);
}
NdisReleaseSpinLock(&ctxtp->load_lock);
/* spew the result to an event log. */
if (pBuf->ret_code == IOCTL_CVY_NOT_FOUND) {
UNIV_PRINT_VERB(("Main_ctrl: Port %d not found in any of the valid port rules", pBuf->data.port.num));
TRACE_VERB("%!FUNC! Port %d not found in any of the valid port rules", pBuf->data.port.num);
LOG_MSG(MSG_WARN_PORT_NOT_FOUND, num);
} else if (pBuf->ret_code == IOCTL_CVY_OK) {
if (ioctl == IOCTL_CVY_PORT_ON) {
if (pBuf->data.port.num == IOCTL_ALL_PORTS) {
UNIV_PRINT_VERB(("Main_ctrl: All port rules enabled"));
TRACE_VERB("%!FUNC! All port rules enabled");
LOG_MSG(MSG_INFO_PORT_ENABLED_ALL, MSG_NONE);
} else {
UNIV_PRINT_VERB(("Main_ctrl: Rule for port %d enabled", pBuf->data.port.num));
TRACE_VERB("%!FUNC! Rule for port %d enabled", pBuf->data.port.num);
LOG_MSG(MSG_INFO_PORT_ENABLED, num);
}
} else {
if (pBuf->data.port.num == IOCTL_ALL_PORTS) {
UNIV_PRINT_VERB(("Main_ctrl: All port rules adjusted"));
TRACE_VERB("%!FUNC! All port rules adjusted");
LOG_MSG(MSG_INFO_PORT_ADJUSTED_ALL, MSG_NONE);
} else {
UNIV_PRINT_VERB(("Main_ctrl: Rule for port %d adjusted to %d", pBuf->data.port.num, pBuf->data.port.load));
TRACE_VERB("%!FUNC! Rule for port %d adjusted to %d", pBuf->data.port.num, pBuf->data.port.load);
LOG_MSG(MSG_INFO_PORT_ADJUSTED, num);
}
}
} else {
UNIV_PRINT_VERB(("Main_ctrl: Port %d already enabled", pBuf->data.port.num));
}
break;
}
/* Shut-off (disable) or drain the connections on a port rule. Disabling a port rule
will destroy all connection information about currently active TCP connections and
cause convergence so that this host will not handle any more connections on this
port rule. Draining also sets the load weigh to zero, but does not destroy the
connection descriptors, such that this host continues to handle existing connections. */
case IOCTL_CVY_PORT_OFF:
case IOCTL_CVY_PORT_DRAIN:
{
/* Extract this host's ID for the sake of event logging. */
Univ_ulong_to_str(pBuf->data.port.num, num, 10);
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If the cluster is suspended, then all control operations are ignored. */
if (ctxtp->suspended) {
UNIV_ASSERT(!ctxtp->convoy_enabled);
UNIV_ASSERT(!ctxtp->draining);
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cluster mode is suspended"));
/* Alert the user that the cluster is in the suspended state. */
pBuf->ret_code = IOCTL_CVY_SUSPENDED;
break;
}
/* If the cluster is stopped, then the load module is turned off,
so we should ignore this request as well. */
if (!ctxtp->convoy_enabled) {
NdisReleaseSpinLock(&ctxtp->load_lock);
UNIV_PRINT_VERB(("Main_ctrl: Cannot enable/disable port while cluster is stopped"));
TRACE_VERB("%!FUNC! Cannot enable/disable port while cluster is stopped");
LOG_MSG(MSG_WARN_CLUSTER_STOPPED, MSG_NONE);
/* Alert the user that the cluster is stopped. */
pBuf->ret_code = IOCTL_CVY_STOPPED;
break;
}
/* If there is no common buffer, which can happen if this is a remote control request from a down-level
client, then we hard-code the VIP to 0xffffffff. If the cluster is operating in a mode that is
compatible with down-level clients, i.e. no per-VIP port rules, no BDA, etc, then hardcoding this
parameter will not matter (the affect is not altered by its value), otherwise we shouldn't get to
here because Main_ctrl_recv is supposed to block those requests. */
if (!pCommon) {
UNIV_PRINT_VERB(("Main_ctrl: DISABLE: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", IOCTL_ALL_VIPS, pBuf->data.port.num, pBuf->data.port.load));
pBuf->ret_code = Load_port_change(&ctxtp->load, IOCTL_ALL_VIPS, pBuf->data.port.num, ioctl, pBuf->data.port.load);
/* If there is a common buffer, then we can extract the VIP from there. */
} else {
UNIV_PRINT_VERB(("Main_ctrl: DISABLE: Calling Load_port_change -> VIP=%08x, port=%d, load=%d\n", pCommon->port.vip, pBuf->data.port.num, pBuf->data.port.load));
pBuf->ret_code = Load_port_change(&ctxtp->load, pCommon->port.vip, pBuf->data.port.num, ioctl, pBuf->data.port.load);
}
NdisReleaseSpinLock(&ctxtp->load_lock);
/* spew the result to an event log. */
if (pBuf->ret_code == IOCTL_CVY_NOT_FOUND) {
UNIV_PRINT_VERB(("Main_ctrl: Port %d not found in any of the valid port rules", pBuf->data.port.num));
TRACE_VERB("%!FUNC! Port %d not found in any of the valid port rules", pBuf->data.port.num);
LOG_MSG(MSG_WARN_PORT_NOT_FOUND, num);
} else if (pBuf->ret_code == IOCTL_CVY_OK) {
if (ioctl == IOCTL_CVY_PORT_OFF) {
if (pBuf->data.port.num == IOCTL_ALL_PORTS) {
UNIV_PRINT_VERB(("Main_ctrl: All port rules disabled"));
TRACE_VERB("%!FUNC! All port rules disabled");
LOG_MSG(MSG_INFO_PORT_DISABLED_ALL, MSG_NONE);
} else {
UNIV_PRINT_VERB(("Main_ctrl: Rule for port %d disabled", pBuf->data.port.num));
TRACE_VERB("%!FUNC! Rule for port %d disabled", pBuf->data.port.num);
LOG_MSG(MSG_INFO_PORT_DISABLED, num);
}
} else {
if (pBuf->data.port.num == IOCTL_ALL_PORTS) {
UNIV_PRINT_VERB(("Main_ctrl: All port rules drained"));
TRACE_VERB("%!FUNC! All port rules drained");
LOG_MSG(MSG_INFO_PORT_DRAINED_ALL, MSG_NONE);
} else {
UNIV_PRINT_VERB(("Main_ctrl: Rule for port %d drained", pBuf->data.port.num));
TRACE_VERB("%!FUNC! Rule for port %d drained", pBuf->data.port.num);
LOG_MSG(MSG_INFO_PORT_DRAINED, num);
}
}
} else {
UNIV_PRINT_VERB(("Main_ctrl: Port %d already disabled", pBuf->data.port.num));
}
break;
}
/* Query this host for its current state and the membership of the cluster. */
case IOCTL_CVY_QUERY:
{
NdisAcquireSpinLock(&ctxtp->load_lock);
/* If this is a local query, by default, indicate no convergence information. */
if (pLocal)
pLocal->query.flags &= ~NLB_OPTIONS_QUERY_CONVERGENCE;
/* If the cluster is suspended, then it won't actually be an active member of
the cluster, so we can't provide a membership map - just alert the user to
the fact that this host is suspended. */
if (ctxtp->suspended) {
UNIV_PRINT_VERB(("Main_ctrl: Cannot query status - this host is suspended"));
pBuf->data.query.state = IOCTL_CVY_SUSPENDED;
/* If the cluster is stopped, then we are not part of the cluster and cannot
provide any membership information, so just let the user know that this
host is stopped. */
} else if (!ctxtp->convoy_enabled) {
UNIV_PRINT_VERB(("Main_ctrl: Cannot query status - this host is not part of the cluster"));
pBuf->data.query.state = IOCTL_CVY_STOPPED;
/* If the adapter is disconnected from the network, then we aren't sending or
receiving heartbeats, so we are again not part of the cluster - just let
the user know that the media is disconnected. */
} else if (!ctxtp->media_connected || !MAIN_PNP_DEV_ON(ctxtp)) {
UNIV_PRINT_VERB(("Main_ctrl: Cannot query status - this host is not connected to the network"));
pBuf->data.query.state = IOCTL_CVY_DISCONNECTED;
} else {
ULONG tmp_host_map;
/* Query the load module for the state of convergence and the cluster membership. */
pBuf->data.query.state = (USHORT)Load_hosts_query(&ctxtp->load, FALSE, &tmp_host_map);
/* Return the host map to the request initiator. */
pBuf->data.query.host_map = tmp_host_map;
/* If this is a local query, then we may have to include convergence information. */
if (pLocal) {
ULONG num_cvgs;
ULONG last_cvg;
/* If the load module is active, this function returns TRUE and fills
in the total number of convergences and the last time convergence
completed. If the load module is inactive, it returns FALSE. */
if (Load_query_convergence_info(&ctxtp->load, &num_cvgs, &last_cvg)) {
pLocal->query.flags |= NLB_OPTIONS_QUERY_CONVERGENCE;
pLocal->query.NumConvergences = num_cvgs;
pLocal->query.LastConvergence = last_cvg;
} else {
pLocal->query.flags &= ~NLB_OPTIONS_QUERY_CONVERGENCE;
}
}
/* If we are draining connections and the load module is converged, then
tell the user that we are draining; otherwise the notification that the
cluster is converging should supercede draining notification. */
if (ctxtp->draining && pBuf->data.query.state != IOCTL_CVY_CONVERGING)
pBuf->data.query.state = IOCTL_CVY_DRAINING;
}
/* Return our host ID to the requestee. */
pBuf->data.query.host_id = (USHORT)ctxtp->params.host_priority;
NdisReleaseSpinLock(&ctxtp->load_lock);
/* If the request is from a host that supports the hostname in the remote
control protocol (versions >= Win XP), copy the hostname and set the
hostname flag to let the request initiator that we have filled it in. */
if (pRemote) {
if (ctxtp->params.hostname[0] != UNICODE_NULL) {
/* Since ctxtp->params.hostname can be larger than the space available
in the remote control packet, assume it's too big and do a truncate */
NdisMoveMemory(pRemote->query.hostname, ctxtp->params.hostname, (CVY_MAX_HOST_NAME) * sizeof(WCHAR));
pRemote->query.hostname[CVY_MAX_HOST_NAME] = UNICODE_NULL;
pRemote->query.flags |= NLB_OPTIONS_QUERY_HOSTNAME;
} else {
pRemote->query.flags &= ~NLB_OPTIONS_QUERY_HOSTNAME;
}
}
break;
}
case IOCTL_CVY_QUERY_BDA_TEAMING:
{
UNIV_ASSERT(pLocal);
/* Security fix: Forcefully null terminate BDA Team Id, just in case */
pLocal->state.bda.TeamID[(sizeof(pLocal->state.bda.TeamID)/sizeof(WCHAR)) - 1] = UNICODE_NULL;
/* Query NLB for the current state of a given BDA team. This function will attempt
to find the given team in the global linked list of teams. If it finds the spec-
ified team, it will fill in the state of the team as well as information about
the configuration of each member. */
pBuf->ret_code = Main_query_bda_teaming(ctxtp, &pLocal->state.bda);
break;
}
/* Query the given NLB instance for its current parameter set, basically copying the
contents of our CVY_PARAMS structure into the provided IOCTL_REG_PARAMS structure. */
case IOCTL_CVY_QUERY_PARAMS:
{
UNIV_ASSERT(pLocal);
/* Query NLB for the current parameter set for this adapter - this essentially just
copies the parameters in the driver data structures to the IOCTL buffer, and in
most instances the parameters will match what is set in the registry. */
Main_query_params(ctxtp, &pLocal->state.params);
/* This operation ALWAYS returns success because it CAN'T really fail. */
pBuf->ret_code = IOCTL_CVY_OK;
break;
}
/* Given a port number, query the load module for its state, including whether the port
rule is enabled, disabled or in the act of draining. This also gathers some packet
handling statistics including bytes and packets handled and dropped on this port rule. */
case IOCTL_CVY_QUERY_PORT_STATE:
{
UNIV_ASSERT(pCommon);
/* Query the load module for the state of the specified rule. The load module will
look up the port rule and fill in the buffer we pass it with the applicable state
and statistical data. */
Load_query_port_state(&ctxtp->load, &pCommon->state.port, pCommon->state.port.VirtualIPAddress, pCommon->state.port.Num);
/* This operation ALWAYS returns success because it CAN'T really fail. */
pBuf->ret_code = IOCTL_CVY_OK;
break;
}
/* Given a IP tuple (client IP, client port, server IP, server port) and a protocol,
determine whether or not this host would accept the packet and why or why not. It
is important that this is performed completely unobtrusively and has no side-effects
on the actual operation of NLB and the load module. */
case IOCTL_CVY_QUERY_FILTER:
{
UNIV_ASSERT(pCommon);
/* Query NLB for packet filter information for this IP tuple and protocol. Main_query_packet_filter
checks the NDIS driver information for filtering issues such as DIP traffic, BDA teaming and
NLB driver state (on/off due to wlbs.exe commands and parameter errors). If necessary, it then
consults the load module to perform the actual port rule lookup and determine packet acceptance. */
Main_query_packet_filter(ctxtp, &pCommon->state.filter);
/* This operation ALWAYS returns success because it CAN'T really fail. */
pBuf->ret_code = IOCTL_CVY_OK;
break;
}
case IOCTL_CVY_CONNECTION_NOTIFY:
{
PNLB_OPTIONS_CONN_NOTIFICATION pConn;
BOOLEAN bRet = TRUE;
UNIV_ASSERT(pLocal);
/* This function is only supported locally. */
pConn = &pLocal->notification.conn;
/* Make sure that the parameters from the input buffer are valid. */
if (pConn->Protocol != TCPIP_PROTOCOL_IPSEC1) {
TRACE_PACKET("%!FUNC! Bad connection notification params");
/* Use the return code in the IOCTL buffer to relay a parameter error. */
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
break;
}
switch (pConn->Operation) {
case NLB_CONN_UP:
{
#if defined (NLB_HOOK_ENABLE)
NLB_FILTER_HOOK_DIRECTIVE filter = NLB_FILTER_HOOK_PROCEED_WITH_HASH;
#endif
TRACE_PACKET("%!FUNC! Got a CONN_UP for 0x%08x:%u -> 0x%08x:%u (%u)", pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol);
#if defined (NLB_HOOK_ENABLE)
/* Invoke the packet query hook, if one has been registered. */
filter = Main_query_hook(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol);
/* Process some of the hook responses. */
if (filter == NLB_FILTER_HOOK_REJECT_UNCONDITIONALLY)
{
/* If the hook asked us to reject this packet, then we can do so here. */
TRACE_INFO("%!FUNC! Packet receive filter hook: REJECT packet");
/* Return REFUSED to notify the caller that a call to WlbsConnectionDown/Reset is NOT necessary. */
pBuf->ret_code = IOCTL_CVY_REQUEST_REFUSED;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
else if (filter == NLB_FILTER_HOOK_ACCEPT_UNCONDITIONALLY)
{
/* If the hook asked us to accept this packet, then break out and don't create state. */
TRACE_INFO("%!FUNC! Packet receive filter hook: ACCEPT packet");
/* Return REFUSED to notify the caller that a call to WlbsConnectionDown/Reset is NOT necessary. */
pBuf->ret_code = IOCTL_CVY_REQUEST_REFUSED;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
#endif
/* Notify the load module of the upcoming connection. */
#if defined (NLB_TCP_NOTIFICATION)
if (NLB_NOTIFICATIONS_ON())
{
#if defined (NLB_HOOK_ENABLE)
bRet = Main_conn_up(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, filter);
#else
bRet = Main_conn_up(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol);
#endif
}
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_UP, filter);
#else
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_UP);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
/* If the packet was refused by BDA or rejected by the load module, return refusal error, otherwise, it succeeded. */
if (!bRet)
pBuf->ret_code = IOCTL_CVY_REQUEST_REFUSED;
else
pBuf->ret_code = IOCTL_CVY_OK;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
case NLB_CONN_DOWN:
{
TRACE_PACKET("%!FUNC! Got a CONN_DOWN for 0x%08x:%u -> 0x%08x:%u (%u)", pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol);
/* Notify the load module that a connection is being torn down. */
#if defined (NLB_TCP_NOTIFICATION)
if (NLB_NOTIFICATIONS_ON())
{
bRet = Main_conn_down(pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_DOWN);
}
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_DOWN, NLB_FILTER_HOOK_PROCEED_WITH_HASH);
#else
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_DOWN);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
/* If the packet was refused by BDA or rejected by the load module, return refusal error, otherwise, it succeeded. */
if (!bRet)
pBuf->ret_code = IOCTL_CVY_REQUEST_REFUSED;
else
pBuf->ret_code = IOCTL_CVY_OK;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
case NLB_CONN_RESET:
{
TRACE_PACKET("%!FUNC! Got a CONN_RESET for 0x%08x:%u -> 0x%08x:%u (%u)", pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol);
/* Notify the load module that a connection is being reset. */
#if defined (NLB_TCP_NOTIFICATION)
if (NLB_NOTIFICATIONS_ON())
{
bRet = Main_conn_down(pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_RESET);
}
else
{
#endif
#if defined (NLB_HOOK_ENABLE)
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_RESET, NLB_FILTER_HOOK_PROCEED_WITH_HASH);
#else
bRet = Main_conn_notify(ctxtp, pConn->ServerIPAddress, pConn->ServerPort, pConn->ClientIPAddress, pConn->ClientPort, pConn->Protocol, CVY_CONN_RESET);
#endif
#if defined (NLB_TCP_NOTIFICATION)
}
#endif
/* If the packet was refused by BDA or rejected by the load module, return refusal error, otherwise, it succeeded. */
if (!bRet)
pBuf->ret_code = IOCTL_CVY_REQUEST_REFUSED;
else
pBuf->ret_code = IOCTL_CVY_OK;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
default:
TRACE_PACKET("%!FUNC! Unknown connection notification operation: %d", pConn->Operation);
/* This should never, ever happen, but if it does, signal a generic NLB error and fail. */
pBuf->ret_code = IOCTL_CVY_GENERIC_FAILURE;
TRACE_PACKET("%!FUNC! Returning %u", pBuf->ret_code);
break;
}
break;
}
case IOCTL_CVY_QUERY_MEMBER_IDENTITY:
NdisAcquireSpinLock(&ctxtp->load_lock);
pBuf->ret_code = IOCTL_CVY_GENERIC_FAILURE;
UNIV_ASSERT(pLocal);
if (pLocal != NULL)
{
/* Get the input host_id from the caller, which has range 1-32. */
ULONG user_host_id = pLocal->identity.host_id;
/* The driver's host id has range 0-31 */
ULONG driver_host_id = IOCTL_NO_SUCH_HOST;
/* Zero the response structure and initialize the host property to "not found" */
NdisZeroMemory(&pLocal->identity.cached_entry, sizeof(pLocal->identity.cached_entry));
pLocal->identity.cached_entry.host = IOCTL_NO_SUCH_HOST;
/* Fill in the host map */
pLocal->identity.host_map = Main_get_cached_hostmap(ctxtp);
/* Validate the host id the user is requesting information on */
if (((user_host_id > CVY_MAX_HOSTS) || (user_host_id < CVY_MIN_MAX_HOSTS)) &&
(user_host_id != IOCTL_FIRST_HOST))
{
pBuf->ret_code = IOCTL_CVY_BAD_PARAMS;
UNIV_PRINT_CRIT(("Main_ctrl: Illegal host_id [1, 32] %d from user while requesting cached identity information", user_host_id));
TRACE_CRIT("%!FUNC! Illegal host_id [1, 32] %d from user while requesting cached identity information", user_host_id);
goto endcase_identity;
}
/* Any response after this is a success */
pBuf->ret_code = IOCTL_CVY_OK;
/* Special case if the caller wants the first in the list */
if (user_host_id == IOCTL_FIRST_HOST)
{
driver_host_id = Main_find_first_in_cache(ctxtp);
/* Main_find_first_in_cache returns CVY_MAX_HOSTS if the cache is empty, so not an error if leaving case here. */
if (driver_host_id >= CVY_MAX_HOSTS)
{
UNIV_PRINT_VERB(("Main_ctrl: Identity cache is empty"));
TRACE_VERB("%!FUNC! No host information in the identity cache");
goto endcase_identity;
}
}
else
{
/* Already verified that user_host_id is valid (1-32). Convert to driver-based host id (0-31) */
driver_host_id = user_host_id - 1;
}
/* We have a legal driver_host_id (0-31 range). Retrieve the cache information if it is currently valid. Return "not found" otherwise. */
if (ctxtp->identity_cache[driver_host_id].ttl > 0)
{
ULONG ulFqdnCB = min(sizeof(pLocal->identity.cached_entry.fqdn) - sizeof(WCHAR),
sizeof(WCHAR)*wcslen(ctxtp->identity_cache[driver_host_id].fqdn)
);
/* Convert host id back to range 1-32 for the user */
pLocal->identity.cached_entry.host = ctxtp->identity_cache[driver_host_id].host_id + 1;
pLocal->identity.cached_entry.ded_ip_addr = ctxtp->identity_cache[driver_host_id].ded_ip_addr;
if (ulFqdnCB > 0)
{
NdisMoveMemory(&pLocal->identity.cached_entry.fqdn, &ctxtp->identity_cache[driver_host_id].fqdn, ulFqdnCB);
}
}
}
endcase_identity:
NdisReleaseSpinLock(&ctxtp->load_lock);
break;
default:
UNIV_PRINT_CRIT(("Main_ctrl: Bad IOCTL %x", ioctl));
status = NDIS_STATUS_FAILURE;
break;
}
/* Re-set the control operation in progress flag. Note that it is not strictly
necessary to hold the lock while re-setting this flag; the worst that will
happen is that we cause another thread to fail to start a control operation
when it might have been able to enter the critical section. To avoid that
possiblilty, hold the univ_bind_lock while re-setting this flag. */
ctxtp->ctrl_op_in_progress = FALSE;
TRACE_VERB("<-%!FUNC! return=0x%x", status);
return status;
}
/*
* Function:
* Description:
* Parameters:
* Returns:
* Author:
* Notes:
*/
NDIS_STATUS Main_ctrl_recv (
PMAIN_CTXT ctxtp,
PMAIN_PACKET_INFO pPacketInfo)
{
NDIS_STATUS status = NDIS_STATUS_SUCCESS;
PIP_HDR ip_hdrp;
PUDP_HDR udp_hdrp;
PUCHAR mac_hdrp;
ULONG soff, doff;
CVY_MAC_ADR tmp_mac;
PIOCTL_REMOTE_HDR rct_hdrp = NULL; // we'll check for null when deleting.
ULONG rct_allocated_length = 0;
ULONG state, host_map;
USHORT checksum, group;
ULONG i;
ULONG tmp_src_addr, tmp_dst_addr;
USHORT tmp_src_port, tmp_dst_port;
UNIV_PRINT_VERB(("Main_ctrl_recv: Processing"));
UNIV_ASSERT((pPacketInfo->Type == TCPIP_IP_SIG) && (pPacketInfo->IP.Protocol == TCPIP_PROTOCOL_UDP));
/* Extract the necessary information from information parsed by Main_recv_frame_parse. */
ip_hdrp = pPacketInfo->IP.pHeader;
udp_hdrp = pPacketInfo->IP.UDP.pHeader;
mac_hdrp = (PUCHAR)pPacketInfo->Ethernet.pHeader;
checksum = Tcpip_chksum(&ctxtp->tcpip, pPacketInfo, TCPIP_PROTOCOL_IP);
if (IP_GET_CHKSUM (ip_hdrp) != checksum)
{
UNIV_PRINT_CRIT(("Main_ctrl_recv: Bad IP checksum %x vs %x\n", IP_GET_CHKSUM (ip_hdrp), checksum))
TRACE_CRIT("%!FUNC! Bad IP checksum 0x%x vs 0x%x", IP_GET_CHKSUM (ip_hdrp), checksum);
goto quit;
}
checksum = Tcpip_chksum(&ctxtp->tcpip, pPacketInfo, TCPIP_PROTOCOL_UDP);
if (UDP_GET_CHKSUM (udp_hdrp) != checksum)
{
UNIV_PRINT_CRIT(("Main_ctrl_recv: Bad UDP checksum %x vs %x\n", UDP_GET_CHKSUM (udp_hdrp), checksum))
TRACE_CRIT("%!FUNC! Bad UDP checksum 0x%x vs 0x%x", UDP_GET_CHKSUM (udp_hdrp), checksum);
goto quit;
}
/* Create an aligned version of the structure. Allocate and populate an IOCTL_REMOTE_HDR
buffer. This guarantees that the information is 8-byte aligned. */
status = NdisAllocateMemoryWithTag(&rct_hdrp, pPacketInfo->IP.UDP.Payload.Length, UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS)
{
TRACE_CRIT("failed to allocate rct_hdrp buffer = 0x%x", status);
rct_hdrp = NULL;
goto quit;
}
NdisMoveMemory(rct_hdrp, pPacketInfo->IP.UDP.Payload.pPayload, pPacketInfo->IP.UDP.Payload.Length);
/* double-check the code */
if (rct_hdrp -> code != IOCTL_REMOTE_CODE)
{
UNIV_PRINT_CRIT(("Main_ctrl_recv: Bad RCT code %x\n", rct_hdrp -> code));
TRACE_CRIT("%!FUNC! Bad RCT code 0x%x", rct_hdrp -> code);
goto quit;
}
/* might want to take appropriate actions for a message from a host
running different version number of software */
if (rct_hdrp -> version != CVY_VERSION_FULL)
{
UNIV_PRINT_VERB(("Main_ctrl_recv: Version mismatch %x vs %x", rct_hdrp -> version, CVY_VERSION_FULL));
TRACE_VERB("%!FUNC! Version mismatch 0x%x vs 0x%x", rct_hdrp -> version, CVY_VERSION_FULL);
}
/* see if this message is destined for this cluster */
if (rct_hdrp -> cluster != ctxtp -> cl_ip_addr)
{
UNIV_PRINT_VERB(("Main_ctrl_recv: Message for cluster %08X rejected on cluster %08X", rct_hdrp -> cluster, ctxtp -> cl_ip_addr));
TRACE_VERB("%!FUNC! Message for cluster 0x%08X rejected on cluster 0x%08X", rct_hdrp -> cluster, ctxtp -> cl_ip_addr);
goto quit;
}
/* Set "access bits" in IOCTL code to use local settings ie. FILE_WRITE_ACCESS */
SET_IOCTL_ACCESS_BITS_TO_LOCAL(rct_hdrp->ioctrl)
/* 64-bit -- ramkrish */
tmp_src_addr = IP_GET_SRC_ADDR_64(ip_hdrp);
tmp_dst_addr = IP_GET_DST_ADDR_64(ip_hdrp);
/* do not trust src addr in header, since winsock does not resolve
multihomed addresses properly */
rct_hdrp -> addr = tmp_src_addr;
/* If remote control is disabled, we drop the requests. */
if (!ctxtp->params.rct_enabled)
goto quit;
/* query load to see if we are the master, etc. */
if (! ctxtp -> convoy_enabled)
state = IOCTL_CVY_STOPPED;
else
state = Load_hosts_query (& ctxtp -> load, FALSE, & host_map);
/* check if this message is destined to us */
//
// The host id in the remote control packet could be
// IOCTL_MASTER_HOST (0) for master host
// IOCTL_ALL_HOSTS (FFFFFFFF) for all hosts
// Host ID, for one host
// Dedicated IP, for one host
// Cluster IP, for all hosts in the cluster
//
if (rct_hdrp -> host == IOCTL_MASTER_HOST)
{
if (state != IOCTL_CVY_MASTER)
{
UNIV_PRINT_VERB(("Main_ctrl_recv: RCT request for MASTER host rejected"));
TRACE_VERB("%!FUNC! RCT request for MASTER host rejected");
goto quit;
}
}
else if (rct_hdrp -> host != IOCTL_ALL_HOSTS)
{
if (rct_hdrp -> host > CVY_MAX_HOSTS)
{
if (! ((ctxtp -> ded_ip_addr != 0 &&
rct_hdrp -> host == ctxtp -> ded_ip_addr) ||
rct_hdrp -> host == ctxtp -> cl_ip_addr))
{
UNIV_PRINT_VERB(("Main_ctrl_recv: RCT request for host IP %x rejected", rct_hdrp -> host));
TRACE_VERB("%!FUNC! RCT request for host IP 0x%x rejected", rct_hdrp -> host);
goto quit;
}
}
else
{
if (! (rct_hdrp -> host == ctxtp -> params . host_priority ||
rct_hdrp -> host == 0))
{
UNIV_PRINT_VERB(("Main_ctrl_recv: RCT request for host ID %d rejected", rct_hdrp -> host));
TRACE_VERB("%!FUNC! RCT request for host ID %d rejected", rct_hdrp -> host);
goto quit;
}
}
}
/* if this is VR remote maintenance password */
if (rct_hdrp -> password == IOCTL_REMOTE_VR_CODE)
{
/* if user disabled this function - drop the packet */
if (ctxtp -> params . rmt_password == 0)
{
UNIV_PRINT_CRIT(("Main_ctrl_recv: VR remote password rejected"));
TRACE_CRIT("%!FUNC! VR remote password rejected");
goto quit;
}
else
{
UNIV_PRINT_VERB(("Main_ctrl_recv: VR remote password accepted"));
TRACE_VERB("%!FUNC! VR remote password accepted");
}
}
//
// This is a new remote control request, with a different source IP
// or newer ID.
// Log an event if the password does not match or if the command is not query
//
if (! (rct_hdrp -> addr == ctxtp -> rct_last_addr &&
rct_hdrp -> id <= ctxtp -> rct_last_id))
{
PWCHAR buf;
PWCHAR ptr;
/* Allocate memory to hold a string buffer containing the source IP
address and UDP port of the initiator of this remote control request. */
status = NdisAllocateMemoryWithTag(&buf, 256 * sizeof(WCHAR), UNIV_POOL_TAG);
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_ctrl_recv: Unable to allocate memory. Exiting..."));
TRACE_CRIT("%!FUNC! Unable to allocate memory");
LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, 256 * sizeof(WCHAR), status);
goto quit;
}
ptr = buf;
//
// Generate string "SourceIp:SourcePort"
//
for (i = 0; i < 4; i ++)
{
ptr = Univ_ulong_to_str (IP_GET_SRC_ADDR (ip_hdrp, i), ptr, 10);
* ptr = L'.';
ptr ++;
}
ptr --;
* ptr = L':';
ptr ++;
ptr = Univ_ulong_to_str (UDP_GET_SRC_PORT (udp_hdrp), ptr, 10);
* ptr = 0;
if (ctxtp -> params . rct_password != 0 &&
rct_hdrp -> password != ctxtp -> params . rct_password)
{
LOG_MSG (MSG_WARN_RCT_HACK, buf);
UNIV_PRINT_CRIT(("Main_ctrl_recv: RCT hack attempt on port %d from %u.%u.%u.%u:%d",
UDP_GET_DST_PORT (udp_hdrp),
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp)));
TRACE_CRIT("%!FUNC! RCT hack attempt on port %d from %u.%u.%u.%u:%u",
UDP_GET_DST_PORT (udp_hdrp),
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp));
}
/* only log error commands and commands that affect cluster state */
else if ((rct_hdrp -> ioctrl != IOCTL_CVY_QUERY) &&
(rct_hdrp -> ioctrl != IOCTL_CVY_QUERY_FILTER) &&
(rct_hdrp -> ioctrl != IOCTL_CVY_QUERY_PORT_STATE))
{
PWSTR cmd;
switch (rct_hdrp -> ioctrl)
{
case IOCTL_CVY_CLUSTER_ON:
cmd = L"START";
break;
case IOCTL_CVY_CLUSTER_OFF:
cmd = L"STOP";
break;
case IOCTL_CVY_CLUSTER_DRAIN:
cmd = L"DRAINSTOP";
break;
case IOCTL_CVY_PORT_ON:
cmd = L"ENABLE";
break;
case IOCTL_CVY_PORT_SET:
cmd = L"ADJUST";
break;
case IOCTL_CVY_PORT_OFF:
cmd = L"DISABLE";
break;
case IOCTL_CVY_PORT_DRAIN:
cmd = L"DRAIN";
break;
case IOCTL_CVY_CLUSTER_SUSPEND:
cmd = L"SUSPEND";
break;
case IOCTL_CVY_CLUSTER_RESUME:
cmd = L"RESUME";
break;
default:
cmd = L"UNKNOWN";
break;
}
LOG_MSGS (MSG_INFO_RCT_RCVD, cmd, buf);
UNIV_PRINT_VERB(("Main_ctrl_recv: RCT command %x port %d from %u.%u.%u.%u:%d",
rct_hdrp -> ioctrl,
UDP_GET_DST_PORT (udp_hdrp),
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp)));
TRACE_VERB("%!FUNC! RCT command 0x%x port %d from %u.%u.%u.%u:%u",
rct_hdrp -> ioctrl,
UDP_GET_DST_PORT (udp_hdrp),
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp));
}
/* Free the memory we used for the initiator string. */
NdisFreeMemory((PUCHAR)buf, 256 * sizeof(WCHAR), 0);
}
ctxtp -> rct_last_addr = rct_hdrp -> addr;
ctxtp -> rct_last_id = rct_hdrp -> id;
/* make sure remote control password matches ours */
if (ctxtp -> params . rct_password != 0 &&
rct_hdrp -> password != ctxtp -> params . rct_password)
{
rct_hdrp -> ctrl . ret_code = IOCTL_CVY_BAD_PASSWORD;
goto send;
}
/* The following operations are not valid if invoked remotely,
so bail out if, somehow, a remote control request arrived
with one of the following IOCTLs in the header. */
if (rct_hdrp->ioctrl == IOCTL_CVY_RELOAD ||
rct_hdrp->ioctrl == IOCTL_CVY_QUERY_PARAMS ||
rct_hdrp->ioctrl == IOCTL_CVY_QUERY_BDA_TEAMING ||
rct_hdrp->ioctrl == IOCTL_CVY_CONNECTION_NOTIFY ||
rct_hdrp->ioctrl == IOCTL_CVY_QUERY_MEMBER_IDENTITY
)
goto quit;
if (rct_hdrp->version == CVY_NT40_VERSION_FULL) {
/* Make sure that the packet length is what we expect, or we may fault. */
if (pPacketInfo->IP.UDP.Length < NLB_NT40_RCTL_PACKET_LEN) {
UNIV_PRINT_CRIT(("Version 0x%08x remote control packet not expected length: got %d bytes, expected %d bytes\n", CVY_NT40_VERSION_FULL, pPacketInfo->IP.UDP.Length, NLB_NT40_RCTL_PACKET_LEN));
TRACE_CRIT("%!FUNC! Version %ls remote control packet not expected length: got %d bytes, expected %d bytes", CVY_NT40_VERSION, pPacketInfo->IP.UDP.Length, NLB_NT40_RCTL_PACKET_LEN);
goto quit;
}
/* If this remote control packet came from an NT 4.0 host, check our current effective version
and drop it if we are operating in a Whistler-specific mode (virtual clusters, BDA, etc.). */
if (ctxtp->params.effective_ver == CVY_VERSION_FULL &&
(rct_hdrp->ioctrl == IOCTL_CVY_PORT_ON ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_OFF ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_SET ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_DRAIN))
goto quit;
/* Otherwise, perform the operation. NT 4.0 remote control packets do not contain options buffers,
so all three options pointers are NULL and Main_ctrl must handle the fact that they can be NULL. */
else
status = Main_ctrl(ctxtp, rct_hdrp->ioctrl, &(rct_hdrp->ctrl), NULL, NULL, NULL);
} else if (rct_hdrp->version == CVY_WIN2K_VERSION_FULL) {
/* Make sure that the packet length is what we expect, or we may fault. */
if (pPacketInfo->IP.UDP.Length < NLB_WIN2K_RCTL_PACKET_LEN) {
UNIV_PRINT_CRIT(("Version 0x%08x remote control packet not expected length: got %d bytes, expected %d bytes\n", CVY_WIN2K_VERSION_FULL, pPacketInfo->IP.UDP.Length, NLB_WIN2K_RCTL_PACKET_LEN));
TRACE_CRIT("%!FUNC! Version %ls remote control packet not expected length: got %d bytes, expected %d bytes", CVY_WIN2K_VERSION, pPacketInfo->IP.UDP.Length, NLB_WIN2K_RCTL_PACKET_LEN);
goto quit;
}
/* If this remote control packet came from an Win2k host, check our current effective version
and drop it if we are operating in a Whistler-specific mode (virtual clusters, BDA, etc.). */
if (ctxtp->params.effective_ver == CVY_VERSION_FULL &&
(rct_hdrp->ioctrl == IOCTL_CVY_PORT_ON ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_OFF ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_SET ||
rct_hdrp->ioctrl == IOCTL_CVY_PORT_DRAIN))
goto quit;
/* Otherwise, perform the operation. Win2K remote control packets do not contain options buffers,
so all three options pointers are NULL and Main_ctrl must handle the fact that they can be NULL. */
else
status = Main_ctrl(ctxtp, rct_hdrp->ioctrl, &(rct_hdrp->ctrl), NULL, NULL, NULL);
} else if (rct_hdrp->version == CVY_VERSION_FULL) {
/* Make sure that the packet length is what we expect, or we may fault. */
if (pPacketInfo->IP.UDP.Length < NLB_WINXP_RCTL_PACKET_LEN) {
UNIV_PRINT_CRIT(("Version 0x%08x remote control packet not expected length: got %d bytes, expected %d bytes\n", CVY_VERSION_FULL, pPacketInfo->IP.UDP.Length, NLB_WINXP_RCTL_PACKET_LEN));
TRACE_CRIT("%!FUNC! Version %ls remote control packet not expected length: got %d bytes, expected %d bytes", CVY_VERSION, pPacketInfo->IP.UDP.Length, NLB_WINXP_RCTL_PACKET_LEN);
goto quit;
}
/* Perform the requested operation. Local options is NULL - Main_ctrl will handle this. */
status = Main_ctrl(ctxtp, rct_hdrp->ioctrl, &(rct_hdrp->ctrl), &(rct_hdrp->options.common), NULL, &(rct_hdrp->options));
} else {
goto quit;
}
/* if did not succeed - just drop the packet - client will timeout and
resend the request */
if (status != NDIS_STATUS_SUCCESS)
goto quit;
send:
rct_hdrp -> version = CVY_VERSION_FULL;
rct_hdrp -> host = ctxtp -> params . host_priority;
rct_hdrp -> addr = ctxtp -> ded_ip_addr;
/* flip source and destination MAC, IP addresses and UDP ports to prepare
for sending this message back */
soff = CVY_MAC_SRC_OFF (ctxtp -> medium);
doff = CVY_MAC_DST_OFF (ctxtp -> medium);
/* V2.0.6 recoded for clarity */
if (ctxtp -> params . mcast_support)
{
if (CVY_MAC_ADDR_BCAST (ctxtp -> medium, mac_hdrp + doff))
CVY_MAC_ADDR_COPY (ctxtp -> medium, & tmp_mac, & ctxtp -> ded_mac_addr);
else
CVY_MAC_ADDR_COPY (ctxtp -> medium, & tmp_mac, mac_hdrp + doff);
CVY_MAC_ADDR_COPY (ctxtp -> medium, mac_hdrp + doff, mac_hdrp + soff);
CVY_MAC_ADDR_COPY (ctxtp -> medium, mac_hdrp + soff, & tmp_mac);
}
else
{
if (! CVY_MAC_ADDR_BCAST (ctxtp -> medium, mac_hdrp + doff))
{
CVY_MAC_ADDR_COPY (ctxtp -> medium, & tmp_mac, mac_hdrp + doff);
CVY_MAC_ADDR_COPY (ctxtp -> medium, mac_hdrp + doff, mac_hdrp + soff);
}
else
CVY_MAC_ADDR_COPY (ctxtp -> medium, & tmp_mac, & ctxtp -> ded_mac_addr);
/* V2.0.6 spoof source mac to prevent switches from getting confused */
if (ctxtp -> params . mask_src_mac &&
CVY_MAC_ADDR_COMP (ctxtp -> medium, & tmp_mac, & ctxtp -> cl_mac_addr))
{
ULONG byte [4];
CVY_MAC_ADDR_LAA_SET (ctxtp -> medium, mac_hdrp + soff);
* ((PUCHAR) (mac_hdrp + soff + 1)) = (UCHAR) ctxtp -> params . host_priority;
IP_GET_ADDR(ctxtp->cl_ip_addr, &byte[0], &byte[1], &byte[2], &byte[3]);
* ((PUCHAR) (mac_hdrp + soff + 2)) = (UCHAR) byte[0];
* ((PUCHAR) (mac_hdrp + soff + 3)) = (UCHAR) byte[1];
* ((PUCHAR) (mac_hdrp + soff + 4)) = (UCHAR) byte[2];
* ((PUCHAR) (mac_hdrp + soff + 5)) = (UCHAR) byte[3];
}
else
CVY_MAC_ADDR_COPY (ctxtp -> medium, mac_hdrp + soff, & tmp_mac);
}
/* Set "access bits" in IOCTL code to use remote (control) settings ie. FILE_ANY_ACCESS */
SET_IOCTL_ACCESS_BITS_TO_REMOTE(rct_hdrp->ioctrl)
/* Copy back the datagram contents from our aligned version. We must do this BEFORE
we compute the checksums. */
NdisMoveMemory(pPacketInfo->IP.UDP.Payload.pPayload, rct_hdrp, pPacketInfo->IP.UDP.Payload.Length);
if (tmp_dst_addr == TCPIP_BCAST_ADDR)
{
tmp_dst_addr = ctxtp -> cl_ip_addr;
if (ctxtp -> params . mcast_support)
IP_SET_DST_ADDR_64 (ip_hdrp, tmp_src_addr);
}
else
IP_SET_DST_ADDR_64 (ip_hdrp, tmp_src_addr);
IP_SET_SRC_ADDR_64 (ip_hdrp, tmp_dst_addr);
checksum = Tcpip_chksum(&ctxtp->tcpip, pPacketInfo, TCPIP_PROTOCOL_IP);
IP_SET_CHKSUM (ip_hdrp, checksum);
tmp_src_port = (USHORT) UDP_GET_SRC_PORT (udp_hdrp);
tmp_dst_port = (USHORT) UDP_GET_DST_PORT (udp_hdrp);
UDP_SET_SRC_PORT_64 (udp_hdrp, tmp_dst_port);
UDP_SET_DST_PORT_64 (udp_hdrp, tmp_src_port);
checksum = Tcpip_chksum(&ctxtp->tcpip, pPacketInfo, TCPIP_PROTOCOL_UDP);
UDP_SET_CHKSUM (udp_hdrp, checksum);
#if defined(TRACE_RCT)
DbgPrint ("(RCT) sending reply to %u.%u.%u.%u:%d [%02x-%02x-%02x-%02x-%02x-%02x]\n",
IP_GET_DST_ADDR (ip_hdrp, 0),
IP_GET_DST_ADDR (ip_hdrp, 1),
IP_GET_DST_ADDR (ip_hdrp, 2),
IP_GET_DST_ADDR (ip_hdrp, 3),
UDP_GET_DST_PORT (udp_hdrp),
* (mac_hdrp + doff + 0),
* (mac_hdrp + doff + 1),
* (mac_hdrp + doff + 2),
* (mac_hdrp + doff + 3),
* (mac_hdrp + doff + 4),
* (mac_hdrp + doff + 5));
DbgPrint (" from %u.%u.%u.%u:%d [%02x-%02x-%02x-%02x-%02x-%02x]\n",
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp),
* (mac_hdrp + soff + 0),
* (mac_hdrp + soff + 1),
* (mac_hdrp + soff + 2),
* (mac_hdrp + soff + 3),
* (mac_hdrp + soff + 4),
* (mac_hdrp + soff + 5));
TRACE_INFO("(RCT) sending reply to %u.%u.%u.%u:%d [0x%02x-0x%02x-0x%02x-0x%02x-0x%02x-0x%02x]",
IP_GET_DST_ADDR (ip_hdrp, 0),
IP_GET_DST_ADDR (ip_hdrp, 1),
IP_GET_DST_ADDR (ip_hdrp, 2),
IP_GET_DST_ADDR (ip_hdrp, 3),
UDP_GET_DST_PORT (udp_hdrp),
* (mac_hdrp + doff + 0),
* (mac_hdrp + doff + 1),
* (mac_hdrp + doff + 2),
* (mac_hdrp + doff + 3),
* (mac_hdrp + doff + 4),
* (mac_hdrp + doff + 5));
TRACE_INFO(" from %u.%u.%u.%u:%d [0x%02x-0x%02x-0x%02x-0x%02x-0x%02x-0x%02x]",
IP_GET_SRC_ADDR (ip_hdrp, 0),
IP_GET_SRC_ADDR (ip_hdrp, 1),
IP_GET_SRC_ADDR (ip_hdrp, 2),
IP_GET_SRC_ADDR (ip_hdrp, 3),
UDP_GET_SRC_PORT (udp_hdrp),
* (mac_hdrp + soff + 0),
* (mac_hdrp + soff + 1),
* (mac_hdrp + soff + 2),
* (mac_hdrp + soff + 3),
* (mac_hdrp + soff + 4),
* (mac_hdrp + soff + 5));
#endif
if (rct_hdrp != NULL)
{
NdisFreeMemory(rct_hdrp, pPacketInfo->IP.UDP.Payload.Length, 0);
}
return NDIS_STATUS_SUCCESS;
quit:
if (rct_hdrp != NULL)
{
NdisFreeMemory(rct_hdrp, pPacketInfo->IP.UDP.Payload.Length, 0);
}
return NDIS_STATUS_FAILURE;
} /* end Main_ctrl_recv */
INT Main_adapter_alloc (PNDIS_STRING device_name)
{
NDIS_STATUS status;
INT i;
/* Called from Prot_bind at PASSIVE_LEVEL - %ls is OK. */
UNIV_PRINT_VERB(("Main_adapter_alloc: Called for %ls", device_name->Buffer));
NdisAcquireSpinLock(&univ_bind_lock);
/* If we have already allocated the maximum allowed, return failure. */
if (univ_adapters_count == CVY_MAX_ADAPTERS) {
NdisReleaseSpinLock(&univ_bind_lock);
TRACE_CRIT("%!FUNC! max adapters=%u allocated already", univ_adapters_count);
return MAIN_ADAPTER_NOT_FOUND;
}
NdisReleaseSpinLock(&univ_bind_lock);
/* Loop through all adapter structures in the global array, looking for
one that is free; mark it as used and increment the adapter count. */
for (i = 0 ; i < CVY_MAX_ADAPTERS; i++) {
NdisAcquireSpinLock(&univ_bind_lock);
/* If this one is unused, grab it. */
if (univ_adapters[i].used == FALSE) {
/* Mark it as in-use. */
univ_adapters[i].used = TRUE;
/* Increment the global count of adapters in-use. */
univ_adapters_count++;
NdisReleaseSpinLock(&univ_bind_lock);
break;
}
NdisReleaseSpinLock(&univ_bind_lock);
}
/* If we looped through the entire array and found jack, return failure. */
if (i >= CVY_MAX_ADAPTERS) return MAIN_ADAPTER_NOT_FOUND;
NdisAcquireSpinLock(&univ_bind_lock);
/* Reset the binding flags, which will be filled in by the binding code. */
univ_adapters[i].bound = FALSE;
univ_adapters[i].inited = FALSE;
univ_adapters[i].announced = FALSE;
#if defined (NLB_TCP_NOTIFICATION)
/* Invalidate the IP interface index and idle the operation in progress. */
univ_adapters[i].if_index = 0;
univ_adapters[i].if_index_operation = IF_INDEX_OPERATION_NONE;
#endif
NdisReleaseSpinLock(&univ_bind_lock);
/* Allocate room for the device name. */
status = NdisAllocateMemoryWithTag(&univ_adapters[i].device_name, device_name->MaximumLength, UNIV_POOL_TAG);
/* If this allocation failed, call Main_adapter_put to release the adapter structure. */
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_adapter_alloc: Error allocating memory %d %x", device_name->MaximumLength * sizeof(WCHAR), status));
TRACE_CRIT("%!FUNC! Error allocating memory %d 0x%x", device_name->MaximumLength * sizeof(WCHAR), status);
__LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, device_name->MaximumLength * sizeof(WCHAR), status);
//
// 22 July 2002 - chrisdar
// Added this event because it is needed for reporting consistency with other memory allocation failures in this code path (see below).
// Left it commented out so as not to cause a code behavior change per SHouse request.
//
// __LOG_MSG(MSG_ERROR_BIND_FAIL, MSG_NONE);
Main_adapter_put(&univ_adapters[i]);
return MAIN_ADAPTER_NOT_FOUND;
}
/* Allocate the context strucutre. */
status = NdisAllocateMemoryWithTag(&univ_adapters[i].ctxtp, sizeof(MAIN_CTXT), UNIV_POOL_TAG);
/* If this allocation failed, call Main_adapter_put to release the adapter structure. */
if (status != NDIS_STATUS_SUCCESS) {
UNIV_PRINT_CRIT(("Main_adapter_alloc: Error allocating memory %d %x", sizeof(MAIN_CTXT), status));
TRACE_CRIT("%!FUNC! Error allocating memory %d 0x%x", sizeof(MAIN_CTXT), status);
__LOG_MSG2(MSG_ERROR_MEMORY, MSG_NONE, sizeof(MAIN_CTXT), status);
__LOG_MSG(MSG_ERROR_BIND_FAIL, MSG_NONE);
Main_adapter_put(&univ_adapters[i]);
return MAIN_ADAPTER_NOT_FOUND;
}
return i;
}
INT Main_adapter_get (PWSTR device_name)
{
INT i;
if (device_name == NULL)
{
TRACE_CRIT("%!FUNC! no device name provided");
return MAIN_ADAPTER_NOT_FOUND;
}
TRACE_INFO("%!FUNC! device_name=%ls", device_name);
/* Loop through all adapters in use, looking for the device specified. If we find it,
return the index of that adapter in the global array; otherwise, return NOT_FOUND. */
for (i = 0 ; i < CVY_MAX_ADAPTERS; i++) {
if (univ_adapters[i].used && univ_adapters[i].bound && univ_adapters[i].inited && univ_adapters[i].device_name_len) {
/* This function compares two unicode strings, insensitive to case. */
if (Univ_equal_unicode_string(univ_adapters[i].device_name, device_name, wcslen(univ_adapters[i].device_name)))
{
return i;
}
}
}
return MAIN_ADAPTER_NOT_FOUND;
}
INT Main_adapter_put (PMAIN_ADAPTER adapterp)
{
INT adapter_id = -1;
INT i;
UNIV_ASSERT(adapterp -> code == MAIN_ADAPTER_CODE);
/* Loop through all adapter structures, looking for the specified pointer.
If we find that the address specified matches the address of one of the
global adapter structures (it should), save the adapter index in the array. */
for (i = 0 ; i < CVY_MAX_ADAPTERS; i++) {
if (adapterp == (univ_adapters + i)) {
adapter_id = i;
UNIV_PRINT_VERB(("Main_adapter_put: For adapter id 0x%x\n", adapter_id));
TRACE_VERB("%!FUNC! For adapter id 0x%x\n", adapter_id);
break;
}
}
UNIV_ASSERT(adapter_id != -1);
if (!adapterp->used || adapter_id == -1) return adapter_id;
UNIV_ASSERT(univ_adapters_count > 0);
NdisAcquireSpinLock(&univ_bind_lock);
/* Reset the binding state flags. */
univ_adapters[adapter_id].bound = FALSE;
univ_adapters[adapter_id].inited = FALSE;
univ_adapters[adapter_id].announced = FALSE;
univ_adapters[adapter_id].used = FALSE;
#if defined (NLB_TCP_NOTIFICATION)
/* Invalidate the IP interface index and idle the operation in progress. */
univ_adapters[adapter_id].if_index = 0;
univ_adapters[adapter_id].if_index_operation = IF_INDEX_OPERATION_NONE;
#endif
/* If the context pointer has been allocated, free that memory - this may not
be non-NULL, i.e., in the case that allocation failed, Main_adapter_alloc()
will call this function to clean up, but ctxtp will be (can be) NULL. */
if (univ_adapters[adapter_id].ctxtp != NULL)
NdisFreeMemory(univ_adapters[adapter_id].ctxtp, sizeof (MAIN_CTXT), 0);
/* NULLL the context pointer out. */
univ_adapters[adapter_id].ctxtp = NULL;
/* If the device name pointer has been allocated, free that memory - this may not
be non-NULL, i.e., in the case that allocation failed, Main_adapter_alloc()
will call this function to clean up, but this pointer will be (can be) NULL. */
if (univ_adapters[adapter_id].device_name != NULL)
NdisFreeMemory(univ_adapters[adapter_id].device_name, univ_adapters[adapter_id].device_name_len, 0);
/* Reset the device name pointer and the length counter. */
univ_adapters[adapter_id].device_name_len = 0;
univ_adapters[adapter_id].device_name = NULL;
/* Decrement the number of "outstanding" adapters - i.e. adapter structures in use. */
univ_adapters_count--;
NdisReleaseSpinLock(&univ_bind_lock);
return adapter_id;
}
INT Main_adapter_selfbind (PWSTR device_name) {
INT i;
/* Called from Prot_bind at PASSIVE_LEVEL - %ls is OK. */
UNIV_PRINT_VERB(("Main_adapter_selfbind: %ls", device_name));
if (device_name == NULL)
{
TRACE_CRIT("%!FUNC! no device name provided");
return MAIN_ADAPTER_NOT_FOUND;
}
TRACE_INFO("%!FUNC! device_name=%ls", device_name);
/* Loop through all adapter structures in use, looking for one already
bound to the device specified. If we find it, return the index of
that adapter in the array; if not, return NOT_FOUND. */
for (i = 0 ; i < CVY_MAX_ADAPTERS; i++) {
if (univ_adapters[i].used && univ_adapters[i].bound && univ_adapters[i].inited) {
/* Called from Prot_bind at PASSIVE_LEVEL - %ls is OK. */
UNIV_PRINT_VERB(("Main_adapter_selfbind: Comparing %ls %ls", univ_adapters[i].ctxtp->virtual_nic_name, device_name));
/* This function compares two unicode strings, insensitive to case. */
if (Univ_equal_unicode_string(univ_adapters[i].ctxtp->virtual_nic_name, device_name, wcslen(univ_adapters[i].ctxtp->virtual_nic_name)))
{
return i;
}
}
}
return MAIN_ADAPTER_NOT_FOUND;
}