#include "mungal4.h" #pragma hdrstop #include "l4nettransport.h" #include //######################################################################## // WinsockNetTransport - the TCP wire the arcade always used, moved // verbatim out of L4NET.CPP behind the NetTransport seam. Behavior // notes preserved from the original: // - Connect retries while the remote refuses (WSAECONNREFUSED): the // console feeds eggs in [pilots] order with ACKs, so earlier pods // are listening before later pods open - the retry covers the race. // - Sockets go nonblocking once connected; listeners are nonblocking // from creation. //######################################################################## namespace { class WinsockNetTransport: public NetTransport { public: WinsockNetTransport(): started(False) { } Logical Startup() { if (started) { return True; } int result = WSAStartup(MAKEWORD(2, 2), &winsockData); if (result != NO_ERROR) { DEBUG_STREAM << "ERROR: WSAStartup() failed with " << result << "!\n" << std::flush; return False; } started = True; return True; } void Cleanup() { if (started) { WSACleanup(); started = False; } } int GetLocalAddresses( unsigned long *addresses, int max_count ) { char name[255]; PHOSTENT hostinfo; if (gethostname(name, sizeof(name)) != 0) { DEBUG_STREAM << "ERROR: gethostname() failed!" << std::endl << std::flush; return 0; } if ((hostinfo = gethostbyname(name)) == NULL) { DEBUG_STREAM << "ERROR: gethostbyname() failed!" << std::endl << std::flush; return 0; } int count = 0; for (int i = 0; hostinfo->h_addr_list[i] != NULL && count < max_count; ++i) { addresses[count++] = *((unsigned long *) hostinfo->h_addr_list[i]); } // loopback rounds out the list (single-machine testing) if (count < max_count) { addresses[count++] = 0x0100007F; } return count; } Logical Resolve( const char *host_name, SOCKADDR_IN *address ) { // numeric ip[:port] only - the egg carries addresses, not // names; port stays 0 when absent (caller applies default) int buffer_size = sizeof(SOCKADDR_IN); return WSAStringToAddressA( (LPSTR) host_name, AF_INET, NULL, (LPSOCKADDR) address, &buffer_size) == 0; } Connection Connect( const SOCKADDR_IN *remote, int local_port ) { DEBUG_STREAM << "Opening connection to " << inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_port) << "...\n" << std::flush; // // Retry-while-refused, bounded: the egg-ACK ordering means // the peer may not be listening yet. A refused TCP socket // is dead - every attempt needs a fresh one. // DWORD deadline = GetTickCount() + 120 * 1000; for (;;) { SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { DEBUG_STREAM << "ERROR: socket() failed with " << WSAGetLastError() << "!\n"; return InvalidConnection; } bool reuse_address = true; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse_address, sizeof(bool))) { DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(sock); return InvalidConnection; } // bind the game port: the mesh identifies peers by // address AND port, so the source port must be ours // (port 0 = ephemeral, for console channels) sockaddr_in local_endpoint; memset(&local_endpoint, 0, sizeof(local_endpoint)); local_endpoint.sin_family = AF_INET; local_endpoint.sin_port = htons((unsigned short) local_port); local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(sock, (sockaddr *) &local_endpoint, sizeof(local_endpoint))) { DEBUG_STREAM << "ERROR: Could not bind local socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(sock); return InvalidConnection; } if (connect(sock, (sockaddr *) remote, sizeof(SOCKADDR_IN)) == 0) { unsigned long enable = 1; if (ioctlsocket(sock, FIONBIO, &enable)) { DEBUG_STREAM << "ERROR: Could not set actively opened socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(sock); return InvalidConnection; } return (Connection) sock; } int wsa_error = WSAGetLastError(); closesocket(sock); if (wsa_error != WSAECONNREFUSED || (LONG)(GetTickCount() - deadline) >= 0) { DEBUG_STREAM << "ERROR: connect() failed with " << wsa_error << "!\n" << std::flush; return InvalidConnection; } Sleep(250); // peer not up yet - redial } } Connection Listen( int local_port, int backlog ) { DEBUG_STREAM << "Starting to listen on port " << local_port << "...\n" << std::flush; SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listener == INVALID_SOCKET) { DEBUG_STREAM << "ERROR: Could not create listener socket; socket() failed with " << WSAGetLastError() << "!\n" << std::flush; return InvalidConnection; } bool reuse_address = true; if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse_address, sizeof(bool))) { DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on listener socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(listener); return InvalidConnection; } sockaddr_in local_endpoint; memset(&local_endpoint, 0, sizeof(local_endpoint)); local_endpoint.sin_family = AF_INET; local_endpoint.sin_port = htons((unsigned short) local_port); local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(listener, (sockaddr *) &local_endpoint, sizeof(local_endpoint))) { DEBUG_STREAM << "ERROR: Could not bind listener socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(listener); return InvalidConnection; } if (listen(listener, backlog)) { DEBUG_STREAM << "ERROR: Could not listen on listener socket; listen() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(listener); return InvalidConnection; } unsigned long enable = 1; if (ioctlsocket(listener, FIONBIO, &enable)) { DEBUG_STREAM << "ERROR: Could not set listener socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush; closesocket(listener); return InvalidConnection; } return (Connection) listener; } Connection Accept(Connection listener) { SOCKET accepted = accept((SOCKET) listener, NULL, 0); if (accepted == INVALID_SOCKET) { return InvalidConnection; } return (Connection) accepted; } void Close(Connection connection) { shutdown((SOCKET) connection, SD_BOTH); closesocket((SOCKET) connection); } int Send( Connection connection, const void *data, int size ) { return send((SOCKET) connection, (const char *) data, size, 0); } int Receive( Connection connection, void *buffer, int size ) { int received = recv((SOCKET) connection, (char *) buffer, size, 0); if (received > 0) { return received; } if (received == 0) { return ReceiveDisconnected; } DWORD error = WSAGetLastError(); switch (error) { case WSAECONNRESET: // hard drop reads the same as an orderly close upstairs return ReceiveDisconnected; case WSAEWOULDBLOCK: return ReceiveNoData; default: DEBUG_STREAM << "WinsockNetTransport::Receive: recv returned an unexpected error: WSAGetLastError = " << error << std::endl << std::flush; return ReceiveNoData; } } Logical GetRemoteAddress( Connection connection, SOCKADDR_IN *address ) { int size = sizeof(SOCKADDR_IN); memset(address, 0, size); return getpeername((SOCKET) connection, (sockaddr *) address, &size) == 0; } private: Logical started; WSADATA winsockData; }; WinsockNetTransport gWinsockTransport; NetTransport *gTransport = &gWinsockTransport; } NetTransport * NetTransport_Get() { return gTransport; } void NetTransport_Set(NetTransport *transport) { gTransport = (transport != NULL) ? transport : &gWinsockTransport; }