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