533 lines
12 KiB
C++
533 lines
12 KiB
C++
|
/*
|
||
|
** Command & Conquer Renegade(tm)
|
||
|
** Copyright 2025 Electronic Arts Inc.
|
||
|
**
|
||
|
** This program is free software: you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation, either version 3 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program is distributed in the hope that it will be useful,
|
||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
** GNU General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "tcpmgr.h"
|
||
|
#include "tcpcon.h"
|
||
|
#include "wlib/wtime.h"
|
||
|
|
||
|
|
||
|
TCPMgr::TCPMgr()
|
||
|
{
|
||
|
HandleSequence_=0;
|
||
|
}
|
||
|
|
||
|
TCPMgr::~TCPMgr()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Add a listener socket to accept connections on a given port
|
||
|
//
|
||
|
bit8 TCPMgr::addListener(uint32 ip, uint16 port, bit8 reuseAddr)
|
||
|
{
|
||
|
SOCKET fd=createSocket(ip,port,reuseAddr);
|
||
|
if (fd == INVALID_SOCKET)
|
||
|
return(FALSE);
|
||
|
|
||
|
listen(fd,64); // Solaris needs lots of listen slots
|
||
|
|
||
|
ListenSocket lsock;
|
||
|
lsock.fd=fd;
|
||
|
lsock.ip=ip;
|
||
|
lsock.port=port;
|
||
|
|
||
|
ListenArray_.addTail(lsock);
|
||
|
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remove listener on a given ip/port
|
||
|
//
|
||
|
bit8 TCPMgr::removeListener(uint32 ip, uint16 port)
|
||
|
{
|
||
|
ListenSocket *lptr;
|
||
|
for (int i=0; i<ListenArray_.length(); i++)
|
||
|
{
|
||
|
ListenArray_.getPointer(&lptr,i);
|
||
|
if ((lptr->ip == ip) && (lptr->port == port))
|
||
|
{
|
||
|
closesocket(lptr->fd);
|
||
|
ListenArray_.remove(i);
|
||
|
return(TRUE);
|
||
|
}
|
||
|
}
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the socket for a given listener
|
||
|
//
|
||
|
bit8 TCPMgr::getListener(uint32 ip, uint16 port, OUT SOCKET &outsock)
|
||
|
{
|
||
|
ListenSocket *lptr;
|
||
|
for (int i=0; i<ListenArray_.length(); i++)
|
||
|
{
|
||
|
ListenArray_.getPointer(&lptr,i);
|
||
|
if ((lptr->ip == ip) && (lptr->port == port))
|
||
|
{
|
||
|
outsock=lptr->fd;
|
||
|
return(TRUE);
|
||
|
}
|
||
|
}
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Enable/Disable buffered writes on a socket
|
||
|
//
|
||
|
bit8 TCPMgr::setBufferedWrites(TCPCon *con, bit8 enabled)
|
||
|
{
|
||
|
TCPCon *tempptr=NULL;
|
||
|
|
||
|
// Check to see if this connection is already in our list
|
||
|
for (int i=0; i<BufferedWriters_.length(); i++)
|
||
|
{
|
||
|
BufferedWriters_.get(tempptr, i);
|
||
|
if (tempptr==con)
|
||
|
{
|
||
|
con->setBufferedWrites(this, false);
|
||
|
BufferedWriters_.remove(i);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (enabled) // OK, now add to the list
|
||
|
{
|
||
|
con->setBufferedWrites(this, true);
|
||
|
BufferedWriters_.addTail(con);
|
||
|
}
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Let all the buffered writers send data
|
||
|
//
|
||
|
void TCPMgr::pumpWriters(void) // pump the buffered writer connections
|
||
|
{
|
||
|
TCPCon *conptr=NULL;
|
||
|
|
||
|
// Check to see if this connection is already in our list
|
||
|
for (int i=0; i<BufferedWriters_.length(); i++)
|
||
|
{
|
||
|
BufferedWriters_.get(conptr, i);
|
||
|
conptr->pumpWrites();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Connect by hostname rather than IP
|
||
|
//
|
||
|
bit8 TCPMgr::connect(char *host, uint16 port, OUT uint32 *handle)
|
||
|
{
|
||
|
char hostName[129];
|
||
|
struct hostent *hostStruct;
|
||
|
struct in_addr *hostNode;
|
||
|
|
||
|
if (isdigit(host[0]))
|
||
|
return ( connect(ntohl(inet_addr(host)), port, handle));
|
||
|
|
||
|
strcpy(hostName, host);
|
||
|
hostStruct = gethostbyname(host);
|
||
|
if (hostStruct == NULL)
|
||
|
return (0);
|
||
|
hostNode = (struct in_addr *) hostStruct->h_addr;
|
||
|
return ( connect(ntohl(hostNode->s_addr),port,handle) );
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Request a connection to a given address (all values in host byte order)
|
||
|
//
|
||
|
bit8 TCPMgr::connect(uint32 ip, uint16 port,OUT uint32 *handle)
|
||
|
{
|
||
|
PendingConn pConn;
|
||
|
if ((pConn.fd=createSocket((uint32) 0,(uint16) 0,FALSE)) == INVALID_SOCKET)
|
||
|
return(FALSE);
|
||
|
pConn.ip=0;
|
||
|
pConn.port=0;
|
||
|
pConn.remoteIp=ip;
|
||
|
pConn.remotePort=port;
|
||
|
pConn.startTime=time(NULL);
|
||
|
pConn.handle=HandleSequence_++;
|
||
|
pConn.state=CLOSED;
|
||
|
pConn.incoming=FALSE; // outgoing connection
|
||
|
|
||
|
ConnectArray_.addTail(pConn);
|
||
|
|
||
|
*handle=pConn.handle;
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get an incoming connection (specify handle or 0 for any)
|
||
|
//
|
||
|
// Wait for upto 'wait_secs' seconds for the connection.
|
||
|
//
|
||
|
bit8 TCPMgr::getOutgoingConnection(TCPCon **conn, uint32 handle, sint32 wait_secs)
|
||
|
{
|
||
|
return(getConnection(conn,handle,0,wait_secs,OUTGOING));
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Get an incoming connection (specify listen port or 0 for any)
|
||
|
//
|
||
|
// Wait for upto 'wait_secs' seconds for the connection.
|
||
|
//
|
||
|
bit8 TCPMgr::getIncomingConnection(TCPCon **conn, uint16 port, sint32 wait_secs)
|
||
|
{
|
||
|
return(getConnection(conn,INVALID_HANDLE,port,wait_secs,INCOMING));
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Return after there is data to read, or we've timed out.
|
||
|
//
|
||
|
int TCPMgr::wait(uint32 sec, uint32 usec, SOCKET *sockets, int count, bit8 readMode)
|
||
|
{
|
||
|
Wtime timeout,timenow,timethen;
|
||
|
fd_set givenSet;
|
||
|
fd_set returnSet;
|
||
|
fd_set backupSet;
|
||
|
int givenMax=0;
|
||
|
bit8 noTimeout=FALSE;
|
||
|
int retval;
|
||
|
uint32 i;
|
||
|
|
||
|
DBGMSG("Waiting on "<<count<<" sockets");
|
||
|
|
||
|
FD_ZERO(&givenSet);
|
||
|
for (i=0; i<(uint32)count; i++)
|
||
|
{
|
||
|
FD_SET(sockets[i],&givenSet);
|
||
|
}
|
||
|
|
||
|
timeval tv;
|
||
|
timeval *tvPtr=NULL;
|
||
|
returnSet=givenSet;
|
||
|
backupSet=givenSet;
|
||
|
|
||
|
if ((sec==-1)||(usec==-1))
|
||
|
noTimeout=TRUE;
|
||
|
|
||
|
timeout.SetSec(sec);
|
||
|
timeout.SetUsec(usec);
|
||
|
timethen+=timeout;
|
||
|
|
||
|
for (i=0; i<(uint32)count; i++)
|
||
|
{
|
||
|
if (sockets[i] > (SOCKET)givenMax)
|
||
|
givenMax=sockets[i];
|
||
|
}
|
||
|
|
||
|
bit8 done=FALSE;
|
||
|
while( ! done)
|
||
|
{
|
||
|
tvPtr=&tv;
|
||
|
if (noTimeout)
|
||
|
tvPtr=NULL;
|
||
|
else
|
||
|
timeout.GetTimevalMT(tv);
|
||
|
|
||
|
if (readMode) // can we read?
|
||
|
retval=select(givenMax+1,&returnSet,0,0,tvPtr);
|
||
|
else // can we write?
|
||
|
retval=select(givenMax+1,0,&returnSet,0,tvPtr);
|
||
|
|
||
|
DBGMSG("Select wake");
|
||
|
|
||
|
if (retval>=0)
|
||
|
done=TRUE;
|
||
|
else if ((retval==SOCKET_ERROR)&&(getStatus()==INTR)) // in case of signal
|
||
|
{
|
||
|
if (noTimeout==FALSE)
|
||
|
{
|
||
|
timenow.Update();
|
||
|
timeout=timethen-timenow;
|
||
|
}
|
||
|
if ((noTimeout==FALSE)&&(timenow.GetSec()==0)&&(timenow.GetUsec()==0))
|
||
|
done=TRUE;
|
||
|
else
|
||
|
returnSet=backupSet;
|
||
|
}
|
||
|
else // maybe out of memory?
|
||
|
{
|
||
|
done=TRUE;
|
||
|
}
|
||
|
}
|
||
|
return(retval);
|
||
|
}
|
||
|
|
||
|
|
||
|
TCPMgr::STATUS TCPMgr::getStatus(void)
|
||
|
{
|
||
|
#ifdef _WINDOWS
|
||
|
int status=WSAGetLastError();
|
||
|
if (status==0) return(OK);
|
||
|
else if (status==WSAEINTR) return(INTR);
|
||
|
else if (status==WSAEINPROGRESS) return(INPROGRESS);
|
||
|
else if (status==WSAECONNREFUSED) return(CONNREFUSED);
|
||
|
else if (status==WSAEINVAL) return(INVAL);
|
||
|
else if (status==WSAEISCONN) return(ISCONN);
|
||
|
else if (status==WSAENOTSOCK) return(NOTSOCK);
|
||
|
else if (status==WSAETIMEDOUT) return(TIMEDOUT);
|
||
|
else if (status==WSAEALREADY) return(ALREADY);
|
||
|
else if (status==WSAEWOULDBLOCK) return(WOULDBLOCK);
|
||
|
else if (status==WSAEBADF) return(BADF);
|
||
|
else return(UNKNOWN);
|
||
|
#else
|
||
|
int status=errno;
|
||
|
if (status==0) return(OK);
|
||
|
else if (status==EINTR) return(INTR);
|
||
|
else if (status==EINPROGRESS) return(INPROGRESS);
|
||
|
else if (status==ECONNREFUSED) return(CONNREFUSED);
|
||
|
else if (status==EINVAL) return(INVAL);
|
||
|
else if (status==EISCONN) return(ISCONN);
|
||
|
else if (status==ENOTSOCK) return(NOTSOCK);
|
||
|
else if (status==ETIMEDOUT) return(TIMEDOUT);
|
||
|
else if (status==EALREADY) return(ALREADY);
|
||
|
else if (status==EAGAIN) return(AGAIN);
|
||
|
else if (status==EWOULDBLOCK) return(WOULDBLOCK);
|
||
|
else if (status==EBADF) return(BADF);
|
||
|
else return(UNKNOWN);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/********************* BEGIN PRIVATE METHODS *********************************/
|
||
|
|
||
|
//
|
||
|
// Create a bound socket
|
||
|
//
|
||
|
SOCKET TCPMgr::createSocket(uint32 ip, uint16 port, bit8 reuseAddr)
|
||
|
{
|
||
|
struct sockaddr_in addr;
|
||
|
addr.sin_family=AF_INET;
|
||
|
addr.sin_port=htons(port);
|
||
|
addr.sin_addr.s_addr=htonl(ip);
|
||
|
|
||
|
SOCKET fd=socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL);
|
||
|
if (fd==-1)
|
||
|
return(INVALID_SOCKET);
|
||
|
|
||
|
if (setBlocking(fd,FALSE)==FALSE)
|
||
|
return(INVALID_SOCKET);
|
||
|
|
||
|
if (reuseAddr)
|
||
|
{
|
||
|
uint32 opval=1;
|
||
|
if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&opval,sizeof(opval)) != 0)
|
||
|
{
|
||
|
closesocket(fd);
|
||
|
return(INVALID_SOCKET);
|
||
|
}
|
||
|
}
|
||
|
if (bind(fd,(struct sockaddr *)&addr, sizeof(addr))==SOCKET_ERROR)
|
||
|
{
|
||
|
closesocket(fd);
|
||
|
return(INVALID_SOCKET);
|
||
|
}
|
||
|
return(fd);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Set the blocking mode of the socket
|
||
|
//
|
||
|
bit8 TCPMgr::setBlocking(SOCKET fd, bit8 block)
|
||
|
{
|
||
|
#ifdef _WINDOWS
|
||
|
unsigned long flag=1;
|
||
|
if (block)
|
||
|
flag=0;
|
||
|
int retval;
|
||
|
retval=ioctlsocket(fd,FIONBIO,&flag);
|
||
|
if (retval==SOCKET_ERROR)
|
||
|
return(FALSE);
|
||
|
else
|
||
|
return(TRUE);
|
||
|
#else // UNIX
|
||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||
|
if (block==FALSE) // set nonblocking
|
||
|
flags |= O_NONBLOCK;
|
||
|
else // set blocking
|
||
|
flags &= ~(O_NONBLOCK);
|
||
|
|
||
|
if (fcntl(fd, F_SETFL, flags) < 0)
|
||
|
{
|
||
|
return(FALSE);
|
||
|
}
|
||
|
return(TRUE);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
bit8 TCPMgr::getConnection(TCPCon **conn, uint32 handle, uint16 port, sint32 wait_secs, DIRECTION dir)
|
||
|
{
|
||
|
PendingConn *connPtr=NULL;
|
||
|
time_t start=time(NULL);
|
||
|
SOCKET fdArray[1024];
|
||
|
|
||
|
for (int i=0; i<ConnectArray_.length(); i++)
|
||
|
{
|
||
|
ConnectArray_.getPointer(&connPtr,i);
|
||
|
fdArray[i]=connPtr->fd;
|
||
|
}
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
pumpConnections();
|
||
|
|
||
|
for (int i=0; i<ConnectArray_.length(); i++)
|
||
|
{
|
||
|
ConnectArray_.getPointer(&connPtr,i);
|
||
|
if (connPtr->state != CONNECTED)
|
||
|
continue;
|
||
|
if (( ! ((int)dir & (int)INCOMING)) && (connPtr->incoming == TRUE))
|
||
|
continue;
|
||
|
if (( ! ((int)dir & (int)OUTGOING)) && (connPtr->incoming == FALSE))
|
||
|
continue;
|
||
|
if ((handle != INVALID_HANDLE) && (handle != connPtr->handle))
|
||
|
continue;
|
||
|
if ((port != 0) && (port != connPtr->port))
|
||
|
continue;
|
||
|
*conn=new TCPCon(connPtr->fd);
|
||
|
ConnectArray_.remove(i);
|
||
|
return(TRUE);
|
||
|
}
|
||
|
// Wait for socket activity for a bit
|
||
|
#ifdef _WINDOWS
|
||
|
Sleep(100); // windows may be getting flooded with conn msgs, test this
|
||
|
#endif
|
||
|
sint32 remaining_wait=wait_secs - (time(NULL)-start);
|
||
|
if ((remaining_wait > 0) && (wait(remaining_wait,0,fdArray,ConnectArray_.length(),FALSE) > 0))
|
||
|
continue; // got something!
|
||
|
|
||
|
if (remaining_wait <= 0)
|
||
|
break;
|
||
|
}
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// TOFIX: don't forget about connect flooding on Win32
|
||
|
//
|
||
|
void TCPMgr::pumpConnections(void)
|
||
|
{
|
||
|
PendingConn *connPtr=NULL;
|
||
|
STATUS status;
|
||
|
int i;
|
||
|
int retval;
|
||
|
|
||
|
// Outgoing connections
|
||
|
for (i=0; i<ConnectArray_.length(); i++)
|
||
|
{
|
||
|
ConnectArray_.getPointer(&connPtr,i);
|
||
|
|
||
|
if ((connPtr->state == CLOSED) || (connPtr->state == CONNECTING))
|
||
|
{
|
||
|
struct sockaddr_in addr;
|
||
|
int addrSize=sizeof(addr);
|
||
|
addr.sin_family=AF_INET;
|
||
|
addr.sin_port=htons(connPtr->remotePort);
|
||
|
addr.sin_addr.s_addr=htonl(connPtr->remoteIp);
|
||
|
|
||
|
if (connPtr->state == CONNECTING)
|
||
|
{
|
||
|
if (getpeername(connPtr->fd,(sockaddr *)&addr,&addrSize)==0) // if get endpoint, then success
|
||
|
connPtr->state=CONNECTED;
|
||
|
}
|
||
|
else if ((retval=::connect(connPtr->fd, (struct sockaddr *)&addr, sizeof(addr)))==SOCKET_ERROR)
|
||
|
{
|
||
|
status=getStatus();
|
||
|
if (status==ISCONN)
|
||
|
retval=0;
|
||
|
else if ((status==INPROGRESS)||(status==ALREADY)||(status==WOULDBLOCK))
|
||
|
{
|
||
|
connPtr->state=CONNECTING;
|
||
|
continue; // Move on to next pending conn...
|
||
|
}
|
||
|
else // doh, real problem
|
||
|
{
|
||
|
assert(0);
|
||
|
closesocket(connPtr->fd);
|
||
|
connPtr->fd=createSocket(connPtr->ip, connPtr->port, FALSE);
|
||
|
}
|
||
|
}
|
||
|
if (retval==0)
|
||
|
{
|
||
|
connPtr->state=CONNECTED;
|
||
|
}
|
||
|
}
|
||
|
} // for ConnectArray_;
|
||
|
|
||
|
// Incoming connections
|
||
|
ListenSocket *listenPtr;
|
||
|
struct sockaddr_in clientAddr;
|
||
|
int addrlen;
|
||
|
for (i=0; i<ListenArray_.length(); i++)
|
||
|
{
|
||
|
ListenArray_.getPointer(&listenPtr,i);
|
||
|
|
||
|
while(1) // accept all incoming on each socket
|
||
|
{
|
||
|
addrlen=sizeof(clientAddr);
|
||
|
SOCKET newFD=accept(listenPtr->fd, (struct sockaddr *)&clientAddr, &addrlen);
|
||
|
if (newFD != INVALID_SOCKET)
|
||
|
{
|
||
|
setBlocking(newFD, FALSE);
|
||
|
|
||
|
DBGMSG("Connection accepted");
|
||
|
|
||
|
PendingConn newConn;
|
||
|
newConn.fd=newFD;
|
||
|
newConn.ip=0;
|
||
|
newConn.port=0;
|
||
|
newConn.remoteIp=ntohl(clientAddr.sin_addr.s_addr);
|
||
|
newConn.remotePort=ntohs(clientAddr.sin_port);
|
||
|
newConn.handle=HandleSequence_++;
|
||
|
newConn.state=CONNECTED;
|
||
|
newConn.incoming=TRUE;
|
||
|
newConn.remoteIp=ntohl(clientAddr.sin_addr.s_addr);
|
||
|
newConn.remotePort=ntohs(clientAddr.sin_port);
|
||
|
|
||
|
ConnectArray_.addTail(newConn);
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|