windows-nt/Source/XPSP1/NT/net/dhcp/server/binl/network.c
2020-09-26 16:20:57 +08:00

561 lines
16 KiB
C

/*++
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;
}