/*++ Copyright (c) 2001-2002 Microsoft Corporation Module Name: mcast.c Abstract: DNS Resolver Service Multicast routines. Author: James Gilroy (jamesg) December 2001 Revision History: --*/ #include "local.h" // // Socket context // typedef struct _McastSocketContext { struct _SocketContext * pNext; SOCKET Socket; PDNS_MSG_BUF pMsg; BOOL fRecvDown; OVERLAPPED Overlapped; } MCSOCK_CONTEXT, *PMCSOCK_CONTEXT; // // Globals // HANDLE g_hMcastThread = NULL; DWORD g_McastThreadId = 0; BOOL g_McastStop = FALSE; BOOL g_McastConfigChange = TRUE; HANDLE g_McastCompletionPort = NULL; PMCSOCK_CONTEXT g_pMcastContext4 = NULL; PMCSOCK_CONTEXT g_pMcastContext6 = NULL; PDNS_NETINFO g_pMcastNetInfo = NULL; PWSTR g_pwsMcastHostName = NULL; // // Mcast config globals // #define MCAST_RECORD_TTL 60 // 1 minute DWORD g_McastRecordTTL = MCAST_RECORD_TTL; // // Private prototypes // VOID mcast_ProcessRecv( IN OUT PMCSOCK_CONTEXT pContext, IN DWORD BytesRecvd ); VOID mcast_FreeIoContext( IN OUT PMCSOCK_CONTEXT pContext ) /*++ Routine Description: Cleanup i/o context. Includes socket close. Arguments: pContext -- context for socket being recieved Return Value: None --*/ { DNSDBG( TRACE, ( "mcast_FreeIoContext( %p )\n", pContext )); // // cleanup context list // - close socket // - free message buffer // - free context // if ( pContext ) { Socket_Close( pContext->Socket ); MCAST_HEAP_FREE( pContext->pMsg ); MCAST_HEAP_FREE( pContext ); } } PMCSOCK_CONTEXT mcast_CreateIoContext( IN INT Family, IN PWSTR pName ) /*++ Routine Description: Create and init a i/o context for a protocol. Arguments: Family -- address family pName -- name to be published Return Value: NO_ERROR if successful. ErrorCode on failure. --*/ { DNS_STATUS status = NO_ERROR; PMCSOCK_CONTEXT pcontext = NULL; SOCKET sock = 0; DNS_ADDR addr; HANDLE hport; // // setup mcast address // status = DnsAddr_BuildMcast( &addr, Family, pName ); if ( status != NO_ERROR ) { goto Failed; } // // create multicast socket // - bound to this address and DNS port // sock = Socket_CreateMulticast( SOCK_DGRAM, &addr, MCAST_PORT_NET_ORDER, FALSE, // not send TRUE // receive ); if ( sock == 0 ) { goto Failed; } // // DCR: mcast context could include message buffer // (or even just reassign some fields in context // // allocate and clear context pcontext = MCAST_HEAP_ALLOC_ZERO( sizeof(MCSOCK_CONTEXT) ); if ( !pcontext ) { goto Failed; } // create message buffer for socket pcontext->pMsg = Dns_AllocateMsgBuf( 0 ); if ( !pcontext->pMsg ) { DNSDBG( ANY, ( "Error: Failed to allocate message buffer." )); goto Failed; } // attach to completion port hport = CreateIoCompletionPort( (HANDLE) sock, g_McastCompletionPort, (UINT_PTR) pcontext, 0 ); if ( !hport ) { DNSDBG( INIT, ( "Failed to add socket to io completion port." )); goto Failed; } // fill in context pcontext->Socket = sock; sock = 0; pcontext->pMsg->fTcp = FALSE; #if 0 // not handling contexts in list yet // insert into list InsertTailList( (PLIST_ENTRY)pcontext ); #endif return pcontext; Failed: Socket_Close( sock ); mcast_FreeIoContext( pcontext ); return NULL; } VOID mcast_DropReceive( IN OUT PMCSOCK_CONTEXT pContext ) /*++ Routine Description: Drop down UDP receive request. Arguments: pContext -- context for socket being recieved Return Value: None --*/ { DNS_STATUS status; WSABUF wsaBuf; DWORD bytesRecvd; DWORD flags = 0; INT retry = 0; DNSDBG( TRACE, ( "mcast_DropReceive( %p )\n", pContext )); if ( !pContext || !pContext->pMsg ) { DNS_ASSERT( FALSE ); return; } // // DCR: could support larger MCAST packets // wsaBuf.len = DNS_MAX_UDP_PACKET_BUFFER_LENGTH; wsaBuf.buf = (PCHAR) (&pContext->pMsg->MessageHead); pContext->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 ) { retry++; status = WSARecvFrom( pContext->Socket, & wsaBuf, 1, & bytesRecvd, & flags, & pContext->pMsg->RemoteAddress.Sockaddr, & pContext->pMsg->RemoteAddress.SockaddrLength, & pContext->Overlapped, NULL ); if ( status == ERROR_SUCCESS ) { pContext->fRecvDown = TRUE; return; } status = GetLastError(); if ( status == WSA_IO_PENDING ) { pContext->fRecvDown = TRUE; return; } // // 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 || status == WSAEMSGSIZE ) { if ( retry > 10 ) { Sleep( retry ); } continue; } return; } } VOID mcast_CreateIoContexts( VOID ) /*++ Routine Description: Create any uncreated i/o contexts. This is run dynamically -- after every mcast recv -- and creates any missing contexts, hence handling either previous failure or change in config (name change, protocol change). Arguments: None Return Value: None --*/ { PDNS_NETINFO pnetInfo; // // get current netinfo // // DCR: should have "do we need new" netinfo check // pnetInfo = GrabNetworkInfo(); if ( pnetInfo ) { NetInfo_Free( g_pMcastNetInfo ); g_pMcastNetInfo = pnetInfo; } else { pnetInfo = g_pMcastNetInfo; } // // try\retry context setup, to handle dynamic IP\name // if ( !g_pMcastContext6 || !Dns_NameCompare_W( g_pwsMcastHostName, pnetInfo->pszHostName ) ) { g_pMcastContext6 = mcast_CreateIoContext( AF_INET6, pnetInfo->pszHostName ); Dns_Free( g_pwsMcastHostName ); g_pwsMcastHostName = Dns_CreateStringCopy_W( pnetInfo->pszHostName ); } if ( !g_pMcastContext4 ) { g_pMcastContext4 = mcast_CreateIoContext( AF_INET, NULL ); } // // redrop receives if not outstanding // mcast_DropReceive( g_pMcastContext6 ); mcast_DropReceive( g_pMcastContext4 ); } VOID mcast_CleanupIoContexts( VOID ) /*++ Routine Description: Cleanup mcast i/o contexts created. Arguments: None Return Value: None --*/ { mcast_FreeIoContext( g_pMcastContext6 ); mcast_FreeIoContext( g_pMcastContext4 ); g_pMcastContext6 = NULL; g_pMcastContext4 = NULL; } VOID Mcast_Thread( VOID ) /*++ Routine Description: Mcast response thread. Runs while cache is running, responding to multicast queries. Arguments: None. Return Value: None. --*/ { DNS_STATUS status = NO_ERROR; PMCSOCK_CONTEXT pcontext; DWORD bytesRecvd; LPOVERLAPPED poverlapped; BOOL bresult; DNSDBG( MCAST, ( "Mcast_Thread() start!\n" )); // // init mcast globals // g_McastStop = FALSE; g_McastCompletionPort = NULL; // // create mcast completion port // g_McastCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 ); if ( ! g_McastCompletionPort ) { DNSDBG( ANY, ( "Error: Failed to create mcast completion port.\n" )); goto Cleanup; } // // main listen loop // while ( 1 ) { DNSDBG( MCAST, ( "Top of loop!\n" )); if ( g_McastStop ) { DNSDBG( MCAST, ( "Terminating mcast loop.\n" )); break; } // // wait // pcontext = NULL; bresult = GetQueuedCompletionStatus( g_McastCompletionPort, & bytesRecvd, & (ULONG_PTR) pcontext, & poverlapped, INFINITE ); if ( g_McastStop ) { DNSDBG( MCAST, ( "Terminating mcast loop after GQCS!\n" )); break; } if ( pcontext ) { pcontext->fRecvDown = FALSE; } if ( bresult ) { if ( pcontext && bytesRecvd ) { mcast_ProcessRecv( pcontext, bytesRecvd ); } else { status = GetLastError(); DNSDBG( ANY, ( "Mcast GQCS() success without context!!!\n" "\terror = %d\n", status )); Sleep( 100 ); continue; } } else { status = GetLastError(); DNSDBG( ANY, ( "Mcast GQCS() failed %d\n" "\terror = %d\n", status )); // Sleep( 100 ); continue; } } Cleanup: // // cleanup multicast stuff // - contexts, i/o completion port // - note thread handle is closed by main thread as it is used // for shutdown wait // mcast_CleanupIoContexts(); if ( g_McastCompletionPort ) { CloseHandle( g_McastCompletionPort ); g_McastCompletionPort = 0; } //NetInfo_Free( g_pMcastNetinfo ); DNSDBG( TRACE, ( "Mcast thread %d exit!\n\n", g_hMcastThread )); g_hMcastThread = NULL; } // // Public start\stop // DNS_STATUS Mcast_Startup( VOID ) /*++ Routine Description: Startup multicast listen. Arguments: None Return Value: None --*/ { DNS_STATUS status; HANDLE hthread; // // screen // - already started // - no listen allowed // - no IP4 listen and no IP6 // if ( g_hMcastThread ) { DNSDBG( ANY, ( "ERROR: called mcast listen with existing thread!\n" )); return ERROR_ALREADY_EXISTS; } if ( g_MulticastListenLevel == MCAST_LISTEN_OFF ) { DNSDBG( ANY, ( "No mcast listen -- mcast list disabled.\n" )); return ERROR_NOT_SUPPORTED; } if ( ! (g_MulticastListenLevel & MCAST_LISTEN_IP4) && ! Util_IsIp6Running() ) { DNSDBG( ANY, ( "No mcast listen -- no IP4 mcast listen.\n" )); return ERROR_NOT_SUPPORTED; } // // fire up IP notify thread // g_McastStop = FALSE; status = NO_ERROR; hthread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) Mcast_Thread, NULL, 0, & g_McastThreadId ); if ( !hthread ) { status = GetLastError(); DNSDBG( ANY, ( "FAILED to create IP notify thread = %d\n", status )); } g_hMcastThread = hthread; return status; } VOID Mcast_SignalShutdown( VOID ) /*++ Routine Description: Signal service shutdown to multicast thread. Arguments: None Return Value: None --*/ { g_McastStop = TRUE; // // shutdown recv // if ( g_McastCompletionPort ) { PostQueuedCompletionStatus( g_McastCompletionPort, 0, 0, NULL ); } } VOID Mcast_ShutdownWait( VOID ) /*++ Routine Description: Wait for mcast shutdown. This is for service stop routine. Arguments: None Return Value: None --*/ { HANDLE mcastThread = g_hMcastThread; if ( ! mcastThread ) { return; } // // signal shutdown and wait // // note, local copy of thread handle, as mcast thread clears // global when it exits to serve as flag // Mcast_SignalShutdown(); ThreadShutdownWait( mcastThread ); } VOID mcast_ProcessRecv( IN OUT PMCSOCK_CONTEXT pContext, IN DWORD BytesRecvd ) /*++ Routine Description: Process received packet. Arguments: pContext -- context for socket being recieved BytesRecvd -- bytes received Return Value: None --*/ { PDNS_RECORD prr = NULL; PDNS_HEADER phead; CHAR nameQuestion[ DNS_MAX_NAME_BUFFER_LENGTH + 4 ]; WORD wtype; WORD class; PDNS_MSG_BUF pmsg; PCHAR pnext; WORD nameLength; DNS_STATUS status; // // need new network info? // if ( g_McastConfigChange || ! g_pMcastNetInfo ) { NetInfo_Free( g_pMcastNetInfo ); DNSDBG( MCAST, ( "Mcast netinfo stale -- refreshing.\n" )); g_pMcastNetInfo = GrabNetworkInfo(); if ( ! g_pMcastNetInfo ) { DNSDBG( MCAST, ( "ERROR: failed to get netinfo for mcast!!!\n" )); return; } } // // check packet, extract question info // // - flip header fields // - opcode question // - good format (Question==1, no Answer or Authority sections) // - extract question // pmsg = pContext->pMsg; if ( !pmsg ) { ASSERT( FALSE ); return; } pmsg->MessageLength = (WORD)BytesRecvd; phead = &pmsg->MessageHead; DNS_BYTE_FLIP_HEADER_COUNTS( phead ); if ( phead->IsResponse || phead->Opcode != DNS_OPCODE_QUERY || phead->QuestionCount != 1 || phead->AnswerCount != 0 || phead->NameServerCount != 0 ) { DNSDBG( ANY, ( "WARNING: invalid message recv'd by mcast listen!\n" )); return; } // read question name pnext = Dns_ReadPacketName( nameQuestion, & nameLength, NULL, // no offset NULL, // no previous name (PCHAR) (phead + 1), // question name start (PCHAR) phead, // message start (PCHAR)phead + BytesRecvd // message end ); if ( !pnext ) { DNSDBG( ANY, ( "WARNING: invalid message question name recv'd by mcast!\n" )); return; } // read question wtype = InlineFlipUnalignedWord( pnext ); pnext += sizeof(WORD); class = InlineFlipUnalignedWord( pnext ); pnext += sizeof(WORD); if ( pnext < (PCHAR)phead+BytesRecvd || class != DNS_CLASS_INTERNET ) { DNSDBG( ANY, ( "WARNING: invalid message question recv'd by mcast!\n" )); return; } DNSDBG( MCAST, ( "Mcast recv msg (%p) qtype = %d, qname = %s\n", pmsg, wtype, nameQuestion )); // // check query type // // // for address\PTR types do local name check // // note: global mcast-netinfo valid, assumes this called only in single // mcast response thread // // note: mcast query match no-data issue // could have query match (name and no-ip of type) or (ip and // unconfigured hostname) that could plausibly generate no-data // response; not worrying about this as it doesn't add value; // and hard to get (no-address -- how'd packet get here) // if ( wtype == DNS_TYPE_AAAA || wtype == DNS_TYPE_A || wtype == DNS_TYPE_PTR ) { QUERY_BLOB blob; WCHAR nameBuf[ DNS_MAX_NAME_BUFFER_LENGTH ]; if ( !Dns_NameCopyWireToUnicode( nameBuf, nameQuestion ) ) { DNSDBG( ANY, ( "ERROR: invalid mcast name %s -- unable to convert to unicode!\n", nameQuestion )); return; } RtlZeroMemory( &blob, sizeof(blob) ); blob.pNameOrig = nameBuf; blob.wType = wtype; blob.Flags |= DNSP_QUERY_NO_GENERIC_NAMES; blob.pNetInfo = g_pMcastNetInfo; status = Local_GetRecordsForLocalName( &blob ); prr = blob.pRecords; if ( status == NO_ERROR && prr ) { goto Respond; } DNSDBG( MCAST, ( "Mcast name %S, no match against local data.!\n", nameBuf )); prr = blob.pRecords; goto Cleanup; } // // unsupported types // else { DNSDBG( MCAST, ( "WARNING: recv'd mcast type %d query -- currently unsupported.\n", wtype )); return; } Respond: // // write packet // - records // - set header bits status = Dns_AddRecordsToMessage( pmsg, prr, FALSE // not an update ); if ( status != NO_ERROR ) { DNSDBG( MCAST, ( "Failed mcast packet write!!!\n" "\tstatus = %d\n", status )); goto Cleanup; } pmsg->MessageHead.IsResponse = TRUE; pmsg->MessageHead.Authoritative = TRUE; // // send -- unicast back to client // // DCR: mcast OPT, could consider assuming all mcast // are OPT aware (WinCE?) // Socket_ClearMessageSockets( pmsg ); status = Send_MessagePrivate( pmsg, & pmsg->RemoteAddress, TRUE // no OPT ); DNSDBG( MCAST, ( "Sent mcast response, status = %d\n", status )); Cleanup: Dns_RecordListFree( prr ); return; } // // End mcast.c //