tcpmgr.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "tcpmgr.h"
  19. #include "tcpcon.h"
  20. #include "wlib/wtime.h"
  21. TCPMgr::TCPMgr()
  22. {
  23. HandleSequence_=0;
  24. }
  25. TCPMgr::~TCPMgr()
  26. {
  27. }
  28. //
  29. // Add a listener socket to accept connections on a given port
  30. //
  31. bit8 TCPMgr::addListener(uint32 ip, uint16 port, bit8 reuseAddr)
  32. {
  33. SOCKET fd=createSocket(ip,port,reuseAddr);
  34. if (fd == INVALID_SOCKET)
  35. return(FALSE);
  36. listen(fd,64); // Solaris needs lots of listen slots
  37. ListenSocket lsock;
  38. lsock.fd=fd;
  39. lsock.ip=ip;
  40. lsock.port=port;
  41. ListenArray_.addTail(lsock);
  42. return(TRUE);
  43. }
  44. //
  45. // Remove listener on a given ip/port
  46. //
  47. bit8 TCPMgr::removeListener(uint32 ip, uint16 port)
  48. {
  49. ListenSocket *lptr;
  50. for (int i=0; i<ListenArray_.length(); i++)
  51. {
  52. ListenArray_.getPointer(&lptr,i);
  53. if ((lptr->ip == ip) && (lptr->port == port))
  54. {
  55. closesocket(lptr->fd);
  56. ListenArray_.remove(i);
  57. return(TRUE);
  58. }
  59. }
  60. return(FALSE);
  61. }
  62. //
  63. // Get the socket for a given listener
  64. //
  65. bit8 TCPMgr::getListener(uint32 ip, uint16 port, OUT SOCKET &outsock)
  66. {
  67. ListenSocket *lptr;
  68. for (int i=0; i<ListenArray_.length(); i++)
  69. {
  70. ListenArray_.getPointer(&lptr,i);
  71. if ((lptr->ip == ip) && (lptr->port == port))
  72. {
  73. outsock=lptr->fd;
  74. return(TRUE);
  75. }
  76. }
  77. return(FALSE);
  78. }
  79. //
  80. // Enable/Disable buffered writes on a socket
  81. //
  82. bit8 TCPMgr::setBufferedWrites(TCPCon *con, bit8 enabled)
  83. {
  84. TCPCon *tempptr=NULL;
  85. // Check to see if this connection is already in our list
  86. for (int i=0; i<BufferedWriters_.length(); i++)
  87. {
  88. BufferedWriters_.get(tempptr, i);
  89. if (tempptr==con)
  90. {
  91. con->setBufferedWrites(this, false);
  92. BufferedWriters_.remove(i);
  93. break;
  94. }
  95. }
  96. if (enabled) // OK, now add to the list
  97. {
  98. con->setBufferedWrites(this, true);
  99. BufferedWriters_.addTail(con);
  100. }
  101. return(TRUE);
  102. }
  103. //
  104. // Let all the buffered writers send data
  105. //
  106. void TCPMgr::pumpWriters(void) // pump the buffered writer connections
  107. {
  108. TCPCon *conptr=NULL;
  109. // Check to see if this connection is already in our list
  110. for (int i=0; i<BufferedWriters_.length(); i++)
  111. {
  112. BufferedWriters_.get(conptr, i);
  113. conptr->pumpWrites();
  114. }
  115. }
  116. //
  117. // Connect by hostname rather than IP
  118. //
  119. bit8 TCPMgr::connect(char *host, uint16 port, OUT uint32 *handle)
  120. {
  121. char hostName[129];
  122. struct hostent *hostStruct;
  123. struct in_addr *hostNode;
  124. if (isdigit(host[0]))
  125. return ( connect(ntohl(inet_addr(host)), port, handle));
  126. strcpy(hostName, host);
  127. hostStruct = gethostbyname(host);
  128. if (hostStruct == NULL)
  129. return (0);
  130. hostNode = (struct in_addr *) hostStruct->h_addr;
  131. return ( connect(ntohl(hostNode->s_addr),port,handle) );
  132. }
  133. //
  134. // Request a connection to a given address (all values in host byte order)
  135. //
  136. bit8 TCPMgr::connect(uint32 ip, uint16 port,OUT uint32 *handle)
  137. {
  138. PendingConn pConn;
  139. if ((pConn.fd=createSocket((uint32) 0,(uint16) 0,FALSE)) == INVALID_SOCKET)
  140. return(FALSE);
  141. pConn.ip=0;
  142. pConn.port=0;
  143. pConn.remoteIp=ip;
  144. pConn.remotePort=port;
  145. pConn.startTime=time(NULL);
  146. pConn.handle=HandleSequence_++;
  147. pConn.state=CLOSED;
  148. pConn.incoming=FALSE; // outgoing connection
  149. ConnectArray_.addTail(pConn);
  150. *handle=pConn.handle;
  151. return(TRUE);
  152. }
  153. //
  154. // Get an incoming connection (specify handle or 0 for any)
  155. //
  156. // Wait for upto 'wait_secs' seconds for the connection.
  157. //
  158. bit8 TCPMgr::getOutgoingConnection(TCPCon **conn, uint32 handle, sint32 wait_secs)
  159. {
  160. return(getConnection(conn,handle,0,wait_secs,OUTGOING));
  161. }
  162. //
  163. // Get an incoming connection (specify listen port or 0 for any)
  164. //
  165. // Wait for upto 'wait_secs' seconds for the connection.
  166. //
  167. bit8 TCPMgr::getIncomingConnection(TCPCon **conn, uint16 port, sint32 wait_secs)
  168. {
  169. return(getConnection(conn,INVALID_HANDLE,port,wait_secs,INCOMING));
  170. }
  171. //
  172. // Return after there is data to read, or we've timed out.
  173. //
  174. int TCPMgr::wait(uint32 sec, uint32 usec, SOCKET *sockets, int count, bit8 readMode)
  175. {
  176. Wtime timeout,timenow,timethen;
  177. fd_set givenSet;
  178. fd_set returnSet;
  179. fd_set backupSet;
  180. int givenMax=0;
  181. bit8 noTimeout=FALSE;
  182. int retval;
  183. uint32 i;
  184. DBGMSG("Waiting on "<<count<<" sockets");
  185. FD_ZERO(&givenSet);
  186. for (i=0; i<(uint32)count; i++)
  187. {
  188. FD_SET(sockets[i],&givenSet);
  189. }
  190. timeval tv;
  191. timeval *tvPtr=NULL;
  192. returnSet=givenSet;
  193. backupSet=givenSet;
  194. if ((sec==-1)||(usec==-1))
  195. noTimeout=TRUE;
  196. timeout.SetSec(sec);
  197. timeout.SetUsec(usec);
  198. timethen+=timeout;
  199. for (i=0; i<(uint32)count; i++)
  200. {
  201. if (sockets[i] > (SOCKET)givenMax)
  202. givenMax=sockets[i];
  203. }
  204. bit8 done=FALSE;
  205. while( ! done)
  206. {
  207. tvPtr=&tv;
  208. if (noTimeout)
  209. tvPtr=NULL;
  210. else
  211. timeout.GetTimevalMT(tv);
  212. if (readMode) // can we read?
  213. retval=select(givenMax+1,&returnSet,0,0,tvPtr);
  214. else // can we write?
  215. retval=select(givenMax+1,0,&returnSet,0,tvPtr);
  216. DBGMSG("Select wake");
  217. if (retval>=0)
  218. done=TRUE;
  219. else if ((retval==SOCKET_ERROR)&&(getStatus()==INTR)) // in case of signal
  220. {
  221. if (noTimeout==FALSE)
  222. {
  223. timenow.Update();
  224. timeout=timethen-timenow;
  225. }
  226. if ((noTimeout==FALSE)&&(timenow.GetSec()==0)&&(timenow.GetUsec()==0))
  227. done=TRUE;
  228. else
  229. returnSet=backupSet;
  230. }
  231. else // maybe out of memory?
  232. {
  233. done=TRUE;
  234. }
  235. }
  236. return(retval);
  237. }
  238. TCPMgr::STATUS TCPMgr::getStatus(void)
  239. {
  240. #ifdef _WINDOWS
  241. int status=WSAGetLastError();
  242. if (status==0) return(OK);
  243. else if (status==WSAEINTR) return(INTR);
  244. else if (status==WSAEINPROGRESS) return(INPROGRESS);
  245. else if (status==WSAECONNREFUSED) return(CONNREFUSED);
  246. else if (status==WSAEINVAL) return(INVAL);
  247. else if (status==WSAEISCONN) return(ISCONN);
  248. else if (status==WSAENOTSOCK) return(NOTSOCK);
  249. else if (status==WSAETIMEDOUT) return(TIMEDOUT);
  250. else if (status==WSAEALREADY) return(ALREADY);
  251. else if (status==WSAEWOULDBLOCK) return(WOULDBLOCK);
  252. else if (status==WSAEBADF) return(BADF);
  253. else return(UNKNOWN);
  254. #else
  255. int status=errno;
  256. if (status==0) return(OK);
  257. else if (status==EINTR) return(INTR);
  258. else if (status==EINPROGRESS) return(INPROGRESS);
  259. else if (status==ECONNREFUSED) return(CONNREFUSED);
  260. else if (status==EINVAL) return(INVAL);
  261. else if (status==EISCONN) return(ISCONN);
  262. else if (status==ENOTSOCK) return(NOTSOCK);
  263. else if (status==ETIMEDOUT) return(TIMEDOUT);
  264. else if (status==EALREADY) return(ALREADY);
  265. else if (status==EAGAIN) return(AGAIN);
  266. else if (status==EWOULDBLOCK) return(WOULDBLOCK);
  267. else if (status==EBADF) return(BADF);
  268. else return(UNKNOWN);
  269. #endif
  270. }
  271. /********************* BEGIN PRIVATE METHODS *********************************/
  272. //
  273. // Create a bound socket
  274. //
  275. SOCKET TCPMgr::createSocket(uint32 ip, uint16 port, bit8 reuseAddr)
  276. {
  277. struct sockaddr_in addr;
  278. addr.sin_family=AF_INET;
  279. addr.sin_port=htons(port);
  280. addr.sin_addr.s_addr=htonl(ip);
  281. SOCKET fd=socket(AF_INET,SOCK_STREAM,DEFAULT_PROTOCOL);
  282. if (fd==-1)
  283. return(INVALID_SOCKET);
  284. if (setBlocking(fd,FALSE)==FALSE)
  285. return(INVALID_SOCKET);
  286. if (reuseAddr)
  287. {
  288. uint32 opval=1;
  289. if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&opval,sizeof(opval)) != 0)
  290. {
  291. closesocket(fd);
  292. return(INVALID_SOCKET);
  293. }
  294. }
  295. if (bind(fd,(struct sockaddr *)&addr, sizeof(addr))==SOCKET_ERROR)
  296. {
  297. closesocket(fd);
  298. return(INVALID_SOCKET);
  299. }
  300. return(fd);
  301. }
  302. //
  303. // Set the blocking mode of the socket
  304. //
  305. bit8 TCPMgr::setBlocking(SOCKET fd, bit8 block)
  306. {
  307. #ifdef _WINDOWS
  308. unsigned long flag=1;
  309. if (block)
  310. flag=0;
  311. int retval;
  312. retval=ioctlsocket(fd,FIONBIO,&flag);
  313. if (retval==SOCKET_ERROR)
  314. return(FALSE);
  315. else
  316. return(TRUE);
  317. #else // UNIX
  318. int flags = fcntl(fd, F_GETFL, 0);
  319. if (block==FALSE) // set nonblocking
  320. flags |= O_NONBLOCK;
  321. else // set blocking
  322. flags &= ~(O_NONBLOCK);
  323. if (fcntl(fd, F_SETFL, flags) < 0)
  324. {
  325. return(FALSE);
  326. }
  327. return(TRUE);
  328. #endif
  329. }
  330. bit8 TCPMgr::getConnection(TCPCon **conn, uint32 handle, uint16 port, sint32 wait_secs, DIRECTION dir)
  331. {
  332. PendingConn *connPtr=NULL;
  333. time_t start=time(NULL);
  334. SOCKET fdArray[1024];
  335. for (int i=0; i<ConnectArray_.length(); i++)
  336. {
  337. ConnectArray_.getPointer(&connPtr,i);
  338. fdArray[i]=connPtr->fd;
  339. }
  340. while(1)
  341. {
  342. pumpConnections();
  343. for (int i=0; i<ConnectArray_.length(); i++)
  344. {
  345. ConnectArray_.getPointer(&connPtr,i);
  346. if (connPtr->state != CONNECTED)
  347. continue;
  348. if (( ! ((int)dir & (int)INCOMING)) && (connPtr->incoming == TRUE))
  349. continue;
  350. if (( ! ((int)dir & (int)OUTGOING)) && (connPtr->incoming == FALSE))
  351. continue;
  352. if ((handle != INVALID_HANDLE) && (handle != connPtr->handle))
  353. continue;
  354. if ((port != 0) && (port != connPtr->port))
  355. continue;
  356. *conn=new TCPCon(connPtr->fd);
  357. ConnectArray_.remove(i);
  358. return(TRUE);
  359. }
  360. // Wait for socket activity for a bit
  361. #ifdef _WINDOWS
  362. Sleep(100); // windows may be getting flooded with conn msgs, test this
  363. #endif
  364. sint32 remaining_wait=wait_secs - (time(NULL)-start);
  365. if ((remaining_wait > 0) && (wait(remaining_wait,0,fdArray,ConnectArray_.length(),FALSE) > 0))
  366. continue; // got something!
  367. if (remaining_wait <= 0)
  368. break;
  369. }
  370. return(FALSE);
  371. }
  372. //
  373. // TOFIX: don't forget about connect flooding on Win32
  374. //
  375. void TCPMgr::pumpConnections(void)
  376. {
  377. PendingConn *connPtr=NULL;
  378. STATUS status;
  379. int i;
  380. int retval;
  381. // Outgoing connections
  382. for (i=0; i<ConnectArray_.length(); i++)
  383. {
  384. ConnectArray_.getPointer(&connPtr,i);
  385. if ((connPtr->state == CLOSED) || (connPtr->state == CONNECTING))
  386. {
  387. struct sockaddr_in addr;
  388. int addrSize=sizeof(addr);
  389. addr.sin_family=AF_INET;
  390. addr.sin_port=htons(connPtr->remotePort);
  391. addr.sin_addr.s_addr=htonl(connPtr->remoteIp);
  392. if (connPtr->state == CONNECTING)
  393. {
  394. if (getpeername(connPtr->fd,(sockaddr *)&addr,&addrSize)==0) // if get endpoint, then success
  395. connPtr->state=CONNECTED;
  396. }
  397. else if ((retval=::connect(connPtr->fd, (struct sockaddr *)&addr, sizeof(addr)))==SOCKET_ERROR)
  398. {
  399. status=getStatus();
  400. if (status==ISCONN)
  401. retval=0;
  402. else if ((status==INPROGRESS)||(status==ALREADY)||(status==WOULDBLOCK))
  403. {
  404. connPtr->state=CONNECTING;
  405. continue; // Move on to next pending conn...
  406. }
  407. else // doh, real problem
  408. {
  409. assert(0);
  410. closesocket(connPtr->fd);
  411. connPtr->fd=createSocket(connPtr->ip, connPtr->port, FALSE);
  412. }
  413. }
  414. if (retval==0)
  415. {
  416. connPtr->state=CONNECTED;
  417. }
  418. }
  419. } // for ConnectArray_;
  420. // Incoming connections
  421. ListenSocket *listenPtr;
  422. struct sockaddr_in clientAddr;
  423. int addrlen;
  424. for (i=0; i<ListenArray_.length(); i++)
  425. {
  426. ListenArray_.getPointer(&listenPtr,i);
  427. while(1) // accept all incoming on each socket
  428. {
  429. addrlen=sizeof(clientAddr);
  430. SOCKET newFD=accept(listenPtr->fd, (struct sockaddr *)&clientAddr, &addrlen);
  431. if (newFD != INVALID_SOCKET)
  432. {
  433. setBlocking(newFD, FALSE);
  434. DBGMSG("Connection accepted");
  435. PendingConn newConn;
  436. newConn.fd=newFD;
  437. newConn.ip=0;
  438. newConn.port=0;
  439. newConn.remoteIp=ntohl(clientAddr.sin_addr.s_addr);
  440. newConn.remotePort=ntohs(clientAddr.sin_port);
  441. newConn.handle=HandleSequence_++;
  442. newConn.state=CONNECTED;
  443. newConn.incoming=TRUE;
  444. newConn.remoteIp=ntohl(clientAddr.sin_addr.s_addr);
  445. newConn.remotePort=ntohs(clientAddr.sin_port);
  446. ConnectArray_.addTail(newConn);
  447. }
  448. else
  449. break;
  450. }
  451. }
  452. return;
  453. }