/*++ Copyright (c) 1997 Microsoft Corporation Module Name: network.c Abstract: This module contains the network interface for the BINL server. Author: Colin Watson (colinw) 2-May-1997 Environment: User Mode - Win32 Revision History: --*/ #include "binl.h" #pragma hdrstop DWORD BinlWaitForMessage( BINL_REQUEST_CONTEXT *pRequestContext ) /*++ Routine Description: This function waits for a request on the BINL port on any of the configured interfaces. Arguments: RequestContext - A pointer to a request context block for this request. Return Value: The status of the operation. --*/ { DWORD length; DWORD error; fd_set readSocketSet; DWORD i; int readySockets; struct timeval timeout = { 0x7FFFFFFF, 0 }; // forever. LPOPTION Option; LPBYTE EndOfScan; LPBYTE MagicCookie; BOOLEAN informPacket; #define CLIENTOPTIONSTRING "PXEClient" #define CLIENTOPTIONSIZE (sizeof(CLIENTOPTIONSTRING) - 1) // // Loop until we get an extended DHCP request or an error // while (1) { // // Setup the file descriptor set for select. // FD_ZERO( &readSocketSet ); for ( i = 0; i < BinlGlobalNumberOfNets ; i++ ) { if (BinlGlobalEndpointList[i].Socket) { FD_SET( BinlGlobalEndpointList[i].Socket, &readSocketSet ); } } readySockets = select( 0, &readSocketSet, NULL, NULL, &timeout ); // // return to caller when the service is shutting down or select() // times out. // if( (readySockets == 0) || (WaitForSingleObject( BinlGlobalProcessTerminationEvent, 0 ) == 0) ) { return( ERROR_SEM_TIMEOUT ); } if( readySockets == SOCKET_ERROR) { continue; // Closed the DHCP socket? } // // Time to play 20 question with winsock. Which socket is ready? // pRequestContext->ActiveEndpoint = NULL; for ( i = 0; i < BinlGlobalNumberOfNets ; i++ ) { if ( FD_ISSET( BinlGlobalEndpointList[i].Socket, &readSocketSet ) ) { pRequestContext->ActiveEndpoint = &BinlGlobalEndpointList[i]; break; } } //BinlAssert(pRequestContext->ActiveEndpoint != NULL ); if ( pRequestContext->ActiveEndpoint == NULL ) { return ERROR_SEM_TIMEOUT; } // // Read data from the net. If multiple sockets have data, just // process the first available socket. // pRequestContext->SourceNameLength = sizeof( struct sockaddr ); // // clean the receive buffer before receiving data in it. We clear // out one more byte than we actually hand to recvfrom, so we can // be sure the message has a NULL after it (in case we do a // wcslen etc. into the received packet). // RtlZeroMemory( pRequestContext->ReceiveBuffer, DHCP_MESSAGE_SIZE + 1 ); pRequestContext->ReceiveMessageSize = DHCP_MESSAGE_SIZE; length = recvfrom( pRequestContext->ActiveEndpoint->Socket, (char *)pRequestContext->ReceiveBuffer, pRequestContext->ReceiveMessageSize, 0, &pRequestContext->SourceName, (int *)&pRequestContext->SourceNameLength ); if ( length == SOCKET_ERROR ) { error = WSAGetLastError(); BinlPrintDbg(( DEBUG_ERRORS, "Recv failed, error = %ld\n", error )); } else { // // Ignore all messages that do not look like DHCP or doesn't have the // option "PXEClient", OR that is not an oschooser message (they // all start with 0x81). // if ( ((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Operation == OSC_REQUEST) { // // All OSC request packets have a 4-byte signature (first byte // is OSC_REQUEST) followed by a DWORD length (that does not // include the signature/length). Make sure the length matches // what we got from recvfrom (we allow padding at the end). We // use SIGNED_PACKET but any of the XXX_PACKET structures in // oscpkt.h would work. // if (length < FIELD_OFFSET(SIGNED_PACKET, SequenceNumber)) { BinlPrintDbg(( DEBUG_OSC_ERROR, "Discarding runt packet %d bytes\n", length )); continue; } if ((length - FIELD_OFFSET(SIGNED_PACKET, SequenceNumber)) < ((SIGNED_PACKET UNALIGNED *)pRequestContext->ReceiveBuffer)->Length) { BinlPrintDbg(( DEBUG_OSC_ERROR, "Discarding invalid length message %d bytes (header said %d)\n", length, ((SIGNED_PACKET UNALIGNED *)pRequestContext->ReceiveBuffer)->Length)); continue; } BinlPrintDbg(( DEBUG_MESSAGE, "Received OSC message\n", 0 )); error = ERROR_SUCCESS; } else { if ( length < FIELD_OFFSET(DHCP_MESSAGE, Option) + 4 ) { // // Message isn't long enough to include the magic cookie, ignore it. // continue; } if ( ((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Operation != BOOT_REQUEST) { continue; // Doesn't look like an interesting DHCP frame } // Stop scanning when there isn't room for a ClientOption, including // the type, length, and the CLIENTOPTIONSTRING. EndOfScan = pRequestContext->ReceiveBuffer + pRequestContext->ReceiveMessageSize - (FIELD_OFFSET(OPTION, OptionValue[0]) + CLIENTOPTIONSIZE); // // check magic cookie. // MagicCookie = (LPBYTE)&((LPDHCP_MESSAGE)pRequestContext->ReceiveBuffer)->Option; if( (*MagicCookie != (BYTE)DHCP_MAGIC_COOKIE_BYTE1) || (*(MagicCookie+1) != (BYTE)DHCP_MAGIC_COOKIE_BYTE2) || (*(MagicCookie+2) != (BYTE)DHCP_MAGIC_COOKIE_BYTE3) || (*(MagicCookie+3) != (BYTE)DHCP_MAGIC_COOKIE_BYTE4)) { continue; // this is a vendor specific magic cookie. } Option = (LPOPTION) (MagicCookie + 4); informPacket = FALSE; while (((LPBYTE)Option <= EndOfScan) && ((Option->OptionType != OPTION_CLIENT_CLASS_INFO) || (Option->OptionLength < CLIENTOPTIONSIZE) || (memcmp(Option->OptionValue, CLIENTOPTIONSTRING, CLIENTOPTIONSIZE) != 0))) { if ( Option->OptionType == OPTION_END ){ break; } else if ( Option->OptionType == OPTION_PAD ){ Option = (LPOPTION)( (LPBYTE)(Option) + 1); } else { if (( Option->OptionType == OPTION_MESSAGE_TYPE ) && ( Option->OptionLength == 1 ) && ( Option->OptionValue[0] == DHCP_INFORM_MESSAGE )) { informPacket = TRUE; } Option = (LPOPTION)( (LPBYTE)(Option) + Option->OptionLength + 2); } } if ((((LPBYTE)Option > EndOfScan) || (Option->OptionType == OPTION_END)) && (informPacket == FALSE)) { continue; // Not an extended DHCP packet so ignore it } BinlPrintDbg(( DEBUG_MESSAGE, "Received message\n", 0 )); error = ERROR_SUCCESS; } } pRequestContext->ReceiveMessageSize = length; return( error ); } } DWORD BinlSendMessage( LPBINL_REQUEST_CONTEXT RequestContext ) /*++ Routine Description: This function send a response to a BINL client. Arguments: RequestContext - A pointer to the BinlRequestContext block for this request. Return Value: The status of the operation. --*/ { DWORD error; struct sockaddr_in *source; LPDHCP_MESSAGE binlMessage; LPDHCP_MESSAGE binlReceivedMessage; DWORD MessageLength; BOOL ArpCacheUpdated = FALSE; binlMessage = (LPDHCP_MESSAGE) RequestContext->SendBuffer; binlReceivedMessage = (LPDHCP_MESSAGE) RequestContext->ReceiveBuffer; // // if the request arrived from a relay agent, then send the reply // on server port otherwise leave it as the client's source port. // source = (struct sockaddr_in *)&RequestContext->SourceName; if ( binlReceivedMessage->RelayAgentIpAddress != 0 ) { source->sin_port = htons( DHCP_SERVR_PORT ); } // // if this request arrived from relay agent then send the // response to the address the relay agent says. // if ( binlReceivedMessage->RelayAgentIpAddress != 0 ) { source->sin_addr.s_addr = binlReceivedMessage->RelayAgentIpAddress; } else { // // if the client didnt specify broadcast bit and if // we know the ipaddress of the client then send unicast. // // // But if IgnoreBroadcastFlag is set in the registry and // if the client requested to broadcast or the server is // nacking or If the client doesn't have an address yet, // respond via broadcast. // Note that IgnoreBroadcastFlag is off by default. But it // can be set as a workaround for the clients that are not // capable of receiving unicast // and they also dont set the broadcast bit. // if ( (RequestContext->MessageType == DHCP_INFORM_MESSAGE) && (ntohs(binlMessage->Reserved) & DHCP_BROADCAST) ) { source->sin_addr.s_addr = (DWORD)-1; } else if ( BinlGlobalIgnoreBroadcastFlag ) { if ((ntohs(binlReceivedMessage->Reserved) & DHCP_BROADCAST) || (binlReceivedMessage->ClientIpAddress == 0) || (source->sin_addr.s_addr == 0) ) { source->sin_addr.s_addr = (DWORD)-1; binlMessage->Reserved = 0; // this flag should be zero in the local response. } } else { if( (ntohs(binlReceivedMessage->Reserved) & DHCP_BROADCAST) || (!source->sin_addr.s_addr ) ){ source->sin_addr.s_addr = (DWORD)-1; binlMessage->Reserved = 0; // this flag should be zero in the local response. } else { // // Send back to the same IP address that the request came in on ( // i.e. source->sin_addr.s_addr) // } } } BinlPrint(( DEBUG_STOC, "Sending response to = %s, XID = %lx.\n", inet_ntoa(source->sin_addr), binlMessage->TransactionID)); // // send minimum DHCP_MIN_SEND_RECV_PK_SIZE (300) bytes, otherwise // bootp relay agents don't like the packet. // MessageLength = (RequestContext->SendMessageSize > DHCP_MIN_SEND_RECV_PK_SIZE) ? RequestContext->SendMessageSize : DHCP_MIN_SEND_RECV_PK_SIZE; error = sendto( RequestContext->ActiveEndpoint->Socket, (char *)RequestContext->SendBuffer, MessageLength, 0, &RequestContext->SourceName, RequestContext->SourceNameLength ); if ( error == SOCKET_ERROR ) { error = WSAGetLastError(); BinlPrintDbg(( DEBUG_ERRORS, "Send failed, error = %ld\n", error )); } else { error = ERROR_SUCCESS; } return( error ); } NTSTATUS GetIpAddressInfo ( ULONG Delay ) { PDNS_ADDRESS_INFO pAddressInfo = NULL; ULONG count; // // We can get out ahead of the dns cached info here... let's delay a bit // if the pnp logic told us there was a change. // if (Delay) { Sleep( Delay ); } count = DnsGetIpAddressInfoList( &pAddressInfo ); if (count == 0) { // // we don't know what went wrong, we'll fall back to old APIs. // DHCP_IP_ADDRESS ipaddr = 0; PHOSTENT Host = gethostbyname( NULL ); if (Host) { ipaddr = *(PDHCP_IP_ADDRESS)Host->h_addr; if ((Host->h_addr_list[0] != NULL) && (Host->h_addr_list[1] != NULL)) { BinlIsMultihomed = TRUE; } else { BinlIsMultihomed = FALSE; } BinlGlobalMyIpAddress = ipaddr; } else { // // what's with the ip stack? we can't get any type of address // info out of it... for now, we won't answer any if we don't // already have the info we need. // if (BinlDnsAddressInfo == NULL) { BinlIsMultihomed = TRUE; } } return STATUS_SUCCESS; } EnterCriticalSection(&gcsParameters); if (BinlDnsAddressInfo) { LocalFree( BinlDnsAddressInfo ); } BinlDnsAddressInfo = pAddressInfo; BinlDnsAddressInfoCount = count; BinlIsMultihomed = (count != 1); if (!BinlIsMultihomed) { BinlGlobalMyIpAddress = pAddressInfo->ipAddress; } LeaveCriticalSection(&gcsParameters); return STATUS_SUCCESS; } DHCP_IP_ADDRESS BinlGetMyNetworkAddress ( LPBINL_REQUEST_CONTEXT RequestContext ) { ULONG RemoteIp; DHCP_IP_ADDRESS ipaddr; ULONG i; ULONG subnetMask; ULONG localAddr; BinlAssert( RequestContext != NULL); // // If we're not multihomed, then we know the address since there's just one. // if (!BinlIsMultihomed) { return BinlGlobalMyIpAddress; } RemoteIp = ((struct sockaddr_in *)&RequestContext->SourceName)->sin_addr.s_addr; if (RemoteIp == 0) { // // If we're multihomed and the client doesn't yet have an IP address, // then we return 0, because we don't know which of our addresses to // use to talk to the client. // return 0; } EnterCriticalSection(&gcsParameters); if (BinlDnsAddressInfo == NULL) { LeaveCriticalSection(&gcsParameters); return (BinlIsMultihomed ? 0 : BinlGlobalMyIpAddress); } ipaddr = 0; for (i = 0; i < BinlDnsAddressInfoCount; i++) { localAddr = BinlDnsAddressInfo[i].ipAddress; subnetMask = BinlDnsAddressInfo[i].subnetMask; // // check that the remote ip address may have come from this subnet. // note that the address could be the address of a dhcp relay agent, // which is fine since we're just looking for the address of the // local subnet to broadcast the response on. // if ((RemoteIp & subnetMask) == (localAddr & subnetMask)) { ipaddr = localAddr; break; } } LeaveCriticalSection(&gcsParameters); return ipaddr; } VOID FreeIpAddressInfo ( VOID ) { EnterCriticalSection(&gcsParameters); if (BinlDnsAddressInfo != NULL) { LocalFree( BinlDnsAddressInfo ); } BinlDnsAddressInfo = NULL; BinlDnsAddressInfoCount = 0; LeaveCriticalSection(&gcsParameters); return; }