mirror of https://github.com/tongzx/nt5src
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.
1137 lines
29 KiB
1137 lines
29 KiB
/*++
|
|
|
|
Copyright (c) 1996-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
udp.c
|
|
|
|
Abstract:
|
|
|
|
Receives and processes UDP DNS packets using i/o completion ports.
|
|
|
|
Author:
|
|
|
|
Jim Gilroy, November 1996
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "dnssrv.h"
|
|
|
|
//
|
|
// UDP completion port
|
|
//
|
|
|
|
HANDLE g_hUdpCompletionPort;
|
|
|
|
//
|
|
// Limit retries to protect against WSAECONNRESET nonsense
|
|
//
|
|
// Retry on CONRESET for 10000 times then give up for unbind and
|
|
// slow retry.
|
|
// We retry on repeated GQCS failures at 10ms interval.
|
|
//
|
|
|
|
#define RECV_RETRY_MAX_COUNT (10000)
|
|
#define RECV_RETRY_SLEEP_TIME (10)
|
|
|
|
//
|
|
// Recv counters to detect socket failures
|
|
//
|
|
|
|
DWORD UdpRecvCount = 0;
|
|
DWORD LastUdpRecvTime = 0;
|
|
DWORD NextUdpLogTime = 0;
|
|
|
|
#define UDP_RECV_TICK() \
|
|
{ \
|
|
UdpRecvCount++; \
|
|
LastUdpRecvTime = DNS_TIME(); \
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
Udp_DropReceive(
|
|
IN OUT PDNS_SOCKET pContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Drop down UDP receive request.
|
|
|
|
Arguments:
|
|
|
|
pContext -- context for socket being recieved
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PDNS_MSGINFO pmsg;
|
|
DNS_STATUS status;
|
|
|
|
IF_DEBUG( READ2 )
|
|
{
|
|
DNS_PRINT((
|
|
"Drop WSARecvFrom on socket %d (thread=%p).\n",
|
|
pContext->Socket,
|
|
GetCurrentThreadId() ));
|
|
ASSERT( pContext->Overlapped.Offset == 0
|
|
&& pContext->Overlapped.OffsetHigh == 0
|
|
&& pContext->Overlapped.hEvent == NULL );
|
|
}
|
|
|
|
//
|
|
// Check for service shutdown/pause.
|
|
//
|
|
|
|
if ( !Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, (
|
|
"Udp_DropReceive detected shutdown -- returning\n" ));
|
|
return;
|
|
}
|
|
|
|
ASSERT( pContext->State != SOCKSTATE_UDP_RECV_DOWN );
|
|
|
|
//
|
|
// get DNS message buffer
|
|
//
|
|
// DEVNOTE: allocation failure handling
|
|
//
|
|
|
|
pmsg = Packet_AllocateUdpMessage();
|
|
IF_NOMEM( !pmsg )
|
|
{
|
|
ASSERT( FALSE );
|
|
return;
|
|
}
|
|
|
|
pContext->pMsg = pmsg;
|
|
pContext->WsaBuf.len = pmsg->MaxBufferLength;
|
|
pContext->WsaBuf.buf = (PCHAR) DNS_HEADER_PTR(pmsg);
|
|
pContext->RecvfromFlags = 0;
|
|
|
|
pmsg->Socket = pContext->Socket;
|
|
|
|
|
|
//
|
|
// loop until successful WSARecvFrom() is down
|
|
//
|
|
// this loop is only active while we continue to recv
|
|
// WSAECONNRESET or WSAEMSGSIZE errors, both of which
|
|
// cause us to dump data and retry;
|
|
//
|
|
// note loop rather than recursion (to this function) is
|
|
// required to avoid possible stack overflow from malicious
|
|
// send
|
|
//
|
|
// normal returns from WSARecvFrom() are
|
|
// SUCCESS -- packet was waiting, GQCS will fire immediately
|
|
// WSA_IO_PENDING -- no data yet, GQCS will fire when ready
|
|
//
|
|
|
|
while ( 1 )
|
|
{
|
|
pContext->State = SOCKSTATE_UDP_RECV_DOWN;
|
|
|
|
status = WSARecvFrom(
|
|
pContext->Socket,
|
|
& pContext->WsaBuf,
|
|
1,
|
|
& pContext->BytesRecvd,
|
|
& pContext->RecvfromFlags,
|
|
(PSOCKADDR) & pmsg->RemoteAddress,
|
|
& pmsg->RemoteAddressLength,
|
|
& pContext->Overlapped,
|
|
NULL );
|
|
|
|
if ( status == ERROR_SUCCESS )
|
|
{
|
|
DNS_DEBUG( RECV, (
|
|
"WSARecvFrom( %d ) immediate completion %d bytes.\n",
|
|
pContext->Socket,
|
|
pContext->BytesRecvd ));
|
|
pContext->fRetry = 0;
|
|
return;
|
|
}
|
|
|
|
status = GetLastError();
|
|
if ( status == WSA_IO_PENDING )
|
|
{
|
|
DNS_DEBUG( RECV, (
|
|
"WSARecvFrom( %d ) WSA_IO_PENDING.\n",
|
|
pContext->Socket ));
|
|
pContext->fRetry = 0;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// if we are doing processing here, then it means completion port
|
|
// should not be waking folks on these errors
|
|
//
|
|
|
|
ASSERT( pContext->State == SOCKSTATE_UDP_RECV_DOWN ||
|
|
pContext->State == SOCKSTATE_DEAD );
|
|
|
|
if ( pContext->State != SOCKSTATE_UDP_RECV_DOWN )
|
|
{
|
|
DWORD state = pContext->State;
|
|
|
|
DnsDbg_Lock();
|
|
DNS_DEBUG( ANY, (
|
|
"ERROR: WSARecvFrom() failed with socket %d in state %d\n"
|
|
"\tthread = %p\n",
|
|
pContext->Socket,
|
|
state,
|
|
GetCurrentThreadId() ));
|
|
Dbg_SocketContext(
|
|
"WSARecvFrom() failed socket in incorrect state\n"
|
|
"\tnote state shown below may be altered!\n",
|
|
pContext );
|
|
DnsDbg_Unlock();
|
|
|
|
if ( state == SOCKSTATE_DEAD )
|
|
{
|
|
ASSERT( status == WSAENOTSOCK );
|
|
Sock_CleanupDeadSocketMessage( pContext );
|
|
}
|
|
ELSE_ASSERT_FALSE;
|
|
|
|
Log_SocketFailure(
|
|
"ERROR: RecvFrom failure in weird state.",
|
|
pContext,
|
|
status );
|
|
return;
|
|
}
|
|
|
|
pContext->State = SOCKSTATE_UDP_RECV_ERROR;
|
|
|
|
DNS_DEBUG( RECV, (
|
|
"WSARecvFrom() error %d %p.\n"
|
|
"\tpContext = %p\n"
|
|
"\tsocket = %d\n"
|
|
"\tthread = %p\n",
|
|
status, status,
|
|
pContext,
|
|
pContext->Socket,
|
|
GetCurrentThreadId() ));
|
|
|
|
//
|
|
// new winsock feature returns WSAECONNRESET when last send ICMP'd
|
|
// - set flag to indicate retry and repost send
|
|
// - if over some reasonable number of retries, assume error
|
|
// and fall through recv failure code
|
|
//
|
|
|
|
if ( status == WSAECONNRESET )
|
|
{
|
|
DNS_DEBUG( RECV, ( "WSARecvFrom() WSAECONNRESET.\n" ));
|
|
if ( pContext->fRetry < RECV_RETRY_MAX_COUNT )
|
|
{
|
|
DNS_DEBUG( RECV, (
|
|
"WSARecvFrom( %d ) ECONNRESET (retry=%d).\n",
|
|
pContext->Socket,
|
|
pContext->fRetry ));
|
|
pContext->fRetry++;
|
|
STAT_INC( PrivateStats.UdpConnResets );
|
|
continue;
|
|
}
|
|
DNS_DEBUG( ANY, (
|
|
"ERROR: unsuccessful in shaking CONNRESET in %d retries\n"
|
|
"\tIndicating recv() error on socket %d to avoid cycle on this\n"
|
|
"\tthread.\n",
|
|
pContext->fRetry,
|
|
pContext->Socket ));
|
|
|
|
STAT_INC( PrivateStats.UdpConnResetRetryOverflow );
|
|
}
|
|
|
|
//
|
|
// message too big
|
|
// - treat like truncated message
|
|
//
|
|
// DEVNOTE: treat WSAEMSGSIZE like trunctated message
|
|
//
|
|
|
|
if ( status == WSAEMSGSIZE )
|
|
{
|
|
DNS_DEBUG( RECV, (
|
|
"WSARecvFrom( %d ) EMSGSIZE (retry=%d).\n",
|
|
pContext->Socket,
|
|
pContext->fRetry ));
|
|
|
|
STAT_INC( PrivateStats.UdpErrorMessageSize );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// DEVNOTE: Plug+Play may byte us on WSARecvFrom, need to cleanup
|
|
// gracefully
|
|
// note, I believe we can return cleanly GetQueuedCompletionStatus()
|
|
// will get new context
|
|
// if add PnP event, the event handling must generate all init on
|
|
// socket
|
|
|
|
if ( ! Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, (
|
|
"WSARecvFrom failed (%d) due to shutdown -- returning\n",
|
|
status ));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// check for PnP delete of socket
|
|
// note, socket list is locked for entire time we do PnP reconfig
|
|
// so when GetAssociateIpAddr() returns we only have valid
|
|
// address if socket is still enlisted
|
|
|
|
if ( Sock_GetAssociatedIpAddr(pContext->Socket) == DNS_INVALID_IP )
|
|
{
|
|
DNS_DEBUG( SOCKET, (
|
|
"WSARecvFrom( %d ) failure, socket no longer enlisted.\n"
|
|
"\tcontext ptr = %p\n",
|
|
pContext->Socket,
|
|
pContext ));
|
|
|
|
Sock_CleanupDeadSocketMessage( pContext );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// recvfrom() failure with active socket
|
|
// - set global flag to indicate retry after timeout
|
|
// - log event
|
|
//
|
|
|
|
Packet_FreeUdpMessage( pmsg );
|
|
STAT_INC( PrivateStats.UdpRecvFailure );
|
|
Sock_IndicateUdpRecvFailure( pContext, status );
|
|
|
|
DNS_PRINT((
|
|
"ERROR: WSARecvFrom( %d ) failed = %d\n"
|
|
"\tcontext ptr %p\n",
|
|
pContext->Socket,
|
|
status,
|
|
pContext ));
|
|
|
|
#if DBG
|
|
DNS_LOG_EVENT(
|
|
DNS_EVENT_RECVFROM_CALL_FAILED,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
status );
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
Udp_RecvThread(
|
|
IN LPVOID pvDummy
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
UDP receiving thread.
|
|
|
|
Loops waiting on sockets, recieving then processing DNS requests.
|
|
|
|
Arguments:
|
|
|
|
pvDummy -- unused
|
|
|
|
Return Value:
|
|
|
|
TRUE on normal service shutdown
|
|
FALSE on socket error
|
|
|
|
--*/
|
|
{
|
|
PDNS_SOCKET pcontext;
|
|
PDNS_MSGINFO pmsg;
|
|
DNS_STATUS status;
|
|
DWORD bytesRecvd;
|
|
LPOVERLAPPED poverlapped;
|
|
|
|
|
|
DNS_DEBUG( INIT, ( "\nStart UDP receive thread.\n" ));
|
|
|
|
// hold off processing until started
|
|
|
|
if ( ! Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( ANY, ( "Terminating UDP thread.\n" ));
|
|
return( 1 );
|
|
}
|
|
|
|
//
|
|
// loop receiving and processing packets, until service shutdown
|
|
//
|
|
|
|
while ( TRUE )
|
|
{
|
|
pcontext = NULL; // PREFIX paranoia
|
|
|
|
//
|
|
// Wait for incoming packet
|
|
//
|
|
|
|
if ( ! GetQueuedCompletionStatus(
|
|
g_hUdpCompletionPort,
|
|
& bytesRecvd,
|
|
& (ULONG_PTR) pcontext,
|
|
& poverlapped,
|
|
INFINITE ) )
|
|
{
|
|
DWORD state = 0;
|
|
|
|
status = GetLastError();
|
|
#if 0
|
|
// ideal to fast path this, but avoid too much duplicate code
|
|
//
|
|
// ICMP port unreachable
|
|
// when response is late, and client has left (hence no port)
|
|
// socket is indicated with conn-reset (WSARecvFrom)
|
|
// or port-unreachable GQCS
|
|
//
|
|
// DEVNOTE: perhaps a similar error when clients IP is entirely
|
|
// down, should trap it also
|
|
|
|
if ( status == ERROR_PORT_UNREACHABLE )
|
|
{
|
|
STAT_INC( PrivateStats.UdpGQCSConnReset );
|
|
|
|
if ( pcontext )
|
|
{
|
|
pcontext->State = SOCKSTATE_UDP_GQCS_ERROR;
|
|
DNS_DEBUG( SOCKET, (
|
|
"GQCS port-unreachable on socket %d (%p)\n"
|
|
"\ttime = %d\n"
|
|
"\tpcontext = %p\n",
|
|
pcontext ? pcontext->Socket : 0,
|
|
pcontext ? pcontext->Socket : 0,
|
|
DNS_TIME(),
|
|
pcontext ));
|
|
|
|
// free message (if any)
|
|
// redrop recv
|
|
// wait again in GQCS
|
|
|
|
Packet_FreeUdpMessage( pcontext->pMsg );
|
|
Udp_DropReceive( pcontext );
|
|
}
|
|
continue;
|
|
}
|
|
#endif
|
|
//
|
|
// if fail with socket context, MUST own context
|
|
// no other thread should own context
|
|
//
|
|
// if detect another thread messing with context, then
|
|
// clear context -- it belongs to other guy
|
|
//
|
|
|
|
if ( pcontext )
|
|
{
|
|
state = pcontext->State;
|
|
|
|
//
|
|
// winsock -- if shutdown just get outta dodge, don't
|
|
// expect that they haven't woken 27 threads on this socket
|
|
//
|
|
|
|
if ( fDnsServiceExit )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, ( "\nTerminating UDP receive thread.\n" ));
|
|
|
|
IF_DEBUG( ANY )
|
|
{
|
|
if ( state != SOCKSTATE_UDP_RECV_DOWN &&
|
|
state != SOCKSTATE_DEAD &&
|
|
state != SOCKSTATE_UDP_GQCS_ERROR )
|
|
{
|
|
DNS_DEBUG( ANY, (
|
|
"Winsock getting weird on me again during socket shutdown:\n"
|
|
"\tsocket handle = %d\n"
|
|
"\tsock state = %d\n",
|
|
pcontext->Socket,
|
|
pcontext->State ));
|
|
}
|
|
}
|
|
return( 1 );
|
|
}
|
|
|
|
// DEVNOTE: winsock has a bug waking up multiple threads on socket close
|
|
// so it's possible on shutdown to also have the GQCS
|
|
// state set by first woken thread, when second comes through
|
|
// hence the additional shutdown case
|
|
//
|
|
// now i've also seen a bug here where state = UDP_COMPLETE which
|
|
// again implies that winsock has woken another thread which is
|
|
// processing this socket (this context); this is handled by
|
|
// the second case which just bails from the context
|
|
//
|
|
|
|
ASSERT( state == SOCKSTATE_UDP_RECV_DOWN || state == SOCKSTATE_DEAD
|
|
|| (fDnsServiceExit && state == SOCKSTATE_UDP_GQCS_ERROR) );
|
|
|
|
//
|
|
// normal failure
|
|
// - signal in failed state
|
|
// - fall through to standard failure processing below
|
|
|
|
if ( state == SOCKSTATE_UDP_RECV_DOWN )
|
|
{
|
|
pcontext->State = SOCKSTATE_UDP_GQCS_ERROR;
|
|
}
|
|
|
|
//
|
|
// socket dead (probably via PnP)
|
|
// - standard cleanup
|
|
|
|
else if ( state == SOCKSTATE_DEAD )
|
|
{
|
|
Log_SocketFailure(
|
|
"ERROR: GQCS failure on dead socket.",
|
|
pcontext,
|
|
status );
|
|
|
|
Sock_CleanupDeadSocketMessage( pcontext );
|
|
pcontext = NULL;
|
|
}
|
|
else
|
|
{
|
|
#if 0
|
|
if ( fDnsServiceExit )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, ( "\nTerminating UDP receive thread.\n" ));
|
|
return( 1 );
|
|
}
|
|
#endif
|
|
DNS_DEBUG( ANY, (
|
|
"ERROR: GQCS() failed with socket %d in state %d\n"
|
|
"\tthread = %p\n",
|
|
pcontext->Socket,
|
|
state,
|
|
GetCurrentThreadId() ));
|
|
Dbg_SocketContext(
|
|
"GCQS() failed socket in incorrect state\n"
|
|
"\tnote state shown below has been altered!\n",
|
|
pcontext );
|
|
|
|
Log_SocketFailure(
|
|
"ERROR: GQCS failure in weird state.",
|
|
pcontext,
|
|
status );
|
|
ASSERT( FALSE );
|
|
pcontext = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if i/o failed, check for shutdown
|
|
//
|
|
// errors seen:
|
|
// 995 (operation aborted) -- on socket close
|
|
// 1234 (port unreachable) -- ICMP port unreachable \ WSAECONNRESET
|
|
//
|
|
|
|
if ( ! Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, (
|
|
"\nTerminating UDP receive thread.\n" ));
|
|
return( 1 );
|
|
}
|
|
#if DBG
|
|
// exclude port-unreach errors from ANY print
|
|
|
|
if ( status != ERROR_PORT_UNREACHABLE )
|
|
{
|
|
DNS_DEBUG( ANY, (
|
|
"ERROR: GetQueuedCompletionStatus (GQCS) failed %d (%p)\n"
|
|
"\tthread id = %d\n"
|
|
"\ttime = %d\n"
|
|
"\tpcontext = %p\n"
|
|
"\tbytesRecvd = %d\n",
|
|
status, status,
|
|
GetCurrentThreadId(),
|
|
DNS_TIME(),
|
|
pcontext,
|
|
bytesRecvd ));
|
|
if ( pcontext )
|
|
{
|
|
Dbg_SocketContext(
|
|
"GCQS() failure context\n",
|
|
pcontext );
|
|
}
|
|
}
|
|
#endif
|
|
STAT_INC( PrivateStats.UdpGQCSFailure );
|
|
|
|
//
|
|
// no socket context? -- continue wait on GQCS()
|
|
//
|
|
|
|
if ( !pcontext )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// socket context with failure
|
|
// - attempt to restart recv() on socket
|
|
// - then continue to wait on GQCS()
|
|
//
|
|
// DEVNOTE: need action here -- restart all UDP sockets?
|
|
// rebuild new completion port?
|
|
//
|
|
|
|
STAT_INC( PrivateStats.UdpGQCSFailureWithContext );
|
|
|
|
if ( status == ERROR_PORT_UNREACHABLE )
|
|
{
|
|
STAT_INC( PrivateStats.UdpGQCSConnReset );
|
|
}
|
|
|
|
Packet_FreeUdpMessage( pcontext->pMsg );
|
|
|
|
//
|
|
// keep dropping recv
|
|
// Udp_DropReceive has code to handle the retry\giveup-unbind-retry issue
|
|
//
|
|
// but avoid CPU spin, if continually banging on this go into very
|
|
// light (10ms) sleep to allow any other socket to run
|
|
//
|
|
|
|
if ( pcontext->fRetry > RECV_RETRY_MAX_COUNT )
|
|
{
|
|
Log_SocketFailure(
|
|
"ERROR: GQCS failure forcing socket sleep.",
|
|
pcontext,
|
|
status );
|
|
|
|
Sleep( RECV_RETRY_SLEEP_TIME );
|
|
}
|
|
Udp_DropReceive( pcontext );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// successful completion
|
|
//
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// Verify that winsock has not written too many bytes to the packet.
|
|
//
|
|
|
|
if ( pcontext && pcontext->pMsg )
|
|
{
|
|
if ( bytesRecvd > pcontext->pMsg->MaxBufferLength )
|
|
{
|
|
DNS_DEBUG( ANY, (
|
|
"FATAL: too many bytes: %d expected max %d msg %p\n",
|
|
bytesRecvd,
|
|
pcontext->pMsg->MaxBufferLength,
|
|
pcontext->pMsg ));
|
|
HARD_ASSERT( bytesRecvd <= pcontext->pMsg->MaxBufferLength );
|
|
}
|
|
|
|
//
|
|
// NOTE: this is expensive!
|
|
//
|
|
|
|
// HeapDbgValidateAllocList();
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
// check if main thread signalling service shutdown
|
|
|
|
if ( !pcontext )
|
|
{
|
|
if ( ! Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, ( "\nTerminating UDP receive thread.\n" ));
|
|
return( 1 );
|
|
}
|
|
ASSERT( FALSE );
|
|
continue;
|
|
}
|
|
|
|
#if DBG
|
|
if ( pcontext->State != SOCKSTATE_UDP_RECV_DOWN )
|
|
{
|
|
DNS_DEBUG( ANY, (
|
|
"unexpected socket state %ul for %d %s %p\n",
|
|
pcontext->State,
|
|
pcontext->Socket,
|
|
IP_STRING( pcontext->ipAddr ),
|
|
pcontext ));
|
|
}
|
|
#endif
|
|
|
|
ASSERT( pcontext->State == SOCKSTATE_UDP_RECV_DOWN );
|
|
pcontext->State = SOCKSTATE_UDP_COMPLETED;
|
|
|
|
//
|
|
// get message info from context
|
|
//
|
|
// immediately clear pMsg from context so that alternative
|
|
// GQCS wakeup (like socket close) will not have ptr to message
|
|
// in use;
|
|
// this should NOT be necessary, but reliablity of
|
|
// GQCS not to wakeup before WSARecvFrom (and hence new pMsg)
|
|
// is in some doubt;
|
|
// - there seem to be cases where it wakes up even when WSARecvFrom()
|
|
// fails through
|
|
// - also may wake up on socket close, before WSARecvFrom() reposts
|
|
// completion request
|
|
//
|
|
|
|
pmsg = pcontext->pMsg;
|
|
if ( !pmsg )
|
|
{
|
|
DNS_PRINT((
|
|
"ERROR: no message came back with pcontext = %p\n",
|
|
pcontext ));
|
|
ASSERT( FALSE );
|
|
|
|
Udp_DropReceive( pcontext );
|
|
continue;
|
|
}
|
|
pcontext->pMsg = NULL;
|
|
pcontext->fRetry = 0;
|
|
|
|
DNS_DEBUG( RECV2, (
|
|
"I/O completion:\n"
|
|
"\tbytes recvd = %d\n"
|
|
"\toverlapped = %p\n"
|
|
"\tpcontext = %p\n"
|
|
"\t\tpmsg = %p\n"
|
|
"\t\toverlapped = %p\n"
|
|
"\t\tsocket = %d\n"
|
|
"\t\tbytes recvd = %d\n",
|
|
bytesRecvd,
|
|
poverlapped,
|
|
pcontext,
|
|
pcontext->pMsg,
|
|
& pcontext->Overlapped,
|
|
pcontext->Socket,
|
|
pcontext->BytesRecvd ));
|
|
|
|
ASSERT( pmsg->Socket == pcontext->Socket
|
|
&& &pcontext->Overlapped == poverlapped );
|
|
|
|
// track successful recv
|
|
|
|
UDP_RECV_TICK();
|
|
|
|
|
|
//
|
|
// Check and possibly wait on service status
|
|
// - even if pause dump packet as now useless
|
|
//
|
|
|
|
if ( fDnsThreadAlert )
|
|
{
|
|
DNS_DEBUG( RECV, ( "\nThread alert in UDP recv thread.\n" ));
|
|
|
|
if ( ! Thread_ServiceCheck() )
|
|
{
|
|
DNS_DEBUG( SHUTDOWN, ( "\nTerminating UDP receive thread.\n" ));
|
|
return( 1 );
|
|
}
|
|
Packet_FreeUdpMessage( pmsg );
|
|
Udp_DropReceive( pcontext );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// drop another recv on socket
|
|
// do this here rather than after processing -- so that if
|
|
// on MP machine, we can have another thread receive and
|
|
// process message from this socket
|
|
//
|
|
|
|
Udp_DropReceive( pcontext );
|
|
|
|
//
|
|
// received packet stats
|
|
//
|
|
|
|
if ( pmsg->Head.IsResponse )
|
|
{
|
|
STAT_INC( QueryStats.UdpResponsesReceived );
|
|
}
|
|
else
|
|
{
|
|
STAT_INC( QueryStats.UdpQueries );
|
|
PERF_INC( pcUdpQueryReceived );
|
|
PERF_INC( pcTotalQueryReceived );
|
|
}
|
|
|
|
//
|
|
// set info / header
|
|
// - set for UDP
|
|
// - save length
|
|
// - flip XID and RR count bytes
|
|
//
|
|
|
|
SET_MESSAGE_FIELDS_AFTER_RECV( pmsg );
|
|
pmsg->MessageLength = ( WORD ) bytesRecvd;
|
|
|
|
DNSMSG_SWAP_COUNT_BYTES( pmsg );
|
|
|
|
DNS_LOG_MESSAGE_RECV( pmsg );
|
|
|
|
IF_DEBUG( RECV )
|
|
{
|
|
Dbg_DnsMessage(
|
|
"Received UDP packet",
|
|
pmsg );
|
|
}
|
|
|
|
// process the packet
|
|
|
|
#if DBG
|
|
if ( SrvCfg_fTest9 )
|
|
{
|
|
DNS_DEBUG( ANY, ( "fTest9: ignoring UDP packet\n" ));
|
|
}
|
|
else
|
|
#endif
|
|
|
|
Answer_ProcessMessage( pmsg );
|
|
|
|
//
|
|
// for debug dump statistics every so often
|
|
//
|
|
|
|
IF_DEBUG( ANY )
|
|
{
|
|
if ( QueryStats.UdpQueries == 10 )
|
|
{
|
|
Dbg_Statistics();
|
|
}
|
|
if ( ! (QueryStats.UdpQueries % 10000) )
|
|
{
|
|
Dbg_Statistics();
|
|
}
|
|
}
|
|
|
|
// loop back to wait on next available message
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
Udp_RecvCheck(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check that UDP socket recv is functioning properly.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD timeDelta;
|
|
|
|
// no action if not logging
|
|
|
|
if ( !SrvCfg_dwQuietRecvLogInterval )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// if received packets since last check -- we're fine
|
|
//
|
|
|
|
if ( UdpRecvCount )
|
|
{
|
|
UdpRecvCount = 0;
|
|
return;
|
|
}
|
|
|
|
// reset time on startup
|
|
|
|
if ( LastUdpRecvTime == 0 )
|
|
{
|
|
LastUdpRecvTime = DNS_TIME();
|
|
}
|
|
|
|
//
|
|
// test if recv quiet for a long interval
|
|
// - but count loggings and only log once per log interval
|
|
//
|
|
// note: none of these globals are protected by CS, so entirely
|
|
// possible for recv thread to reset while in this function
|
|
// but effect is limited to:
|
|
// - an extra logging, right when recv counted
|
|
// (RecvFailureLogCount dropped after timeDelta calculated)
|
|
// - or logging or faulting appropriately, but immediately
|
|
// after recv has reset globals (only problem is that in
|
|
// debugging globals won't look correct)
|
|
//
|
|
|
|
timeDelta = DNS_TIME() - LastUdpRecvTime;
|
|
|
|
if ( timeDelta < SrvCfg_dwQuietRecvLogInterval ||
|
|
DNS_TIME() < NextUdpLogTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Log_Printf(
|
|
"WARNING: No recv for %d seconds\r\n",
|
|
timeDelta
|
|
);
|
|
NextUdpLogTime = DNS_TIME() + SrvCfg_dwQuietRecvLogInterval;
|
|
|
|
//
|
|
// quiet a REALLY long time -- fault
|
|
//
|
|
|
|
if ( SrvCfg_dwQuietRecvFaultInterval &&
|
|
timeDelta > SrvCfg_dwQuietRecvFaultInterval )
|
|
{
|
|
DNS_DEBUG( ANY, (
|
|
"Recv quiet for longer than fault interval %d -- fault now!\n",
|
|
SrvCfg_dwQuietRecvFaultInterval
|
|
));
|
|
HARD_ASSERT( FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DNS_STATUS
|
|
Udp_CreateReceiveThreads(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Setup UDP I/O and dispatch threads.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful.
|
|
FALSE if failure.
|
|
|
|
--*/
|
|
{
|
|
PDNS_SOCKET pcontext;
|
|
DWORD countUdpThreads;
|
|
DWORD i;
|
|
SOCKET s;
|
|
HANDLE hport;
|
|
DWORD status;
|
|
|
|
//
|
|
// calculate number of worker threads to create
|
|
// - twice total processors in system (Paula Tomlison
|
|
// assuming so that with blocked threads (on send?), still
|
|
// thread to run on processor)
|
|
//
|
|
// DEVNOTE: like to set number of threads limit
|
|
// - low >= 2
|
|
// - high above say 4 processors, processors * 80% for scaling
|
|
//
|
|
|
|
countUdpThreads = g_ProcessorCount * 2;
|
|
|
|
//
|
|
// setup sockets with completion port
|
|
// then drop initial receive on each socket
|
|
//
|
|
|
|
status = Sock_StartReceiveOnUdpSockets();
|
|
if ( status != ERROR_SUCCESS )
|
|
{
|
|
ASSERT( FALSE );
|
|
// DEVNOTE: figure out what to do here, if started on some
|
|
// sockets continue
|
|
//return( FALSE );
|
|
}
|
|
|
|
//
|
|
// dispatch UDP recv() threads
|
|
//
|
|
|
|
for ( i=0; i<countUdpThreads; i++ )
|
|
{
|
|
if ( ! Thread_Create(
|
|
"UDP Listen",
|
|
Udp_RecvThread,
|
|
(PVOID) 0,
|
|
0 ) )
|
|
{
|
|
DNS_PRINT((
|
|
"ERROR: failed to create UDP recv thread %d\n",
|
|
i ));
|
|
ASSERT( FALSE );
|
|
return( ERROR_SERVICE_NO_THREAD );
|
|
}
|
|
}
|
|
|
|
return( ERROR_SUCCESS );
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
|
DNS_STATUS
|
|
Udp_StartReceiveOnSocket(
|
|
IN SOCKET Socket
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Start receive on given UDP socket.
|
|
|
|
Arguments:
|
|
|
|
Socket -- socket to start receive on.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful
|
|
Error code on failure.
|
|
|
|
--*/
|
|
{
|
|
PDNS_SOCKET pcontext;
|
|
HANDLE hport;
|
|
|
|
//
|
|
// create context for socket
|
|
// this is returned by the GetQueuedCompletionStatus() call
|
|
//
|
|
|
|
pcontext = ALLOC_TAGHEAP( sizeof(DNS_SOCKET), MEMTAG_SOCKET );
|
|
IF_NOMEM( !pcontext )
|
|
{
|
|
return( GetLastError() );
|
|
}
|
|
|
|
pcontext->Socket = Socket;
|
|
pcontext->Overlapped.Offset = 0;
|
|
pcontext->Overlapped.OffsetHigh = 0;
|
|
pcontext->Overlapped.hEvent = NULL;
|
|
|
|
hport = CreateIoCompletionPort(
|
|
(HANDLE) pcontext->Socket,
|
|
g_hUdpCompletionPort,
|
|
(DWORD) pcontext,
|
|
0 // threads matched to system processors
|
|
);
|
|
if ( !hport )
|
|
{
|
|
DNS_PRINT(( "ERROR: in CreateIoCompletionPort\n" ));
|
|
ASSERT( FALSE );
|
|
return( GetLastError() );
|
|
}
|
|
ASSERT( hport == g_hUdpCompletionPort );
|
|
|
|
DNS_DEBUG( INIT, (
|
|
"Created i/o context %p for UDP socket %d\n"
|
|
"\toverlapped ptr = %p\n",
|
|
pcontext,
|
|
pcontext->Socket,
|
|
&pcontext->Overlapped ));
|
|
|
|
Udp_DropReceive( pcontext );
|
|
|
|
return( ERROR_SUCCESS );
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
VOID
|
|
Udp_ShutdownListenThreads(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Shutdown UDP listen threads.
|
|
All threads do not necessarily terminate due to socket
|
|
closure as they are not directly associated with a socket.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful.
|
|
FALSE if failure.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hport = g_hUdpCompletionPort;
|
|
|
|
// wake up threads hung in wait
|
|
|
|
PostQueuedCompletionStatus(
|
|
g_hUdpCompletionPort,
|
|
0,
|
|
0,
|
|
NULL );
|
|
|
|
//
|
|
// if allowing UDP threads to shutdown, then have concurrency issue
|
|
// avoiding double close or NULL close
|
|
// interlocked set value?
|
|
|
|
g_hUdpCompletionPort = NULL;
|
|
if ( !hport )
|
|
{
|
|
CloseHandle( hport );
|
|
}
|
|
}
|
|
|
|
//
|
|
// End udp.c
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|