1415 lines
35 KiB
C
1415 lines
35 KiB
C
//============================================================================
|
|
// Copyright (c) 1995, Microsoft Corporation
|
|
//
|
|
// File: work.c
|
|
//
|
|
// History:
|
|
// Abolade Gbadegesin August 31, 1995 Created
|
|
//
|
|
// Worker function implementation
|
|
//============================================================================
|
|
|
|
|
|
#include "pchbootp.h"
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: CallbackFunctionNetworkEvents
|
|
//
|
|
// This function runs in the context of the ntdll wait thread. Using
|
|
// QueueBootpWorker ensures that the bootp dll is running
|
|
//----------------------------------------------------------------------------
|
|
VOID
|
|
CallbackFunctionNetworkEvents(
|
|
PVOID pvContext,
|
|
BOOLEAN NotUsed
|
|
) {
|
|
|
|
HANDLE WaitHandle;
|
|
|
|
if (!ENTER_BOOTP_API()) { return; }
|
|
|
|
|
|
//
|
|
// set the handle to NULL, so that Unregister wont be called
|
|
//
|
|
|
|
WaitHandle = InterlockedExchangePointer(&ig.IG_InputEventHandle, NULL);
|
|
|
|
if (WaitHandle) {
|
|
UnregisterWaitEx( WaitHandle, NULL ) ;
|
|
}
|
|
|
|
|
|
QueueBootpWorker(WorkerFunctionNetworkEvents, pvContext);
|
|
|
|
|
|
LEAVE_BOOTP_API();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: WorkerFunctionNetworkEvents
|
|
//
|
|
// This function enumerates the input events on each interface and processes
|
|
// any incoming input packets. Queued by CallbackFunctionNetworkEvents
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
WorkerFunctionNetworkEvents(
|
|
PVOID pvContextNotused
|
|
) {
|
|
|
|
DWORD i, dwErr;
|
|
PIF_TABLE pTable;
|
|
PIPBOOTP_IF_CONFIG pic;
|
|
PIPBOOTP_IF_BINDING pib;
|
|
PIPBOOTP_IP_ADDRESS paddr;
|
|
PIF_TABLE_ENTRY pite;
|
|
PLIST_ENTRY ple, phead;
|
|
WSANETWORKEVENTS wsane;
|
|
|
|
|
|
if (!ENTER_BOOTP_WORKER()) { return; }
|
|
|
|
|
|
pTable = ig.IG_IfTable;
|
|
|
|
ACQUIRE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
//
|
|
// go through the list of active interfaces
|
|
// processing sockets which are read-ready
|
|
//
|
|
|
|
phead = &pTable->IT_ListByAddress;
|
|
|
|
for (ple = phead->Flink; ple != phead; ple = ple->Flink) {
|
|
|
|
pite = CONTAINING_RECORD(ple, IF_TABLE_ENTRY, ITE_LinkByAddress);
|
|
|
|
pic = pite->ITE_Config;
|
|
pib = pite->ITE_Binding;
|
|
paddr = IPBOOTP_IF_ADDRESS_TABLE(pib);
|
|
|
|
for (i = 0; i < pib->IB_AddrCount; i++, paddr++) {
|
|
|
|
if (pite->ITE_Sockets[i] == INVALID_SOCKET) { continue; }
|
|
|
|
//
|
|
// enumerate network events to see whether
|
|
// any packets have arrived on this interface
|
|
//
|
|
|
|
dwErr = WSAEnumNetworkEvents(pite->ITE_Sockets[i], NULL, &wsane);
|
|
if (dwErr != NO_ERROR) {
|
|
|
|
LPSTR lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE3(
|
|
RECEIVE, "error %d checking for input on interface %d (%s)",
|
|
dwErr, pite->ITE_Index, lpszAddr
|
|
);
|
|
LOGWARN1(ENUM_NETWORK_EVENTS_FAILED, lpszAddr, dwErr);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// see if the input bit is set
|
|
//
|
|
|
|
if (!(wsane.lNetworkEvents & FD_READ)) { continue; }
|
|
|
|
|
|
//
|
|
// the input flag is set, now see if there was an error
|
|
//
|
|
|
|
if (wsane.iErrorCode[FD_READ_BIT] != NO_ERROR) {
|
|
|
|
LPSTR lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE3(
|
|
RECEIVE, "error %d in input record for interface %d (%s)",
|
|
wsane.iErrorCode[FD_READ_BIT], pite->ITE_Index, lpszAddr
|
|
);
|
|
LOGWARN1(INPUT_RECORD_ERROR, lpszAddr, dwErr);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// there is no error, so process the socket
|
|
//
|
|
|
|
ProcessSocket(pite, i, pTable);
|
|
}
|
|
}
|
|
|
|
RELEASE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
|
|
|
|
//
|
|
// register the InputEvent with the NtdllWait Thread again (only if the
|
|
// dll is not stopping). I use this model of registering the event with
|
|
// ntdll every time, to prevent the worker function from being called for
|
|
// every packet received (when packets are received at the same time).
|
|
//
|
|
|
|
if (ig.IG_Status != IPBOOTP_STATUS_STOPPING) {
|
|
|
|
|
|
if (! RegisterWaitForSingleObject(
|
|
&ig.IG_InputEventHandle,
|
|
ig.IG_InputEvent,
|
|
CallbackFunctionNetworkEvents,
|
|
NULL, //null context
|
|
INFINITE, //no timeout
|
|
(WT_EXECUTEINWAITTHREAD|WT_EXECUTEONLYONCE)
|
|
)) {
|
|
|
|
dwErr = GetLastError();
|
|
TRACE1(
|
|
START, "error %d returned by RegisterWaitForSingleObjectEx",
|
|
dwErr
|
|
);
|
|
LOGERR0(REGISTER_WAIT_FAILED, dwErr);
|
|
}
|
|
}
|
|
|
|
|
|
LEAVE_BOOTP_WORKER();
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ProcessSocket
|
|
//
|
|
// This function processes a packet on an interface, queueing
|
|
// the packet for processing by a worker function after doing some
|
|
// basic validation.
|
|
//----------------------------------------------------------------------------
|
|
|
|
DWORD
|
|
ProcessSocket(
|
|
PIF_TABLE_ENTRY pite,
|
|
DWORD dwAddrIndex,
|
|
PIF_TABLE pTable
|
|
) {
|
|
|
|
BOOL bFreePacket;
|
|
WORKERFUNCTION pwf;
|
|
PINPUT_CONTEXT pwc;
|
|
PIPBOOTP_IF_STATS pis;
|
|
PIPBOOTP_IF_CONFIG pic;
|
|
PIPBOOTP_IP_ADDRESS paddr;
|
|
PIPBOOTP_PACKET pibp;
|
|
PLIST_ENTRY ple, phead;
|
|
DWORD dwErr, dwInputSource;
|
|
SOCKADDR_IN sinInputSource;
|
|
PIPBOOTP_GLOBAL_CONFIG pigc;
|
|
INT iInputLength, iAddrLength;
|
|
PBYTE pInputPacket;
|
|
|
|
pigc = ig.IG_Config;
|
|
|
|
|
|
|
|
pis = &pite->ITE_Stats;
|
|
paddr = IPBOOTP_IF_ADDRESS_TABLE(pite->ITE_Binding) + dwAddrIndex;
|
|
|
|
|
|
//
|
|
// the descriptor for this interface is set,
|
|
// so allocate space for the packet
|
|
//
|
|
|
|
pwc = BOOTP_ALLOC(sizeof(INPUT_CONTEXT));
|
|
if (pwc == NULL) {
|
|
|
|
dwErr = GetLastError();
|
|
TRACE2(
|
|
RECEIVE, "error %d allocating %d bytes for incoming packet",
|
|
dwErr, sizeof(INPUT_CONTEXT)
|
|
);
|
|
LOGERR0(HEAP_ALLOC_FAILED, dwErr);
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
|
|
pInputPacket = pwc->IC_InputPacket;
|
|
|
|
dwErr = NO_ERROR;
|
|
bFreePacket = TRUE;
|
|
|
|
do {
|
|
|
|
CHAR szSource[20];
|
|
|
|
|
|
//
|
|
// receive the packet
|
|
//
|
|
|
|
iAddrLength = sizeof(SOCKADDR_IN);
|
|
|
|
iInputLength = recvfrom(
|
|
pite->ITE_Sockets[dwAddrIndex], pInputPacket,
|
|
MAX_PACKET_SIZE, 0, (PSOCKADDR)&sinInputSource,
|
|
&iAddrLength
|
|
);
|
|
|
|
if (iInputLength == 0 || iInputLength == SOCKET_ERROR) {
|
|
|
|
LPSTR lpszAddr;
|
|
|
|
dwErr = WSAGetLastError();
|
|
lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE3(
|
|
RECEIVE, "error %d receiving on interface %d (%s)",
|
|
dwErr, pite->ITE_Index, lpszAddr
|
|
);
|
|
LOGERR1(RECVFROM_FAILED, lpszAddr, dwErr);
|
|
|
|
InterlockedIncrement(&pis->IS_ReceiveFailures);
|
|
break;
|
|
}
|
|
|
|
|
|
dwInputSource = sinInputSource.sin_addr.s_addr;
|
|
|
|
|
|
//
|
|
// filter out packets we sent ourselves
|
|
//
|
|
|
|
if (GetIfByAddress(pTable, dwInputSource, NULL)) {
|
|
break;
|
|
}
|
|
|
|
{
|
|
PCHAR pStr1, pStr2;
|
|
pStr1 = INET_NTOA(dwInputSource);
|
|
if (pStr1)
|
|
lstrcpy(szSource, pStr1);
|
|
|
|
pStr2 = INET_NTOA(paddr->IA_Address);
|
|
|
|
if (pStr1 && pStr2) {
|
|
|
|
TRACE4(
|
|
RECEIVE, "received %d-byte packet from %s on interface %d (%s)",
|
|
iInputLength, szSource, pite->ITE_Index,
|
|
pStr2
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// cast packet as a BOOTP message
|
|
//
|
|
|
|
pibp = (PIPBOOTP_PACKET)pInputPacket;
|
|
|
|
|
|
|
|
//
|
|
// consistency check 1: length of packet must exceed the length
|
|
// of the BOOTP header
|
|
//
|
|
|
|
if (iInputLength < sizeof(IPBOOTP_PACKET)) {
|
|
|
|
LPSTR lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE3(
|
|
RECEIVE,
|
|
"minimum BOOTP data is %d bytes, dropping %d byte packet from %s",
|
|
sizeof(IPBOOTP_PACKET), iInputLength, szSource
|
|
);
|
|
LOGWARN2(PACKET_TOO_SMALL, lpszAddr, szSource, 0);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// consistency check 2: op field must be either BOOTP_REQUEST
|
|
// or BOOTP_REPLY
|
|
//
|
|
if (pibp->IP_Operation != IPBOOTP_OPERATION_REQUEST &&
|
|
pibp->IP_Operation != IPBOOTP_OPERATION_REPLY) {
|
|
|
|
LPSTR lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE2(
|
|
RECEIVE,
|
|
"dropping packet from %s due to unknown operation field %d",
|
|
szSource, pibp->IP_Operation
|
|
);
|
|
LOGWARN2(PACKET_OPCODE_INVALID, lpszAddr, szSource, 0);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// update statistics on incoming packets
|
|
//
|
|
|
|
switch (pibp->IP_Operation) {
|
|
|
|
case IPBOOTP_OPERATION_REQUEST:
|
|
InterlockedIncrement(&pis->IS_RequestsReceived);
|
|
break;
|
|
|
|
case IPBOOTP_OPERATION_REPLY:
|
|
InterlockedIncrement(&pis->IS_RepliesReceived);
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// finish initializing the work context
|
|
//
|
|
|
|
pwc->IC_InterfaceIndex = pite->ITE_Index;
|
|
pwc->IC_AddrIndex = dwAddrIndex;
|
|
pwc->IC_InputSource = sinInputSource;
|
|
pwc->IC_InputLength = iInputLength;
|
|
|
|
|
|
//
|
|
// place the packet on the receive queue
|
|
//
|
|
|
|
ACQUIRE_READ_LOCK(&ig.IG_RWL);
|
|
ACQUIRE_LIST_LOCK(ig.IG_RecvQueue);
|
|
|
|
dwErr = EnqueueRecvEntry(
|
|
ig.IG_RecvQueue, (DWORD)pibp->IP_Operation, (PBYTE)pwc
|
|
);
|
|
|
|
RELEASE_LIST_LOCK(ig.IG_RecvQueue);
|
|
RELEASE_READ_LOCK(&ig.IG_RWL);
|
|
|
|
if (dwErr != NO_ERROR) {
|
|
|
|
LPSTR lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
TRACE4(
|
|
RECEIVE, "error %d queueing packet from %s on interface %d (%s)",
|
|
dwErr, szSource, pite->ITE_Index, lpszAddr
|
|
);
|
|
LOGERR2(QUEUE_PACKET_FAILED, lpszAddr, szSource, dwErr);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// queue the function to handle the packet
|
|
//
|
|
|
|
dwErr = QueueBootpWorker(WorkerFunctionProcessInput, NULL);
|
|
|
|
if (dwErr != NO_ERROR) {
|
|
|
|
PLIST_ENTRY phead;
|
|
|
|
TRACE2(
|
|
RECEIVE, "error %d queueing packet from %s for processing",
|
|
dwErr, szSource
|
|
);
|
|
LOGERR0(QUEUE_WORKER_FAILED, dwErr);
|
|
|
|
ACQUIRE_LIST_LOCK(ig.IG_RecvQueue);
|
|
|
|
phead = &ig.IG_RecvQueue->LL_Head;
|
|
RemoveTailList(phead);
|
|
ig.IG_RecvQueueSize -= sizeof(RECV_QUEUE_ENTRY);
|
|
|
|
RELEASE_LIST_LOCK(ig.IG_RecvQueue);
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// all went well, so we let the input processor free the packet
|
|
//
|
|
|
|
bFreePacket = FALSE;
|
|
|
|
} while(FALSE);
|
|
|
|
|
|
if (bFreePacket) { BOOTP_FREE(pwc); }
|
|
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: WorkerFunctionProcessInput
|
|
//
|
|
// This function processes an incoming packet.
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
WorkerFunctionProcessInput(
|
|
PVOID pContext
|
|
) {
|
|
|
|
PINPUT_CONTEXT pwc;
|
|
DWORD dwErr, dwCommand;
|
|
|
|
if (!ENTER_BOOTP_WORKER()) { return; }
|
|
|
|
TRACE0(ENTER, "entering WorkerFunctionProcessInput");
|
|
|
|
|
|
do {
|
|
|
|
|
|
ACQUIRE_LIST_LOCK(ig.IG_RecvQueue);
|
|
dwErr = DequeueRecvEntry(ig.IG_RecvQueue, &dwCommand, (PBYTE *)&pwc);
|
|
RELEASE_LIST_LOCK(ig.IG_RecvQueue);
|
|
|
|
if (dwErr != NO_ERROR) {
|
|
TRACE1(
|
|
RECEIVE, "error %d dequeueing packet from receive queue", dwErr
|
|
);
|
|
break;
|
|
}
|
|
|
|
|
|
switch (dwCommand) {
|
|
|
|
case IPBOOTP_OPERATION_REQUEST:
|
|
ProcessRequest(pwc);
|
|
break;
|
|
|
|
case IPBOOTP_OPERATION_REPLY:
|
|
ProcessReply(pwc);
|
|
break;
|
|
}
|
|
|
|
|
|
} while(FALSE);
|
|
|
|
|
|
TRACE0(LEAVE, "leaving WorkerFunctionProcessInput");
|
|
|
|
LEAVE_BOOTP_WORKER();
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ProcessRequest
|
|
//
|
|
// This function handles the processing of BOOT_REQUEST messages
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
ProcessRequest(
|
|
PVOID pContext
|
|
) {
|
|
|
|
INT iErr;
|
|
PIF_TABLE pTable;
|
|
PINPUT_CONTEXT pwc;
|
|
SOCKADDR_IN sinsrv;
|
|
PIPBOOTP_PACKET pibp;
|
|
PIF_TABLE_ENTRY pite;
|
|
PIPBOOTP_IF_STATS pis;
|
|
PIPBOOTP_IF_CONFIG pic;
|
|
PIPBOOTP_IP_ADDRESS paddr;
|
|
PIPBOOTP_GLOBAL_CONFIG pigc;
|
|
DWORD dwErr, dwIndex, dwDhcpInformServer;
|
|
PDWORD pdwAddr, pdwEnd;
|
|
PDHCP_PACKET pdp;
|
|
|
|
TRACE0(ENTER, "entering ProcessRequest");
|
|
|
|
|
|
pwc = (PINPUT_CONTEXT)pContext;
|
|
|
|
pTable = ig.IG_IfTable;
|
|
|
|
ACQUIRE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
do { // error breakout loop
|
|
|
|
|
|
//
|
|
// find the interface on which the input arrived
|
|
//
|
|
|
|
dwIndex = pwc->IC_InterfaceIndex;
|
|
pite = GetIfByIndex(pTable, dwIndex);
|
|
if (pite == NULL) {
|
|
|
|
TRACE1(
|
|
REQUEST, "processing request: interface %d not found", dwIndex
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
pis = &pite->ITE_Stats;
|
|
pic = pite->ITE_Config;
|
|
|
|
//
|
|
// Check if interface still bound to an IP address
|
|
//
|
|
|
|
if (pite->ITE_Binding == NULL) {
|
|
|
|
TRACE1(
|
|
REQUEST, "processing request: interface %d not bound",
|
|
dwIndex
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
paddr = IPBOOTP_IF_ADDRESS_TABLE(pite->ITE_Binding) + pwc->IC_AddrIndex;
|
|
|
|
//
|
|
// if we are not configured to relay, do nothing
|
|
//
|
|
|
|
if (pic->IC_RelayMode == IPBOOTP_RELAY_DISABLED) { break; }
|
|
|
|
pibp = (PIPBOOTP_PACKET)pwc->IC_InputPacket;
|
|
|
|
|
|
//
|
|
// check the hop-count field to see if it is over the max hop-count
|
|
// configured for this interface
|
|
//
|
|
|
|
if (pibp->IP_HopCount > IPBOOTP_MAX_HOP_COUNT ||
|
|
pibp->IP_HopCount > pic->IC_MaxHopCount) {
|
|
|
|
//
|
|
// discard and log
|
|
//
|
|
|
|
CHAR szHops[12], *lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
|
|
_ltoa(pibp->IP_HopCount, szHops, 10);
|
|
TRACE4(
|
|
REQUEST,
|
|
"dropping REQUEST with hop-count %d: max hop-count is %d on interface %d (%s)",
|
|
pibp->IP_HopCount, pic->IC_MaxHopCount, dwIndex, lpszAddr
|
|
);
|
|
LOGWARN2(HOP_COUNT_TOO_HIGH, lpszAddr, szHops, 0);
|
|
|
|
InterlockedIncrement(&pis->IS_RequestsDiscarded);
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// check the seconds threshold to make sure it is up to the minimum
|
|
//
|
|
|
|
if (pibp->IP_SecondsSinceBoot < pic->IC_MinSecondsSinceBoot) {
|
|
|
|
//
|
|
// discard and log
|
|
//
|
|
|
|
CHAR szSecs[12], *lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
|
|
_ltoa(pibp->IP_SecondsSinceBoot, szSecs, 10);
|
|
TRACE3(
|
|
REQUEST,
|
|
"dropping REQUEST with secs-since-boot %d on interface %d (%s)",
|
|
pibp->IP_SecondsSinceBoot, dwIndex, lpszAddr
|
|
);
|
|
LOGINFO2(SECS_SINCE_BOOT_TOO_LOW, lpszAddr, szSecs, 0);
|
|
|
|
InterlockedIncrement(&pis->IS_RequestsDiscarded);
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// increment the hop-count
|
|
//
|
|
|
|
++pibp->IP_HopCount;
|
|
|
|
|
|
|
|
//
|
|
// fill in relay agent IP address if it is empty
|
|
//
|
|
|
|
if (pibp->IP_AgentAddress == 0) {
|
|
pibp->IP_AgentAddress = paddr->IA_Address;
|
|
}
|
|
|
|
|
|
//
|
|
// if a dhcp-inform server has been set,
|
|
// and this packet is a dhcp inform packet,
|
|
// we will forward it to the dhcp-inform server.
|
|
//
|
|
|
|
pdp = (PDHCP_PACKET)(pibp + 1);
|
|
if (!(dwDhcpInformServer = ig.IG_DhcpInformServer) ||
|
|
pwc->IC_InputLength <
|
|
sizeof(IPBOOTP_PACKET) + sizeof(DHCP_PACKET) + 1 ||
|
|
*(DWORD UNALIGNED *)pdp->Cookie != DHCP_MAGIC_COOKIE ||
|
|
pdp->Tag != DHCP_TAG_MESSAGE_TYPE ||
|
|
pdp->Length != 1 ||
|
|
pdp->Option[0] != DHCP_MESSAGE_INFORM
|
|
) {
|
|
dwDhcpInformServer = 0;
|
|
}
|
|
|
|
//
|
|
// relay the request to all configured BOOTP servers
|
|
//
|
|
|
|
ACQUIRE_READ_LOCK(&ig.IG_RWL);
|
|
|
|
pigc = ig.IG_Config;
|
|
if (dwDhcpInformServer) {
|
|
pdwAddr = &dwDhcpInformServer;
|
|
pdwEnd = pdwAddr + 1;
|
|
}
|
|
else {
|
|
pdwAddr = (PDWORD)((PBYTE)pigc + sizeof(IPBOOTP_GLOBAL_CONFIG));
|
|
pdwEnd = pdwAddr + pigc->GC_ServerCount;
|
|
}
|
|
|
|
for ( ; pdwAddr < pdwEnd; pdwAddr++) {
|
|
|
|
sinsrv.sin_family = AF_INET;
|
|
sinsrv.sin_port = htons(IPBOOTP_SERVER_PORT);
|
|
sinsrv.sin_addr.s_addr = *pdwAddr;
|
|
|
|
iErr = sendto(
|
|
pite->ITE_Sockets[pwc->IC_AddrIndex],
|
|
pwc->IC_InputPacket, pwc->IC_InputLength, 0,
|
|
(PSOCKADDR)&sinsrv, sizeof(SOCKADDR_IN)
|
|
);
|
|
|
|
if (iErr == SOCKET_ERROR || iErr < (INT)pwc->IC_InputLength) {
|
|
|
|
CHAR szSrv[20], *lpszAddr;
|
|
|
|
dwErr = WSAGetLastError();
|
|
if ((lpszAddr = INET_NTOA(*pdwAddr)) != NULL) {
|
|
lstrcpy(szSrv, lpszAddr);
|
|
lpszAddr = INET_NTOA(paddr->IA_Address);
|
|
if (lpszAddr != NULL) {
|
|
TRACE4(
|
|
REQUEST,
|
|
"error %d relaying REQUEST to server %s on interface %d (%s)",
|
|
dwErr, szSrv, dwIndex, lpszAddr
|
|
);
|
|
LOGERR2(RELAY_REQUEST_FAILED, lpszAddr, szSrv, dwErr);
|
|
}
|
|
}
|
|
InterlockedIncrement(&pis->IS_SendFailures);
|
|
}
|
|
}
|
|
|
|
RELEASE_READ_LOCK(&ig.IG_RWL);
|
|
|
|
} while(FALSE);
|
|
|
|
|
|
RELEASE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
BOOTP_FREE(pwc);
|
|
|
|
|
|
TRACE0(LEAVE, "leaving ProcessRequest");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ProcessReply
|
|
//
|
|
// This function handles the relaying of BOOT_REPLY packets
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
ProcessReply(
|
|
PVOID pContext
|
|
) {
|
|
|
|
INT iErr;
|
|
PIF_TABLE pTable;
|
|
BOOL bArpUpdated;
|
|
SOCKADDR_IN sincli;
|
|
PINPUT_CONTEXT pwc;
|
|
PIPBOOTP_PACKET pibp;
|
|
PIPBOOTP_IP_ADDRESS paddrin, paddrout;
|
|
PIPBOOTP_IF_STATS pisin, pisout;
|
|
PIF_TABLE_ENTRY pitein, piteout;
|
|
DWORD dwErr, dwIndex, dwAddress, dwAddrIndexOut;
|
|
|
|
|
|
TRACE0(ENTER, "entering ProcessReply");
|
|
|
|
|
|
pwc = (PINPUT_CONTEXT)pContext;
|
|
|
|
pTable = ig.IG_IfTable;
|
|
|
|
ACQUIRE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
|
|
do { // error breakout loop
|
|
|
|
|
|
//
|
|
// get the interface on which the packet was received
|
|
//
|
|
|
|
dwIndex = pwc->IC_InterfaceIndex;
|
|
|
|
pitein = GetIfByIndex(pTable, dwIndex);
|
|
|
|
if (pitein == NULL) {
|
|
|
|
TRACE1(REPLY, "processing REPLY: interface %d not found", dwIndex);
|
|
|
|
break;
|
|
}
|
|
|
|
if (pitein->ITE_Binding == NULL) {
|
|
|
|
TRACE1(REPLY, "processing REPLY: interface %d not bound", dwIndex);
|
|
|
|
break;
|
|
}
|
|
|
|
paddrin = IPBOOTP_IF_ADDRESS_TABLE(pitein->ITE_Binding) +
|
|
pwc->IC_AddrIndex;
|
|
|
|
|
|
//
|
|
// if we are not configured t relay on this interface, do nothing
|
|
//
|
|
|
|
if (pitein->ITE_Config->IC_RelayMode == IPBOOTP_RELAY_DISABLED) {
|
|
|
|
TRACE2(
|
|
REPLY,
|
|
"dropping REPLY: relaying on interface %d (%s) is disabled",
|
|
pitein->ITE_Index, INET_NTOA(paddrin->IA_Address)
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
pisin = &pitein->ITE_Stats;
|
|
|
|
|
|
//
|
|
// place a template over the packet, and retrieve
|
|
// the AgentAddress field; this contains the address
|
|
// of the relay agent responsible for relaying this REPLY
|
|
//
|
|
|
|
pibp = (PIPBOOTP_PACKET)pwc->IC_InputPacket;
|
|
dwAddress = pibp->IP_AgentAddress;
|
|
|
|
|
|
//
|
|
// see if the address in the reply matches any local interface
|
|
//
|
|
|
|
piteout = GetIfByAddress(pTable, dwAddress, &dwAddrIndexOut);
|
|
|
|
if (piteout == NULL) {
|
|
|
|
CHAR szAddress[20];
|
|
PCHAR pStr1, pStr2;
|
|
|
|
pStr1 = INET_NTOA(dwAddress);
|
|
if (pStr1)
|
|
lstrcpy(szAddress, pStr1);
|
|
|
|
pStr2 = INET_NTOA(paddrin->IA_Address);
|
|
|
|
if (pStr1 && pStr2) {
|
|
TRACE3(
|
|
REPLY,
|
|
"dropping REPLY packet on interface %d (%s); no interfaces have address %s",
|
|
pitein->ITE_Index, pStr2, szAddress
|
|
);
|
|
}
|
|
|
|
InterlockedIncrement(&pisin->IS_RepliesDiscarded);
|
|
break;
|
|
}
|
|
|
|
if (piteout->ITE_Binding == NULL) {
|
|
|
|
TRACE1(REPLY, "processing REPLY: outgoing interface %d is not bound", dwIndex);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
paddrout = IPBOOTP_IF_ADDRESS_TABLE(piteout->ITE_Binding) +
|
|
dwAddrIndexOut;
|
|
|
|
|
|
//
|
|
// only relay if relay is enabled on the outgoing interface
|
|
//
|
|
|
|
if (piteout->ITE_Config->IC_RelayMode == IPBOOTP_RELAY_DISABLED) {
|
|
|
|
TRACE2(
|
|
REPLY,
|
|
"dropping REPLY: relaying on interface %d (%s) is disabled",
|
|
piteout->ITE_Index, INET_NTOA(paddrout->IA_Address)
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
pisout = &piteout->ITE_Stats;
|
|
|
|
|
|
//
|
|
// the message must be relayed on the interface whose address
|
|
// was specifed in the packet;
|
|
//
|
|
|
|
|
|
// if the broadcast bit is not set and the clients IP address
|
|
// is in the packet, add an entry to the ARP cache for the client
|
|
// and then relay the packet by unicast
|
|
//
|
|
|
|
sincli.sin_family = AF_INET;
|
|
sincli.sin_port = htons(IPBOOTP_CLIENT_PORT);
|
|
|
|
if ((pibp->IP_Flags & htons(IPBOOTP_FLAG_BROADCAST)) != 0 ||
|
|
pibp->IP_OfferedAddress == 0) {
|
|
|
|
//
|
|
// the broadcast bit is set of the offered address is 0,
|
|
// which is not an address we can add to the ARP cache;
|
|
// in this case, send by broadcast
|
|
//
|
|
|
|
bArpUpdated = FALSE;
|
|
sincli.sin_addr.s_addr = INADDR_BROADCAST;
|
|
}
|
|
else {
|
|
|
|
|
|
//
|
|
// attempt to seed the ARP cache with the address
|
|
// offered to the client in the packet we are about
|
|
// to send to the client.
|
|
//
|
|
|
|
dwErr = UpdateArpCache(
|
|
piteout->ITE_Index, pibp->IP_OfferedAddress,
|
|
(PBYTE)pibp->IP_MacAddr, pibp->IP_MacAddrLength,
|
|
TRUE, ig.IG_FunctionTable
|
|
);
|
|
|
|
if (dwErr == NO_ERROR) {
|
|
|
|
bArpUpdated = TRUE;
|
|
sincli.sin_addr.s_addr = pibp->IP_OfferedAddress;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// okay, that didn't work,
|
|
// so fall back on broadcasting the packet
|
|
//
|
|
|
|
TRACE3(
|
|
REPLY,
|
|
"error %d adding entry to ARP cache on interface %d (%s)",
|
|
dwErr, piteout->ITE_Index, INET_NTOA(paddrout->IA_Address)
|
|
);
|
|
|
|
bArpUpdated = FALSE;
|
|
sincli.sin_addr.s_addr = INADDR_BROADCAST;
|
|
|
|
InterlockedIncrement(&pisout->IS_ArpUpdateFailures);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// relay the packet
|
|
//
|
|
|
|
iErr = sendto(
|
|
piteout->ITE_Sockets[dwAddrIndexOut], pwc->IC_InputPacket,
|
|
pwc->IC_InputLength, 0,
|
|
(PSOCKADDR)&sincli, sizeof(SOCKADDR_IN)
|
|
);
|
|
|
|
if (iErr == SOCKET_ERROR || iErr < (INT)pwc->IC_InputLength) {
|
|
|
|
INT i;
|
|
BYTE *pb;
|
|
CHAR szCli[64], *psz, *lpszAddr, szDigits[] = "0123456789ABCDEF";
|
|
|
|
dwErr = WSAGetLastError();
|
|
lpszAddr = INET_NTOA(paddrout->IA_Address);
|
|
|
|
//
|
|
// format the client's hardware address
|
|
//
|
|
for (i = 0, psz = szCli, pb = pibp->IP_MacAddr;
|
|
i < 16 && i < pibp->IP_MacAddrLength;
|
|
i++, pb++) {
|
|
*psz++ = szDigits[*pb / 16];
|
|
*psz++ = szDigits[*pb % 16];
|
|
*psz++ = ':';
|
|
}
|
|
|
|
(psz == szCli) ? (*psz = '\0') : (*(psz - 1) = '\0');
|
|
|
|
|
|
TRACE4(
|
|
REPLY,
|
|
"error %d relaying REPLY to client %s on interface %d (%s)",
|
|
dwErr, szCli, dwIndex, lpszAddr
|
|
);
|
|
LOGERR2(RELAY_REPLY_FAILED, lpszAddr, szCli, dwErr);
|
|
|
|
InterlockedIncrement(&pisout->IS_SendFailures);
|
|
}
|
|
|
|
|
|
//
|
|
// remove the ARP entry if one was added
|
|
//
|
|
|
|
if (bArpUpdated) {
|
|
|
|
dwErr = UpdateArpCache(
|
|
piteout->ITE_Index, pibp->IP_OfferedAddress,
|
|
(PBYTE)pibp->IP_MacAddr, pibp->IP_MacAddrLength,
|
|
FALSE, ig.IG_FunctionTable
|
|
);
|
|
|
|
if (dwErr != NO_ERROR) {
|
|
InterlockedIncrement(&pisout->IS_ArpUpdateFailures);
|
|
}
|
|
}
|
|
|
|
} while(FALSE);
|
|
|
|
RELEASE_READ_LOCK(&pTable->IT_RWL);
|
|
|
|
BOOTP_FREE(pwc);
|
|
|
|
|
|
|
|
TRACE0(LEAVE, "leaving ProcessReply");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#define ClearScreen(h) { \
|
|
DWORD _dwin,_dwout; \
|
|
COORD _c = {0, 0}; \
|
|
CONSOLE_SCREEN_BUFFER_INFO _csbi; \
|
|
GetConsoleScreenBufferInfo(h,&_csbi); \
|
|
_dwin = _csbi.dwSize.X * _csbi.dwSize.Y; \
|
|
FillConsoleOutputCharacter(h,' ',_dwin,_c,&_dwout); \
|
|
}
|
|
|
|
VOID
|
|
PrintGlobalConfig(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
);
|
|
|
|
VOID
|
|
PrintIfConfig(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
);
|
|
|
|
VOID
|
|
PrintIfBinding(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
);
|
|
|
|
VOID
|
|
PrintIfStats(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
);
|
|
|
|
|
|
|
|
#if DBG
|
|
|
|
VOID
|
|
CallbackFunctionMibDisplay(
|
|
PVOID pContext,
|
|
BOOLEAN NotUsed
|
|
) {
|
|
|
|
// enter/leaveBootpWorker not required as timer queue is persistent
|
|
|
|
QueueBootpWorker(WorkerFunctionMibDisplay, pContext);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
WorkerFunctionMibDisplay(
|
|
PVOID pContext
|
|
) {
|
|
COORD c;
|
|
INT iErr;
|
|
FD_SET fdsRead;
|
|
HANDLE hConsole;
|
|
TIMEVAL tvTimeout;
|
|
DWORD dwErr, dwTraceID;
|
|
DWORD dwExactSize, dwInSize, dwOutSize;
|
|
IPBOOTP_MIB_GET_INPUT_DATA imgid;
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod;
|
|
|
|
if (!ENTER_BOOTP_WORKER()) { return; }
|
|
|
|
TraceGetConsole(ig.IG_MibTraceID, &hConsole);
|
|
if (hConsole == NULL) {
|
|
LEAVE_BOOTP_WORKER();
|
|
return;
|
|
}
|
|
|
|
ClearScreen(hConsole);
|
|
|
|
c.X = c.Y = 0;
|
|
|
|
dwInSize = sizeof(imgid);
|
|
imgid.IMGID_TypeID = IPBOOTP_GLOBAL_CONFIG_ID;
|
|
dwOutSize = 0;
|
|
pimgod = NULL;
|
|
|
|
|
|
dwErr = MibGetFirst(dwInSize, &imgid, &dwOutSize, pimgod);
|
|
|
|
if (dwErr == ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
pimgod = BOOTP_ALLOC(dwOutSize);
|
|
if (pimgod) {
|
|
dwErr = MibGetFirst(dwInSize, &imgid, &dwOutSize, pimgod);
|
|
}
|
|
}
|
|
|
|
while (dwErr == NO_ERROR) {
|
|
|
|
switch(pimgod->IMGOD_TypeID) {
|
|
case IPBOOTP_GLOBAL_CONFIG_ID:
|
|
PrintGlobalConfig(hConsole, &c, &imgid, pimgod);
|
|
break;
|
|
case IPBOOTP_IF_CONFIG_ID:
|
|
PrintIfConfig(hConsole, &c, &imgid, pimgod);
|
|
break;
|
|
case IPBOOTP_IF_BINDING_ID:
|
|
PrintIfBinding(hConsole, &c, &imgid, pimgod);
|
|
break;
|
|
case IPBOOTP_IF_STATS_ID:
|
|
PrintIfStats(hConsole, &c, &imgid, pimgod);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// move to next line
|
|
//
|
|
|
|
++c.Y;
|
|
|
|
dwOutSize = 0;
|
|
if (pimgod) { BOOTP_FREE(pimgod); pimgod = NULL; }
|
|
|
|
dwErr = MibGetNext(dwInSize, &imgid, &dwOutSize, pimgod);
|
|
|
|
if (dwErr == ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
pimgod = BOOTP_ALLOC(dwOutSize);
|
|
if (pimgod) {
|
|
dwErr = MibGetNext(dwInSize, &imgid, &dwOutSize, pimgod);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pimgod != NULL) { BOOTP_FREE(pimgod); }
|
|
|
|
|
|
LEAVE_BOOTP_WORKER();
|
|
|
|
return;
|
|
}
|
|
#endif //if DBG
|
|
|
|
#define WriteLine(h,c,fmt,arg) { \
|
|
DWORD _dw; \
|
|
CHAR _sz[200], _fmt[200]; \
|
|
wsprintf(_fmt,"%-100s",fmt); \
|
|
wsprintf(_sz,_fmt,arg); \
|
|
WriteConsoleOutputCharacter(h,_sz,lstrlen(_sz),c,&_dw); \
|
|
++(c).Y; \
|
|
}
|
|
|
|
|
|
VOID
|
|
PrintGlobalConfig(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
) {
|
|
|
|
PDWORD pdwsrv, pdwsrvend;
|
|
PIPBOOTP_GLOBAL_CONFIG pgc;
|
|
|
|
pgc = (PIPBOOTP_GLOBAL_CONFIG)pimgod->IMGOD_Buffer;
|
|
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Logging Level: %d",
|
|
pgc->GC_LoggingLevel
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Max Receive Queue Size: %d",
|
|
pgc->GC_MaxRecvQueueSize
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"BOOTP Server Count: %d",
|
|
pgc->GC_ServerCount
|
|
);
|
|
pdwsrv = (PDWORD)(pgc + 1);
|
|
pdwsrvend = pdwsrv + pgc->GC_ServerCount;
|
|
for ( ; pdwsrv < pdwsrvend; pdwsrv++) {
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"BOOTP Server: %s",
|
|
INET_NTOA(*pdwsrv)
|
|
);
|
|
}
|
|
|
|
pimgid->IMGID_TypeID = IPBOOTP_GLOBAL_CONFIG_ID;
|
|
}
|
|
|
|
|
|
VOID
|
|
PrintIfConfig(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
) {
|
|
|
|
CHAR szMode[20];
|
|
PIPBOOTP_IF_CONFIG pic;
|
|
|
|
pic = (PIPBOOTP_IF_CONFIG)pimgod->IMGOD_Buffer;
|
|
|
|
switch (pic->IC_RelayMode) {
|
|
case IPBOOTP_RELAY_ENABLED:
|
|
strcpy(szMode, "enabled"); break;
|
|
case IPBOOTP_RELAY_DISABLED:
|
|
strcpy(szMode, "disabled"); break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Interface Index: %d",
|
|
pimgod->IMGOD_IfIndex
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Relay Mode: %s",
|
|
szMode
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Max Hop Count: %d",
|
|
pic->IC_MaxHopCount
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Min Seconds Since Boot: %d",
|
|
pic->IC_MinSecondsSinceBoot
|
|
);
|
|
|
|
pimgid->IMGID_TypeID = IPBOOTP_IF_CONFIG_ID;
|
|
pimgid->IMGID_IfIndex = pimgod->IMGOD_IfIndex;
|
|
}
|
|
|
|
|
|
VOID
|
|
PrintIfBinding(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
) {
|
|
|
|
DWORD i;
|
|
CHAR szAddr[64];
|
|
PIPBOOTP_IF_BINDING pib;
|
|
PIPBOOTP_IP_ADDRESS paddr;
|
|
|
|
pib = (PIPBOOTP_IF_BINDING)pimgod->IMGOD_Buffer;
|
|
paddr = IPBOOTP_IF_ADDRESS_TABLE(pib);
|
|
|
|
WriteLine(
|
|
hConsole, *pc, "Interface Index: %d",
|
|
pimgod->IMGOD_IfIndex
|
|
);
|
|
WriteLine(
|
|
hConsole, *pc, "Address Count: %d",
|
|
pib->IB_AddrCount
|
|
);
|
|
for (i = 0; i < pib->IB_AddrCount; i++, paddr++) {
|
|
LPSTR szTemp;
|
|
szTemp = INET_NTOA(paddr->IA_Address);
|
|
if (szTemp != NULL) {
|
|
lstrcpy(szAddr, szTemp);
|
|
lstrcat(szAddr, " - ");
|
|
szTemp = INET_NTOA(paddr->IA_Netmask);
|
|
if ( szTemp != NULL ) { lstrcat(szAddr, szTemp); }
|
|
WriteLine(
|
|
hConsole, *pc, "Address Entry: %s",
|
|
szAddr
|
|
);
|
|
}
|
|
}
|
|
|
|
pimgid->IMGID_TypeID = IPBOOTP_IF_BINDING_ID;
|
|
pimgid->IMGID_IfIndex = pimgod->IMGOD_IfIndex;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
PrintIfStats(
|
|
HANDLE hConsole,
|
|
PCOORD pc,
|
|
PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
|
|
PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod
|
|
) {
|
|
|
|
PIPBOOTP_IF_STATS pis;
|
|
|
|
pis = (PIPBOOTP_IF_STATS)pimgod->IMGOD_Buffer;
|
|
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Interface Index: %d",
|
|
pimgod->IMGOD_IfIndex
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Send Failures: %d",
|
|
pis->IS_SendFailures
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Receive Failures: %d",
|
|
pis->IS_ReceiveFailures
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"ARP Cache Update Failures: %d",
|
|
pis->IS_ArpUpdateFailures
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Requests Received: %d",
|
|
pis->IS_RequestsReceived
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Requests Discarded: %d",
|
|
pis->IS_RequestsDiscarded
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Replies Received: %d",
|
|
pis->IS_RepliesReceived
|
|
);
|
|
WriteLine(
|
|
hConsole,
|
|
*pc,
|
|
"Replies Discarded: %d",
|
|
pis->IS_RepliesDiscarded
|
|
);
|
|
|
|
pimgid->IMGID_TypeID = IPBOOTP_IF_STATS_ID;
|
|
pimgid->IMGID_IfIndex = pimgod->IMGOD_IfIndex;
|
|
}
|