// all server side masterserver and pinging functionality #include "cube.h" #ifdef STANDALONE bool resolverwait(const char *name, ENetAddress *address) { return enet_address_set_host(address, name) >= 0; } int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remoteaddress) { int result = enet_socket_connect(sock, &remoteaddress); if(result<0) enet_socket_destroy(sock); return result; } #endif extern struct servercommandline scl; bool usemaster = false; ENetSocket mastersock = ENET_SOCKET_NULL; ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY }; string mastername = AC_MASTER_URI; int masterport = AC_MASTER_PORT; int lastupdatemaster = 0; vector masterout, masterin; int masteroutpos = 0, masterinpos = 0; void disconnectmaster() { if(mastersock == ENET_SOCKET_NULL) return; enet_socket_destroy(mastersock); mastersock = ENET_SOCKET_NULL; masterout.setsize(0); masterin.setsize(0); masteroutpos = masterinpos = 0; masteraddress.host = ENET_HOST_ANY; masteraddress.port = ENET_PORT_ANY; //lastupdatemaster = 0; } ENetSocket connectmaster() { if(!mastername[0] || !usemaster) return ENET_SOCKET_NULL; if(masteraddress.host == ENET_HOST_ANY) { xlog(ACLOG_INFO, "looking up %s:%d...", mastername, masterport); masteraddress.port = masterport; if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; } ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM); if(sock != ENET_SOCKET_NULL && serveraddress.host != ENET_HOST_ANY && enet_socket_bind(sock, &serveraddress) < 0) { enet_socket_destroy(sock); sock = ENET_SOCKET_NULL; } if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0) { xlog(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); return ENET_SOCKET_NULL; } enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); return sock; } bool requestmaster(const char *req) { if(mastersock == ENET_SOCKET_NULL) { mastersock = connectmaster(); if(mastersock == ENET_SOCKET_NULL) return false; } masterout.put(req, strlen(req)); return true; } bool requestmasterf(const char *fmt, ...) { defvformatstring(req, fmt, fmt); return requestmaster(req); } extern void processmasterinput(const char *cmd, int cmdlen, const char *args); void processmasterinput() { if(masterinpos >= masterin.length()) return; char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos); while(end) { *end++ = '\0'; const char *args = input; while(args < end && !isspace(*args)) args++; int cmdlen = args - input; while(args < end && isspace(*args)) args++; if(!strncmp(input, "failreg", cmdlen)) xlog(ACLOG_WARNING, "master server registration failed: %s", args); else if(!strncmp(input, "succreg", cmdlen)) { xlog(ACLOG_INFO, "master server registration succeeded"); } else processmasterinput(input, cmdlen, args); masterinpos = end - masterin.getbuf(); input = end; end = (char *)memchr(input, '\n', masterin.length() - masterinpos); } if(masterinpos >= masterin.length()) { masterin.setsize(0); masterinpos = 0; } } void flushmasteroutput() { if(masterout.empty()) return; ENetBuffer buf; buf.data = &masterout[masteroutpos]; buf.dataLength = masterout.length() - masteroutpos; int sent = enet_socket_send(mastersock, NULL, &buf, 1); if(sent >= 0) { masteroutpos += sent; if(masteroutpos >= masterout.length()) { masterout.setsize(0); masteroutpos = 0; } } else disconnectmaster(); } void flushmasterinput() { if(masterin.length() >= masterin.capacity()) masterin.reserve(4096); ENetBuffer buf; buf.data = masterin.getbuf() + masterin.length(); buf.dataLength = masterin.capacity() - masterin.length(); int recv = enet_socket_receive(mastersock, NULL, &buf, 1); if(recv > 0) { masterin.advance(recv); processmasterinput(); } else disconnectmaster(); } extern int totalclients; // send alive signal to masterserver after 40 minutes of uptime and if currently in intermission (so theoretically <= 1 hour) // TODO?: implement a thread to drop this "only in intermission" business, we'll need it once AUTH gets active! static inline void updatemasterserver(int millis, int port, int _interm) { if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { string servdesc; extern string servpubkey; filtertext(servdesc, scl.servdesc_full[0] ? scl.servdesc_full : "noname", FTXT__GLOBALNAME, 20); if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? servpubkey : "nokey", AC_VERSION); lastupdatemaster = millis + 1; } } ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL; extern int getpongflags(enet_uint32 ip); void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm) { flushmasteroutput(); updatemasterserver(millis, localaddr.port, _interm); static ENetSocketSet sockset; ENET_SOCKETSET_EMPTY(sockset); ENetSocket maxsock = pongsock; ENET_SOCKETSET_ADD(sockset, pongsock); if(mastersock != ENET_SOCKET_NULL) { maxsock = max(maxsock, mastersock); ENET_SOCKETSET_ADD(sockset, mastersock); } if(lansock != ENET_SOCKET_NULL) { maxsock = max(maxsock, lansock); ENET_SOCKETSET_ADD(sockset, lansock); } if(enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0) return; // reply all server info requests static uchar data[MAXTRANS]; ENetBuffer buf; ENetAddress addr; buf.data = data; int len; loopi(2) { ENetSocket sock = i ? lansock : pongsock; if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(sockset, sock)) continue; buf.dataLength = sizeof(data); len = enet_socket_receive(sock, &addr, &buf, 1); if(len < 0) continue; // ping & pong buf ucharbuf pi(data, len), po(&data[len], sizeof(data)-len); bool std = false; if(getint(pi) != 0) // std pong { extern struct servercommandline scl; (*mnum)++; *mrec += len; std = true; putint(po, protocol_version); putint(po, mode); putint(po, numplayers); putint(po, minremain); sendstring(smapname, po); sendstring(servdesccur, po); putint(po, scl.maxclients); putint(po, getpongflags(addr.host)); if(pi.remaining()) { int query = getint(pi); switch(query) { case EXTPING_NAMELIST: { extern void extping_namelist(ucharbuf &p); putint(po, query); extping_namelist(po); break; } case EXTPING_SERVERINFO: { extern void extping_serverinfo(ucharbuf &pi, ucharbuf &po); putint(po, query); extping_serverinfo(pi, po); break; } case EXTPING_MAPROT: { extern void extping_maprot(ucharbuf &po); putint(po, query); extping_maprot(po); break; } case EXTPING_UPLINKSTATS: { extern void extping_uplinkstats(ucharbuf &po); putint(po, query); extping_uplinkstats(po); break; } case EXTPING_NOP: default: putint(po, EXTPING_NOP); break; } } } else // ext pong - additional server infos { (*cnum)++; *crec += len; int extcmd = getint(pi); putint(po, EXT_ACK); putint(po, EXT_VERSION); switch(extcmd) { case EXT_UPTIME: // uptime in seconds { putint(po, uint(millis)/1000); break; } case EXT_PLAYERSTATS: // playerstats { int cn = getint(pi); // get requested player, -1 for all if(!valid_client(cn) && cn != -1) { putint(po, EXT_ERROR); break; } putint(po, EXT_ERROR_NONE); // add no error flag int bpos = po.length(); // remember buffer position putint(po, EXT_PLAYERSTATS_RESP_IDS); // send player ids following extinfo_cnbuf(po, cn); *csend += int(buf.dataLength = len + po.length()); enet_socket_send(pongsock, &addr, &buf, 1); // send all available player ids po.len = bpos; extinfo_statsbuf(po, cn, bpos, pongsock, addr, buf, len, csend); return; } case EXT_TEAMSCORE: extinfo_teamscorebuf(po); break; default: putint(po,EXT_ERROR); break; } } buf.dataLength = len + po.length(); entropy_add_byte(addr.host ^ (addr.host >> 24)); enet_socket_send(pongsock, &addr, &buf, 1); if(std) *msend += (int)buf.dataLength; else *csend += (int)buf.dataLength; } if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput(); } void servermsinit(bool listen) { if(scl.master && *scl.master) copystring(mastername, scl.master); usemaster = !listen || (listen && scl.master && scl.ssk && scl.maxclients <= MAXCLIENTSONMASTER); // true: connect to master, false: LAN-only server if(scl.maxclients > MAXCLIENTSONMASTER) mlog(ACLOG_WARNING, "maxclient exceeded: cannot register"); disconnectmaster(); if(listen) { ENetAddress address = { ENET_HOST_ANY, (enet_uint16) CUBE_SERVINFO_PORT(scl.serverport) }; if(*scl.ip) { if(enet_address_set_host(&address, scl.ip) < 0) mlog(ACLOG_WARNING, "server ip \"%s\" not resolved", scl.ip); else serveraddress.host = address.host; } pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) { enet_socket_destroy(pongsock); pongsock = ENET_SOCKET_NULL; } if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket"); else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); address.port = CUBE_SERVINFO_PORT_LAN; lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) { enet_socket_destroy(lansock); lansock = ENET_SOCKET_NULL; } if(lansock == ENET_SOCKET_NULL) mlog(ACLOG_WARNING, "could not create LAN server info socket"); else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); } if(!usemaster) mlog(ACLOG_INFO, "server is LAN-only - without connection to masterserver"); }