|
|
/*++
Copyright (c) 1996-2000 Microsoft Corporation
Module Name:
socket.c
Abstract:
Domain Name System (DNS) API
Socket setup.
Author:
Jim Gilroy (jamesg) October, 1996
Revision History:
--*/
#include "local.h"
//
// Winsock startup
//
LONG WinsockStartCount = 0; WSADATA WsaData;
//
// Async i/o
//
// If want async socket i/o then can create single async socket, with
// corresponding event and always use it. Requires winsock 2.2
//
SOCKET DnsSocket = 0;
OVERLAPPED DnsSocketOverlapped; HANDLE hDnsSocketEvent = NULL;
//
// App shutdown flag
//
BOOLEAN fApplicationShutdown = FALSE;
DNS_STATUS Dns_InitializeWinsock( VOID ) /*++
Routine Description:
Initialize winsock for this process.
Currently, assuming process must do WSAStartup() before calling any Dns_Api entry point.
Arguments:
None
Return Value:
ERROR_SUCCESS if successful. ErrorCode on failure.
--*/ { DNSDBG( SOCKET, ( "Dns_InitializeWinsock()\n" ));
//
// start winsock, if not already started
//
if ( WinsockStartCount == 0 ) { DNS_STATUS status;
DNSDBG( TRACE, ( "Dns_InitializeWinsock() version %x\n", DNS_WINSOCK_VERSION ));
status = WSAStartup( DNS_WINSOCK_VERSION, &WsaData ); if ( status != ERROR_SUCCESS ) { DNS_PRINT(( "ERROR: WSAStartup failure %d.\n", status )); return( status ); }
DNSDBG( TRACE, ( "Winsock initialized => wHighVersion=0x%x, wVersion=0x%x\n", WsaData.wHighVersion, WsaData.wVersion ));
InterlockedIncrement( &WinsockStartCount ); } return( ERROR_SUCCESS ); }
DNS_STATUS Dns_InitializeWinsockEx( IN BOOL fForce ) /*++
Routine Description:
Dummy -- just in case called somewhere
DCR: eliminate Dns_InitializeWinsockEx()
--*/ { return Dns_InitializeWinsock(); }
VOID Dns_CleanupWinsock( VOID ) /*++
Routine Description:
Cleanup winsock if it was initialized by dnsapi.dll
Arguments:
None.
Return Value:
None.
--*/ { DNSDBG( SOCKET, ( "Dns_CleanupWinsock()\n" ));
//
// WSACleanup() for value of ref count
// - ref count pushed down to one below real value, but
// fixed up at end
// - note: the GUI_MODE_SETUP_WS_CLEANUP deal means that
// we can be called other than process detach, making
// interlock necessary
//
while ( InterlockedDecrement( &WinsockStartCount ) >= 0 ) { WSACleanup(); }
InterlockedIncrement( &WinsockStartCount ); }
SOCKET Dns_CreateSocketEx( IN INT Family, IN INT SockType, IN IP_ADDRESS IpAddress, IN USHORT Port, IN DWORD dwFlags ) /*++
Routine Description:
Create socket.
Arguments:
Family -- socket family AF_INET or AF_INET6
SockType -- SOCK_DGRAM or SOCK_STREAM
IpAddress -- IP address to listen on (net byte order)
DCR: will need to change to IP6_ADDRESS then use MAPPED to extract
Port -- desired port in net order - NET_ORDER_DNS_PORT for DNS listen sockets - 0 for any port
dwFlags -- specifiy the attributes of the sockets
Return Value:
Socket if successful. Otherwise INVALID_SOCKET.
--*/ { SOCKET s; INT err; INT val; DNS_STATUS status; BOOL fretry = FALSE;
//
// create socket
// - try again if winsock not initialized
while( 1 ) { s = WSASocket( Family, SockType, 0, NULL, 0, dwFlags ); if ( s != INVALID_SOCKET ) { break; }
status = GetLastError();
DNSDBG( SOCKET, ( "ERROR: Failed to open socket of type %d.\n" "\terror = %d.\n", SockType, status ));
if ( status != WSANOTINITIALISED || fretry ) { SetLastError( DNS_ERROR_NO_TCPIP ); return INVALID_SOCKET; }
//
// initialize Winsock if not already started
//
// note: do NOT automatically initialize winsock
// init jacks ref count and will break applications
// which use WSACleanup to close outstanding sockets;
// we'll init only when the choice is that or no service;
// apps can still cleanup with WSACleanup() called
// in loop until WSANOTINITIALISED failure
//
fretry = TRUE; status = Dns_InitializeWinsock(); if ( status != NO_ERROR ) { SetLastError( DNS_ERROR_NO_TCPIP ); return INVALID_SOCKET; } }
//
// bind socket
// - only if specific port given, this keeps remote winsock
// from grabbing it if we are on the local net
//
if ( IpAddress || Port ) { SOCKADDR_IN6 sockaddr; INT sockaddrLength;
if ( Family == AF_INET ) { PSOCKADDR_IN psin = (PSOCKADDR_IN) &sockaddr;
sockaddrLength = sizeof(*psin); RtlZeroMemory( psin, sockaddrLength );
psin->sin_family = AF_INET; psin->sin_port = Port; psin->sin_addr.s_addr = IpAddress; } else { PSOCKADDR_IN6 psin = (PSOCKADDR_IN6) &sockaddr;
DNS_ASSERT( Family == AF_INET6 );
sockaddrLength = sizeof(*psin); RtlZeroMemory( psin, sockaddrLength );
psin->sin6_family = AF_INET6; psin->sin6_port = Port; //psin->sin6_addr = IpAddress;
}
if ( Port > 0 ) { // allow us to bind to a port that someone else is already listening on
val = 1; setsockopt( s, SOL_SOCKET, SO_REUSEADDR, (const char *)&val, sizeof(val) ); }
err = bind( s, (PSOCKADDR) &sockaddr, sockaddrLength );
if ( err == SOCKET_ERROR ) { DNSDBG( SOCKET, ( "Failed to bind() socket %d, to port %d, address %s.\n" "\terror = %d.\n", s, ntohs(Port), IP_STRING( IpAddress ), GetLastError() ));
closesocket( s ); SetLastError( DNS_ERROR_NO_TCPIP ); return INVALID_SOCKET; } }
DNSDBG( SOCKET, ( "Created socket %d, of type %d, for address %s, port %d.\n", s, SockType, inet_ntoa( *(struct in_addr *) &IpAddress ), ntohs(Port) ));
return s; }
SOCKET Dns_CreateSocket( IN INT SockType, IN IP_ADDRESS IpAddress, IN USHORT Port ) /*++
Routine Description:
Wrapper function for CreateSocketAux. Passes in 0 for dwFlags (as opposed to Dns_CreateMulticastSocket, which passes in flags to specify that the socket is to be used for multicasting).
Arguments:
SockType -- SOCK_DGRAM or SOCK_STREAM
IpAddress -- IP address to listen on (net byte order)
Port -- desired port in net order - NET_ORDER_DNS_PORT for DNS listen sockets - 0 for any port
Return Value:
socket if successful. Otherwise INVALID_SOCKET.
--*/ { return Dns_CreateSocketEx( AF_INET, SockType, IpAddress, Port, 0 // no flags
); }
SOCKET Dns_CreateMulticastSocket( IN INT SockType, IN IP_ADDRESS IpAddress, IN USHORT Port, IN BOOL fSend, IN BOOL fReceive ) /*++
Routine Description:
Create socket and join it to the multicast DNS address.
Arguments:
SockType -- SOCK_DGRAM or SOCK_STREAM
IpAddress -- IP address to listen on (net byte order)
Port -- desired port in net order - NET_ORDER_DNS_PORT for DNS listen sockets - 0 for any port
Return Value:
socket if successful. Otherwise INVALID_SOCKET.
--*/ { SOCKADDR_IN multicastAddress; DWORD byteCount; BOOL bflag; SOCKET s; INT err; s = Dns_CreateSocketEx( AF_INET, SockType, IpAddress, Port, WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF | WSA_FLAG_OVERLAPPED );
if ( s != INVALID_SOCKET ) { multicastAddress.sin_family = PF_INET; multicastAddress.sin_addr.s_addr = MULTICAST_DNS_RADDR; multicastAddress.sin_port = Port;
// set loopback
bflag = TRUE;
err = WSAIoctl( s, SIO_MULTIPOINT_LOOPBACK, // loopback iotcl
& bflag, // turn on
sizeof(bflag), NULL, // no output
0, // no output size
&byteCount, // bytes returned
NULL, // no overlapped
NULL // no completion routine
);
if ( err == SOCKET_ERROR ) { DNSDBG( SOCKET, ( "Unable to turn multicast loopback on for socket %d; error = %d.\n", s, GetLastError() )); }
//
// join socket to multicast group
//
s = WSAJoinLeaf( s, (PSOCKADDR)&multicastAddress, sizeof (multicastAddress), NULL, // caller data buffer
NULL, // callee data buffer
NULL, // socket QOS setting
NULL, // socket group QOS
((fSend && fReceive) ? JL_BOTH : // send and/or receive
(fSend ? JL_SENDER_ONLY : JL_RECEIVER_ONLY)) ); if ( s == INVALID_SOCKET ) { DNSDBG( SOCKET, ( "Unable to join socket %d to multicast address, error = %d.\n", s, GetLastError() )); } } return s; }
VOID Dns_CloseSocket( IN SOCKET Socket ) /*++
Routine Description:
Close DNS socket.
Arguments:
Socket -- socket to close
Return Value:
None.
--*/ { if ( Socket == 0 || Socket == INVALID_SOCKET ) { DNS_PRINT(( "WARNING: Dns_CloseSocket() called on invalid socket %d.\n", Socket )); return; }
DNSDBG( SOCKET, ( "closesocket( %d )\n", Socket ));
closesocket( Socket ); }
VOID Dns_CloseConnection( IN SOCKET Socket ) /*++
Routine Description:
Close connection on socket.
Arguments:
Socket -- socket to close
Return Value:
None.
--*/ { if ( Socket == 0 || Socket == INVALID_SOCKET ) { DNS_PRINT(( "WARNING: Dns_CloseTcpConnection() called on invalid socket.\n" )); //DNS_ASSERT( FALSE );
return; }
// shutdown connection, then close
DNSDBG( SOCKET, ( "shutdown and closesocket( %d )\n", Socket ));
shutdown( Socket, 2 ); closesocket( Socket ); }
#if 0
//
// Global async socket routines
//
DNS_STATUS Dns_SetupGlobalAsyncSocket( VOID ) /*++
Routine Description:
Create global async UDP socket.
Arguments:
SockType -- SOCK_DGRAM or SOCK_STREAM
IpAddress -- IP address to listen on (net byte order)
Port -- desired port in net order - NET_ORDER_DNS_PORT for DNS listen sockets - 0 for any port
Return Value:
socket if successful. Otherwise INVALID_SOCKET.
--*/ { DNS_STATUS status; INT err; SOCKADDR_IN sockaddrIn;
//
// start winsock, need winsock 2 for async
//
if ( ! fWinsockStarted ) { status = WSAStartup( DNS_WINSOCK_VERSION, &WsaData ); if ( status != ERROR_SUCCESS ) { DNS_PRINT(( "ERROR: WSAStartup failure %d.\n", status )); return( status ); } if ( WsaData.wVersion != DNS_WINSOCK2_VERSION ) { WSACleanup(); return( WSAVERNOTSUPPORTED ); } fWinsockStarted = TRUE; }
//
// setup socket
// - overlapped i\o with event so can run asynchronously in
// this thread and wait with queuing event
//
DnsSocket = WSASocket( AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); if ( DnsSocket == INVALID_SOCKET ) { status = GetLastError(); DNS_PRINT(( "\nERROR: Async socket create failed.\n" )); goto Error; }
//
// bind socket
//
RtlZeroMemory( &sockaddrIn, sizeof(sockaddrIn) ); sockaddrIn.sin_family = AF_INET; sockaddrIn.sin_port = 0; sockaddrIn.sin_addr.s_addr = INADDR_ANY;
err = bind( DnsSocket, (PSOCKADDR)&sockaddrIn, sizeof(sockaddrIn) ); if ( err == SOCKET_ERROR ) { status = GetLastError(); DNSDBG( SOCKET, ( "Failed to bind() DnsSocket %d.\n" "\terror = %d.\n", DnsSocket, status )); goto Error; }
//
// create event to signal on async i/o completion
//
hDnsSocketEvent = CreateEvent( NULL, // Security Attributes
TRUE, // create Manual-Reset event
FALSE, // start unsignalled -- paused
NULL // event name
); if ( !hDnsSocketEvent ) { status = GetLastError(); DNS_PRINT(( "Failed event creation\n" )); goto Error; } DnsSocketOverlapped.hEvent = hDnsSocketEvent;
DNSDBG( SOCKET, ( "Created global async UDP socket %d.\n" "\toverlapped at %p\n" "\tevent handle %p\n", DnsSocket, DnsSocketOverlapped, hDnsSocketEvent ));
return ERROR_SUCCESS;
Error:
DNS_PRINT(( "ERROR: Failed async socket creation, status = %d\n", status )); closesocket( DnsSocket ); DnsSocket = INVALID_SOCKET; WSACleanup(); return( status ); }
#endif
//
// Socket caching
//
// Doing limited caching of UDP unbound sockets used for standard
// DNS lookups in resolver. This allows us to prevent denial of
// service attack by using up all ports on the machine.
// Resolver is the main customer for this, but we'll code it to
// be useable by any process.
//
// Implementation notes:
//
// There are a couple specific goals to this implementation:
// - Minimal code impact; Try NOT to change the resolver
// code.
// - Usage driven caching; Don't want to create on startup
// "cache sockets" that we don't use; Instead have actual usage
// drive up the cached socket count.
//
// There are several approaches here.
//
// 1) explicit resolver cache -- passed down sockets
//
// 2) add caching seamlessly into socket open and close
// this was my first choice, but the problem here is that on
// close we must either do additional calls to winsock to determine
// whether cachable (UDP-unbound) socket OR cache must include some
// sort of "in-use" tag and we trust that socket is never closed
// outside of path (otherwise handle reuse could mess us up)
//
// 3) new UDP-unbound open\close function
// this essentially puts the "i-know-i'm-using-UDP-unbound-sockets"
// burden on the caller who must switch to this new API;
// fortunately this meshes well with our "SendAndRecvUdp()" function;
// this approach still allows a caller driven ramp up we desire,
// so i'm using this approach
//
//
// Keep array of sockets
//
// Dev note: the Array and MaxCount MUST be kept in sync, no
// independent check of array is done, it is assumed to exist when
// MaxCount is non-zero, so they MUST be in sync when lock released
//
SOCKET * g_pCacheSocketArray = NULL;
DWORD g_CacheSocketMaxCount = 0; DWORD g_CacheSocketCount = 0;
// Hard limit on what we'll allow folks to keep awake
#define MAX_SOCKET_CACHE_LIMIT (100)
// Lock access with generic lock
// This is very short\fast CS, contention will be minimal
#define LOCK_SOCKET_CACHE() LOCK_GENERAL()
#define UNLOCK_SOCKET_CACHE() UNLOCK_GENERAL()
DNS_STATUS Dns_CacheSocketInit( IN DWORD MaxSocketCount ) /*++
Routine Description:
Initialize socket caching.
Arguments:
MaxSocketCount -- max count of sockets to cache
Return Value:
ERROR_SUCCESS if successful. DNS_ERROR_NO_MEMORY on alloc failure. ERROR_INVALID_PARAMETER if already initialized or bogus count.
--*/ { SOCKET * parray; DNS_STATUS status = NO_ERROR;
DNSDBG( SOCKET, ( "Dns_CacheSocketInit()\n" ));
//
// validity check
// - note, one byte of the apple, we don't let you raise
// count, though we later could; i see this as at most a
// "configure for machine use" kind of deal
//
LOCK_SOCKET_CACHE();
if ( MaxSocketCount == 0 || g_CacheSocketMaxCount != 0 ) { status = ERROR_INVALID_PARAMETER; goto Done; }
//
// allocate
//
if ( MaxSocketCount > MAX_SOCKET_CACHE_LIMIT ) { MaxSocketCount = MAX_SOCKET_CACHE_LIMIT; }
parray = (SOCKET *) ALLOCATE_HEAP_ZERO( sizeof(SOCKET) * MaxSocketCount ); if ( !parray ) { status = DNS_ERROR_NO_MEMORY; goto Done; }
// set globals
g_pCacheSocketArray = parray; g_CacheSocketMaxCount = MaxSocketCount; g_CacheSocketCount = 0;
Done:
UNLOCK_SOCKET_CACHE();
return status; }
VOID Dns_CacheSocketCleanup( VOID ) /*++
Routine Description:
Cleanup socket caching.
Arguments:
None
Return Value:
None
--*/ { DWORD i; SOCKET sock;
DNSDBG( SOCKET, ( "Dns_CacheSocketCleanup()\n" ));
//
// close cached sockets
//
LOCK_SOCKET_CACHE();
for ( i=0; i<g_CacheSocketMaxCount; i++ ) { sock = g_pCacheSocketArray[i]; if ( sock ) { Dns_CloseSocket( sock ); g_CacheSocketCount--; } }
DNS_ASSERT( g_CacheSocketCount == 0 );
// dump socket array memory
FREE_HEAP( g_pCacheSocketArray );
// clear globals
g_pCacheSocketArray = NULL; g_CacheSocketMaxCount = 0; g_CacheSocketCount = 0;
UNLOCK_SOCKET_CACHE(); }
SOCKET Dns_GetUdpSocket( VOID ) /*++
Routine Description:
Get a cached socket.
Arguments:
None
Return Value:
Socket handle if successful. Zero if no cached socket available.
--*/ { SOCKET sock; DWORD i;
//
// quick return if nothing available
// - do outside lock so function can be called cheaply
// without other checks
//
if ( g_CacheSocketCount == 0 ) { goto Open; }
//
// get a cached socket
//
LOCK_SOCKET_CACHE();
for ( i=0; i<g_CacheSocketMaxCount; i++ ) { sock = g_pCacheSocketArray[i]; if ( sock != 0 ) { g_pCacheSocketArray[i] = 0; g_CacheSocketCount--; UNLOCK_SOCKET_CACHE();
//
// DCR: clean out any data on cached socket
// it would be cool to cheaply dump useless data
//
// right now we just let XID match, then question match
// dump data on recv
//
DNSDBG( SOCKET, ( "Returning cached socket %d.\n", sock )); return sock; } }
UNLOCK_SOCKET_CACHE();
Open:
sock = Dns_CreateSocket( SOCK_DGRAM, INADDR_ANY, 0 ); return sock; }
VOID Dns_ReturnUdpSocket( IN SOCKET Socket ) /*++
Routine Description:
Return UDP socket for possible caching.
Arguments:
Socket -- socket handle
Return Value:
None
--*/ { SOCKET sock; DWORD i;
//
// quick return if not caching
// - do outside lock so function can be called cheaply
// without other checks
//
if ( g_CacheSocketMaxCount == 0 || g_CacheSocketMaxCount == g_CacheSocketCount ) { goto Close; }
//
// return cached socket
//
LOCK_SOCKET_CACHE();
for ( i=0; i<g_CacheSocketMaxCount; i++ ) { if ( g_pCacheSocketArray[i] ) { continue; } g_pCacheSocketArray[i] = Socket; g_CacheSocketCount++; UNLOCK_SOCKET_CACHE();
DNSDBG( SOCKET, ( "Returned socket %d to cache.\n", Socket )); return; }
UNLOCK_SOCKET_CACHE();
DNSDBG( SOCKET, ( "Socket cache full, closing socket %d.\n" "WARNING: should only see this message on race!\n", Socket ));
Close:
Dns_CloseSocket( Socket ); }
//
// End socket.c
//
|