//----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- // Touch-ups by William Taysom #include "PlatformMacCarb/platformMacCarb.h" #include "platform/platform.h" #include "platform/event.h" #include "platform/platformNetAsync.h" #include #include #include #include #include #include #include #include // Header clean-up by William Taysom #include // IPX fixes from William Taysom. #define IPX_NODE_LEN 6 // for 10.2 compatability... #ifndef socklen_t #define socklen_t unsigned int #endif struct sockaddr_ipx { sa_family_t sipx_family; U16 sipx_port; U32 sipx_network; unsigned char sipx_node[IPX_NODE_LEN]; U8 sipx_type; unsigned char sipx_zero; /* 16 byte fill */ }; // end wtaysom changes (May 26, 2004) #include #include "console/console.h" #include "platform/gameInterface.h" #include "core/fileStream.h" #include "core/tVector.h" static Net::Error getLastError(); static S32 defaultPort = 28000; static S32 netPort = 0; static int ipxSocket = InvalidSocket; static int udpSocket = InvalidSocket; // local enum for socket states for polled sockets enum SocketState { InvalidState, Connected, ConnectionPending, Listening, NameLookupRequired }; // the Socket structure helps us keep track of the // above states struct Socket { Socket() { fd = InvalidSocket; state = InvalidState; remoteAddr[0] = 0; remotePort = -1; } NetSocket fd; S32 state; char remoteAddr[256]; S32 remotePort; }; // list of polled sockets static Vector gPolledSockets; static Socket* addPolledSocket(NetSocket& fd, S32 state, char* remoteAddr = NULL, S32 port = -1) { Socket* sock = new Socket(); sock->fd = fd; sock->state = state; if (remoteAddr) dStrcpy(sock->remoteAddr, remoteAddr); if (port != -1) sock->remotePort = port; gPolledSockets.push_back(sock); return sock; } enum { MaxConnections = 1024, }; bool netSocketWaitForWritable(NetSocket fd, S32 timeoutMs) { fd_set writefds; timeval timeout; FD_ZERO(&writefds); FD_SET( fd, &writefds ); timeout.tv_sec = timeoutMs / 1000; timeout.tv_usec = ( timeoutMs % 1000 ) * 1000; if( select(fd + 1, NULL, &writefds, NULL, &timeout) > 0 ) return true; return false; } bool Net::init() { NetAsync::startAsync(); return(true); } void Net::shutdown() { while (gPolledSockets.size() > 0) closeConnectTo(gPolledSockets[0]->fd); closePort(); NetAsync::stopAsync(); } static void netToIPSocketAddress(const NetAddress *address, struct sockaddr_in *sockAddr) { dMemset(sockAddr, 0, sizeof(struct sockaddr_in)); sockAddr->sin_family = AF_INET; sockAddr->sin_port = htons(address->port); char tAddr[20]; dSprintf(tAddr, 20, "%d.%d.%d.%d\n", address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3]); //fprintf(stdout,"netToIPSocketAddress(): %s\n",tAddr);fflush(NULL); sockAddr->sin_addr.s_addr = inet_addr(tAddr); } static void IPSocketToNetAddress(const struct sockaddr_in *sockAddr, NetAddress *address) { address->type = NetAddress::IPAddress; address->port = htons(sockAddr->sin_port); char *tAddr; tAddr = inet_ntoa(sockAddr->sin_addr); //fprintf(stdout,"IPSocketToNetAddress(): %s\n",tAddr);fflush(NULL); U8 nets[4]; nets[0] = atoi(strtok(tAddr, ".")); nets[1] = atoi(strtok(NULL, ".")); nets[2] = atoi(strtok(NULL, ".")); nets[3] = atoi(strtok(NULL, ".")); //fprintf(stdout,"0 = %d, 1 = %d, 2 = %d, 3 = %d\n", nets[0], nets[1], nets[2], nets[3]); address->netNum[0] = nets[0]; address->netNum[1] = nets[1]; address->netNum[2] = nets[2]; address->netNum[3] = nets[3]; } static void netToIPXSocketAddress(const NetAddress *address, sockaddr_ipx *sockAddr) { dMemset(sockAddr, 0, sizeof(sockaddr_ipx)); sockAddr->sipx_family = AF_INET; sockAddr->sipx_port = htons(address->port); sockAddr->sipx_network = address->netNum[0]; sockAddr->sipx_node[0] = address->nodeNum[0]; sockAddr->sipx_node[1] = address->nodeNum[1]; sockAddr->sipx_node[2] = address->nodeNum[2]; sockAddr->sipx_node[3] = address->nodeNum[3]; sockAddr->sipx_node[4] = address->nodeNum[4]; sockAddr->sipx_node[5] = address->nodeNum[5]; } NetSocket Net::openListenPort(U16 port) { if(Game->isJournalReading()) { U32 ret; Game->journalRead(&ret); return NetSocket(ret); } NetSocket sock = openSocket(); if (sock == InvalidSocket) { Con::errorf("Unable to open listen socket: %s", strerror(errno)); return InvalidSocket; } if (bind(sock, port) != NoError) { Con::errorf("Unable to bind port %d: %s", port, strerror(errno)); ::close(sock); return InvalidSocket; } if (listen(sock, 4) != NoError) { Con::errorf("Unable to listen on port %d: %s", port, strerror(errno)); ::close(sock); return InvalidSocket; } setBlocking(sock, false); addPolledSocket(sock, Listening); if (Game->isJournalWriting()) Game->journalWrite(U32(sock)); return sock; } NetSocket Net::openConnectTo(const char *addressString) { if(!dStrnicmp(addressString, "ipx:", 4)) return InvalidSocket; if(!dStrnicmp(addressString, "ip:", 3)) addressString += 3; // eat off the ip: char remoteAddr[256]; dStrcpy(remoteAddr, addressString); char *portString = dStrchr(remoteAddr, ':'); U16 port; if(portString) { *portString++ = 0; port = htons(dAtoi(portString)); } else port = htons(defaultPort); if(!dStricmp(remoteAddr, "broadcast")) return InvalidSocket; if(Game->isJournalReading()) { U32 ret; Game->journalRead(&ret); return NetSocket(ret); } NetSocket sock = openSocket(); setBlocking(sock, false); sockaddr_in ipAddr; dMemset(&ipAddr, 0, sizeof(ipAddr)); if (inet_aton(remoteAddr, &ipAddr.sin_addr) != 0) { ipAddr.sin_port = port; ipAddr.sin_family = AF_INET; if(::connect(sock, (struct sockaddr *)&ipAddr, sizeof(ipAddr)) == -1 && errno != EINPROGRESS) { Con::errorf("Error connecting %s: %s", addressString, strerror(errno)); ::close(sock); sock = InvalidSocket; } if(sock != InvalidSocket) { // add this socket to our list of polled sockets addPolledSocket(sock, ConnectionPending); } } else { // need to do an asynchronous name lookup. first, add the socket // to the polled list addPolledSocket(sock, NameLookupRequired, remoteAddr, port); // queue the lookup gNetAsync.queueLookup(remoteAddr, sock); } if(Game->isJournalWriting()) Game->journalWrite(U32(sock)); return sock; } void Net::closeConnectTo(NetSocket sock) { if(Game->isJournalReading()) return; // if this socket is in the list of polled sockets, remove it for (int i = 0; i < gPolledSockets.size(); ++i) if (gPolledSockets[i]->fd == sock) { delete gPolledSockets[i]; gPolledSockets.erase(i); break; } closeSocket(sock); } Net::Error Net::sendtoSocket(NetSocket socket, const U8 *buffer, int bufferSize) { if(Game->isJournalReading()) { U32 e; Game->journalRead(&e); return (Net::Error) e; } Net::Error e = send(socket, buffer, bufferSize); if(Game->isJournalWriting()) Game->journalWrite(U32(e)); return e; } bool Net::openPort(S32 port) { if(udpSocket != InvalidSocket) close(udpSocket); if(ipxSocket != InvalidSocket) close(ipxSocket); udpSocket = socket(AF_INET, SOCK_DGRAM, 0); ipxSocket = socket(AF_IPX, SOCK_DGRAM, 0); if(udpSocket != InvalidSocket) { Net::Error error; error = bind(udpSocket, port); if(error == NoError) error = setBufferSize(udpSocket, 32768); if(error == NoError) error = setBroadcast(udpSocket, true); if(error == NoError) error = setBlocking(udpSocket, false); if(error == NoError) Con::printf("UDP initialized on port %d", port); else { close(udpSocket); udpSocket = InvalidSocket; Con::printf("Unable to initialize UDP - error %d", error); } } if(ipxSocket != InvalidSocket) { Net::Error error = NoError; sockaddr_ipx ipxAddress; memset((char *)&ipxAddress, 0, sizeof(ipxAddress)); ipxAddress.sipx_family = AF_IPX; ipxAddress.sipx_port = htons(port); S32 err = ::bind(ipxSocket, (struct sockaddr *)&ipxAddress, sizeof(ipxAddress)); if(err) error = getLastError(); if(error == NoError) error = setBufferSize(ipxSocket, 32768); if(error == NoError) error = setBroadcast(ipxSocket, true); if(error == NoError) error = setBlocking(ipxSocket, false); if(error == NoError) Con::printf("IPX initialized on port %d", port); else { close(ipxSocket); ipxSocket = InvalidSocket; Con::printf("Unable to initialize IPX - error %d", error); } } netPort = port; return ipxSocket != InvalidSocket || udpSocket != InvalidSocket; } void Net::closePort() { if(ipxSocket != InvalidSocket) close(ipxSocket); if(udpSocket != InvalidSocket) close(udpSocket); } Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) { if(Game->isJournalReading()) return NoError; if(address->type == NetAddress::IPAddress) { sockaddr_in ipAddr; netToIPSocketAddress(address, &ipAddr); if(::sendto(udpSocket, (const char*)buffer, bufferSize, 0, (sockaddr *) &ipAddr, sizeof(sockaddr_in)) == -1) return getLastError(); else return NoError; } } void Net::process() { sockaddr sa; PacketReceiveEvent receiveEvent; for(;;) { U32 addrLen = sizeof(sa); S32 bytesRead = -1; if(udpSocket != InvalidSocket) bytesRead = recvfrom(udpSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); if(bytesRead == -1 && ipxSocket != InvalidSocket) { addrLen = sizeof(sa); bytesRead = recvfrom(ipxSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); } if(bytesRead == -1) break; if(sa.sa_family == AF_INET) IPSocketToNetAddress((sockaddr_in *) &sa, &receiveEvent.sourceAddress); else continue; NetAddress &na = receiveEvent.sourceAddress; if(na.type == NetAddress::IPAddress && na.netNum[0] == 127 && na.netNum[1] == 0 && na.netNum[2] == 0 && na.netNum[3] == 1 && na.port == netPort) continue; if(bytesRead <= 0) continue; receiveEvent.size = PacketReceiveEventHeaderSize + bytesRead; Game->postEvent(receiveEvent); } // process the polled sockets. This blob of code performs functions // similar to WinsockProc in winNet.cc if (gPolledSockets.size() == 0) return; static ConnectedNotifyEvent notifyEvent; static ConnectedAcceptEvent acceptEvent; static ConnectedReceiveEvent cReceiveEvent; S32 optval; socklen_t optlen = sizeof(S32); S32 bytesRead; Net::Error err; bool removeSock = false; Socket *currentSock = NULL; sockaddr_in ipAddr; NetSocket incoming = InvalidSocket; char out_h_addr[1024]; int out_h_length = 0; for (S32 i = 0; i < gPolledSockets.size(); /* no increment, this is done at end of loop body */) { removeSock = false; currentSock = gPolledSockets[i]; switch (currentSock->state) { case InvalidState: Con::errorf("Error, InvalidState socket in polled sockets list"); break; case ConnectionPending: notifyEvent.tag = currentSock->fd; // see if it is now connected if (getsockopt(currentSock->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { Con::errorf("Error getting socket options: %s", strerror(errno)); notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; Game->postEvent(notifyEvent); removeSock = true; } else { if (optval == EINPROGRESS) // still connecting... break; if (optval == 0) { // poll for writable status to be sure we're connected. bool ready = netSocketWaitForWritable(currentSock->fd,0); if(!ready) break; // connected. notifyEvent.state = ConnectedNotifyEvent::Connected; Game->postEvent(notifyEvent); currentSock->state = Connected; } else { // some kind of error Con::errorf("Error connecting: %s", strerror(errno)); notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; Game->postEvent(notifyEvent); removeSock = true; } } break; case Connected: bytesRead = 0; // try to get some data err = Net::recv(currentSock->fd, cReceiveEvent.data, MaxPacketDataSize, &bytesRead); if(err == Net::NoError) { if (bytesRead > 0) { // got some data, post it cReceiveEvent.tag = currentSock->fd; cReceiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; Game->postEvent(cReceiveEvent); } else { // zero bytes read means EOF if (bytesRead < 0) // ack! this shouldn't happen Con::errorf("Unexpected error on socket: %s", strerror(errno)); notifyEvent.tag = currentSock->fd; notifyEvent.state = ConnectedNotifyEvent::Disconnected; Game->postEvent(notifyEvent); removeSock = true; } } else if (err != Net::NoError && err != Net::WouldBlock) { Con::errorf("Error reading from socket: %s", strerror(errno)); notifyEvent.tag = currentSock->fd; notifyEvent.state = ConnectedNotifyEvent::Disconnected; Game->postEvent(notifyEvent); removeSock = true; } break; case NameLookupRequired: // is the lookup complete? if (!gNetAsync.checkLookup( currentSock->fd, out_h_addr, &out_h_length, sizeof(out_h_addr))) break; notifyEvent.tag = currentSock->fd; if (out_h_length == -1) { Con::errorf("DNS lookup failed: %s", currentSock->remoteAddr); notifyEvent.state = ConnectedNotifyEvent::DNSFailed; removeSock = true; } else { // try to connect dMemcpy(&(ipAddr.sin_addr.s_addr), out_h_addr, out_h_length); ipAddr.sin_port = currentSock->remotePort; ipAddr.sin_family = AF_INET; if(::connect(currentSock->fd, (struct sockaddr *)&ipAddr, sizeof(ipAddr)) == -1) { if (errno == EINPROGRESS) { notifyEvent.state = ConnectedNotifyEvent::DNSResolved; currentSock->state = ConnectionPending; } else { Con::errorf("Error connecting to %s: %s", currentSock->remoteAddr, strerror(errno)); notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; removeSock = true; } } else { notifyEvent.state = ConnectedNotifyEvent::Connected; currentSock->state = Connected; } } Game->postEvent(notifyEvent); break; case Listening: incoming = Net::accept(currentSock->fd, &acceptEvent.address); if(incoming != InvalidSocket) { acceptEvent.portTag = currentSock->fd; acceptEvent.connectionTag = incoming; setBlocking(incoming, false); addPolledSocket(incoming, Connected); Game->postEvent(acceptEvent); } break; } // only increment index if we're not removing the connection, since // the removal will shift the indices down by one if (removeSock) closeConnectTo(currentSock->fd); else i++; } } NetSocket Net::openSocket() { int retSocket; retSocket = socket(AF_INET, SOCK_STREAM, 0); if(retSocket == InvalidSocket) return InvalidSocket; else return retSocket; } Net::Error Net::closeSocket(NetSocket socket) { if(socket != InvalidSocket) { if(!close(socket)) return NoError; else return getLastError(); } else return NotASocket; } Net::Error Net::connect(NetSocket socket, const NetAddress *address) { if(address->type != NetAddress::IPAddress) return WrongProtocolType; sockaddr_in socketAddress; netToIPSocketAddress(address, &socketAddress); if(!::connect(socket, (sockaddr *) &socketAddress, sizeof(socketAddress))) return NoError; return getLastError(); } Net::Error Net::listen(NetSocket socket, S32 backlog) { if(!::listen(socket, backlog)) return NoError; return getLastError(); } NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) { sockaddr_in socketAddress; U32 addrLen = sizeof(socketAddress); int retVal = ::accept(acceptSocket, (sockaddr *) &socketAddress, &addrLen); if(retVal != InvalidSocket) { IPSocketToNetAddress(&socketAddress, remoteAddress); return retVal; } return InvalidSocket; } Net::Error Net::bind(NetSocket socket, U16 port) { S32 error; sockaddr_in socketAddress; dMemset((char *)&socketAddress, 0, sizeof(socketAddress)); socketAddress.sin_family = AF_INET; // It's entirely possible that there are two NIC cards. // We let the user specify which one the server runs on. // thanks to [TPG]P1aGu3 for the name const char* serverIP = Con::getVariable( "Pref::Net::BindAddress" ); // serverIP is guaranteed to be non-0. AssertFatal( serverIP, "serverIP is NULL!" ); if( serverIP[0] != '\0' ) { // we're not empty socketAddress.sin_addr.s_addr = inet_addr( serverIP ); if( socketAddress.sin_addr.s_addr != INADDR_NONE ) { Con::printf( "Binding server port to %s", serverIP ); } else { Con::warnf( ConsoleLogEntry::General, "inet_addr() failed for %s while binding!", serverIP ); socketAddress.sin_addr.s_addr = INADDR_ANY; } } else { Con::printf( "Binding server port to default IP" ); socketAddress.sin_addr.s_addr = INADDR_ANY; } socketAddress.sin_port = htons(port); error = ::bind(socket, (sockaddr *) &socketAddress, sizeof(socketAddress)); if(!error) return NoError; return getLastError(); } Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) { S32 error; error = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &bufferSize, sizeof(bufferSize)); if(!error) error = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *) &bufferSize, sizeof(bufferSize)); if(!error) return NoError; return getLastError(); } Net::Error Net::setBroadcast(NetSocket socket, bool broadcast) { S32 bc = broadcast; S32 error = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&bc, sizeof(bc)); if(!error) return NoError; return getLastError(); } Net::Error Net::setBlocking(NetSocket socket, bool blockingIO) { int notblock = !blockingIO; S32 error = ioctl(socket, FIONBIO, ¬block); if(!error) return NoError; return getLastError(); } Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) { errno = 0; S32 bytesWritten = ::send(socket, (const char*)buffer, bufferSize, 0); if(bytesWritten == -1) Con::errorf("Could not write to socket. Error: %s",strerror(errno)); return getLastError(); } Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) { *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); if(*bytesRead == -1) return getLastError(); return NoError; } bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) { if((a1->type != a2->type) || (*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) || (a1->port != a2->port)) return false; if(a1->type == NetAddress::IPAddress) return true; for(S32 i = 0; i < 6; i++) if(a1->nodeNum[i] != a2->nodeNum[i]) return false; return true; } bool Net::stringToAddress(const char *addressString, NetAddress *address) { // assume IP if it doesn't have ipx: at the front. if(!dStrnicmp(addressString, "ip:", 3)) addressString += 3; // eat off the ip: sockaddr_in ipAddr; char remoteAddr[256]; if(strlen(addressString) > 255) return false; dStrcpy(remoteAddr, addressString); char *portString = dStrchr(remoteAddr, ':'); if(portString) *portString++ = '\0'; struct hostent *hp; if(!dStricmp(remoteAddr, "broadcast")) ipAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); else { if (inet_aton(remoteAddr,&ipAddr.sin_addr) == 0) // error { if((hp = gethostbyname(remoteAddr)) == 0) return false; else memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(in_addr)); } } if(portString) ipAddr.sin_port = htons(dAtoi(portString)); else ipAddr.sin_port = htons(defaultPort); ipAddr.sin_family = AF_INET; IPSocketToNetAddress(&ipAddr, address); return true; } void Net::addressToString(const NetAddress *address, char addressString[256]) { if(address->type == NetAddress::IPAddress) { sockaddr_in ipAddr; netToIPSocketAddress(address, &ipAddr); if(ipAddr.sin_addr.s_addr == htonl(INADDR_BROADCAST)) dSprintf(addressString, 256, "IP:Broadcast:%d", ntohs(ipAddr.sin_port)); else dSprintf(addressString, 256, "IP:%s:%d", inet_ntoa(ipAddr.sin_addr), ntohs(ipAddr.sin_port)); // dSprintf(addressString, 256, "IP:%d:%d", ipAddr.sin_addr.s_addr, // ntohs(ipAddr.sin_port)); } else { return; dSprintf(addressString, 256, "IPX:%.2X%.2X%.2X%.2X:%.2X%.2X%.2X%.2X%.2X%.2X:%d", address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3], address->nodeNum[0], address->nodeNum[1], address->nodeNum[2], address->nodeNum[3], address->nodeNum[4], address->nodeNum[5], address->port); } } Net::Error getLastError() { if (errno == EAGAIN) return Net::WouldBlock; if (errno == 0) return Net::NoError; return Net::UnknownError; }