1 /** @file serversystem.cpp  Subsystem for tending to clients.
2  *
3  * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2014 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #include "serversystem.h"
21 
22 #include <de/c_wrapper.h>
23 #include <de/timer.h>
24 #include <de/Address>
25 #include <de/Beacon>
26 #include <de/ByteRefArray>
27 #include <de/Garbage>
28 #include <de/ListenSocket>
29 #include <de/TextApp>
30 
31 #include "api_console.h"
32 
33 #include "serverapp.h"
34 #include "shellusers.h"
35 #include "remoteuser.h"
36 #include "remotefeeduser.h"
37 
38 #include "server/sv_def.h"
39 #include "server/sv_frame.h"
40 
41 #include "network/net_main.h"
42 #include "network/net_buf.h"
43 #include "network/net_event.h"
44 #include "network/monitor.h"
45 #include "network/masterserver.h"
46 
47 #include "dd_main.h"
48 #include "dd_loop.h"
49 #include "sys_system.h"
50 #include "world/map.h"
51 #include "world/p_players.h"
52 
53 using namespace de;
54 
55 char *nptIPAddress = (char *) ""; ///< Public domain for clients to connect to (cvar).
56 int   nptIPPort    = 0; ///< Server TCP port (cvar).
57 
Server_ListenPort()58 static de::duint16 Server_ListenPort()
59 {
60     return (!nptIPPort ? DEFAULT_TCP_PORT : nptIPPort);
61 }
62 
DENG2_PIMPL(ServerSystem)63 DENG2_PIMPL(ServerSystem)
64 {
65     bool inited = false;
66 
67     /// Beacon for informing clients that a server is present.
68     Beacon beacon = { DEFAULT_UDP_PORT };
69     Time lastBeaconUpdateAt;
70 
71     ListenSocket *serverSock = nullptr;
72 
73     QHash<Id, RemoteUser *> users;
74     ShellUsers shellUsers;
75     Users remoteFeedUsers;
76 
77     Impl(Public *i) : Base(i) {}
78     ~Impl() { deinit(); }
79 
80     bool isStarted() const
81     {
82         return serverSock != nullptr;
83     }
84 
85     bool init(duint16 port)
86     {
87         // Note: re-initialization is allowed, so we don't check for inited now.
88 
89         LOG_NET_NOTE("Server listening on TCP port %i") << port;
90 
91         deinit();
92 
93         // Open a listening TCP socket. It will accept client connections.
94         DENG2_ASSERT(!serverSock);
95         if (!(serverSock = new ListenSocket(port)))
96             return false;
97 
98         QObject::connect(serverSock, SIGNAL(incomingConnection()), thisPublic, SLOT(handleIncomingConnection()));
99 
100         // Update the beacon with the new port.
101         beacon.start(port);
102 
103         App_World().audienceForMapChange() += shellUsers;
104 
105         inited = true;
106         return true;
107     }
108 
109     void clearUsers()
110     {
111         // Clear the client nodes.
112         for (RemoteUser *u : users.values())
113         {
114             delete u;
115         }
116         DENG2_ASSERT(users.isEmpty());
117     }
118 
119     void deinit()
120     {
121         if (!inited) return;
122         inited = false;
123 
124         if (ServerApp::appExists())
125         {
126             App_World().audienceForMapChange() -= shellUsers;
127         }
128 
129         beacon.stop();
130 
131         // Close the listening socket.
132         delete serverSock;
133         serverSock = 0;
134 
135         clearUsers();
136     }
137 
138     RemoteUser &findUser(Id const &id) const
139     {
140         DENG2_ASSERT(users.contains(id));
141         return *users[id];
142     }
143 
144     void updateBeacon(Clock const &clock)
145     {
146         if (lastBeaconUpdateAt.since() > 0.5)
147         {
148             lastBeaconUpdateAt = clock.time();
149 
150             // Update the status message in the server's presence beacon.
151             if (serverSock && App_World().hasMap())
152             {
153                 Block msg;
154                 de::Writer(msg).withHeader() << ServerApp::currentServerInfo().strippedForBroadcast();
155                 beacon.setMessage(msg);
156             }
157         }
158     }
159 
160     /**
161      * The client is removed from the game immediately. This is used when
162      * the server needs to terminate a client's connection abnormally.
163      */
164     void terminateNode(Id const &id)
165     {
166         if (id)
167         {
168             DENG2_ASSERT(users.contains(id));
169 
170             delete users[id];
171 
172             DENG2_ASSERT(!users.contains(id));
173         }
174     }
175 
176     void printStatus()
177     {
178         if (serverSock)
179         {
180             LOG_NOTE("SERVER: Listening on TCP port %i") << serverSock->port();
181         }
182         else
183         {
184             LOG_NOTE("SERVER: No server socket open");
185         }
186 
187         int first = true;
188         for (int i = 1; i < DDMAXPLAYERS; ++i)
189         {
190             player_t *plr = DD_Player(i);
191             if (plr->remoteUserId)
192             {
193                 DENG2_ASSERT(users.contains(plr->remoteUserId));
194 
195                 RemoteUser *user = users[plr->remoteUserId];
196                 if (first)
197                 {
198                     LOG_MSG(_E(m) "P# Name:      Nd Jo Hs Rd Gm Age:");
199                     first = false;
200                 }
201 
202                 LOG_MSG(_E(m) "%2i %-10s %2i %c  %c  %c  %c  %f sec")
203                         << i << plr->name << plr->remoteUserId
204                         << (user->isJoined()? '*' : ' ')
205                         << (plr->handshake? '*' : ' ')
206                         << (plr->ready? '*' : ' ')
207                         << (plr->publicData().inGame? '*' : ' ')
208                         << (Timer_RealSeconds() - plr->enterTime);
209             }
210         }
211         if (first)
212         {
213             LOG_MSG("No clients connected");
214         }
215 
216         if (shellUsers.count())
217         {
218             LOG_MSG("%i shell user%s")
219                     << shellUsers.count()
220                     << DENG2_PLURAL_S(shellUsers.count());
221         }
222 
223         if (remoteFeedUsers.count())
224         {
225             LOG_MSG("%i remote file system user%s")
226                     << remoteFeedUsers.count()
227                     << DENG2_PLURAL_S(remoteFeedUsers.count());
228         }
229 
230         N_PrintBufferInfo();
231 
232         LOG_MSG(_E(b) "Configuration:");
233         LOG_MSG("  Port for hosting games (net-ip-port): %i") << Con_GetInteger("net-ip-port");
234         LOG_MSG("  Shell password (server-password): \"%s\"") << netPassword;
235     }
236 };
237 
ServerSystem()238 ServerSystem::ServerSystem() : d(new Impl(this))
239 {}
240 
start(duint16 port)241 void ServerSystem::start(duint16 port)
242 {
243     d->init(port);
244 }
245 
stop()246 void ServerSystem::stop()
247 {
248     d->deinit();
249 }
250 
isListening() const251 bool ServerSystem::isListening() const
252 {
253     return d->isStarted();
254 }
255 
terminateNode(Id const & id)256 void ServerSystem::terminateNode(Id const &id)
257 {
258     d->terminateNode(id);
259 }
260 
user(Id const & id) const261 RemoteUser &ServerSystem::user(Id const &id) const
262 {
263     if (!d->users.contains(id))
264     {
265         throw IdError("ServerSystem::user", "User " + id.asText() + " does not exist");
266     }
267     return *d->users[id];
268 }
269 
isUserAllowedToJoin(RemoteUser &) const270 bool ServerSystem::isUserAllowedToJoin(RemoteUser &/*user*/) const
271 {
272     if (!CVar_Byte(Con_FindVariable("server-allowjoin"))) return false;
273     // If the server is full, attempts to connect are canceled.
274     return (Sv_GetNumConnected() < svMaxPlayers);
275 }
276 
convertToShellUser(RemoteUser * user)277 void ServerSystem::convertToShellUser(RemoteUser *user)
278 {
279     DENG2_ASSERT(user);
280     LOG_AS("convertToShellUser");
281 
282     Socket *socket = user->takeSocket();
283 
284     LOGDEV_NET_VERBOSE("Remote user %s converted to shell user") << user->id();
285     user->deleteLater();
286 
287     d->shellUsers.add(new ShellUser(socket));
288 }
289 
convertToRemoteFeedUser(RemoteUser * user)290 void ServerSystem::convertToRemoteFeedUser(RemoteUser *user)
291 {
292     DENG2_ASSERT(user);
293 
294     Socket *socket = user->takeSocket();
295     LOGDEV_NET_VERBOSE("Remote user %s converted to remote file system user") << user->id();
296     user->deleteLater();
297 
298     d->remoteFeedUsers.add(new RemoteFeedUser(socket));
299 }
300 
userCount() const301 int ServerSystem::userCount() const
302 {
303     return d->remoteFeedUsers.count() +
304            d->shellUsers.count() +
305            d->users.size();
306 }
307 
timeChanged(Clock const & clock)308 void ServerSystem::timeChanged(Clock const &clock)
309 {
310     if (Sys_IsShuttingDown())
311         return; // Shouldn't run this while shutting down.
312 
313     Garbage_Recycle();
314 
315     // Adjust loop rate depending on whether users are connected.
316     DENG2_TEXT_APP->loop().setRate(userCount()? 35 : 3);
317 
318     Loop_RunTics();
319 
320     // Update clients at regular intervals.
321     Sv_TransmitFrame();
322 
323     d->updateBeacon(clock);
324 
325     /// @todo There's no need to queue packets via net_buf, just handle
326     /// them right away.
327     Sv_GetPackets();
328 
329     /// @todo Kick unjoined nodes who are silent for too long.
330 }
331 
handleIncomingConnection()332 void ServerSystem::handleIncomingConnection()
333 {
334     LOG_AS("ServerSystem");
335     forever
336     {
337         Socket *sock = d->serverSock->accept();
338         if (!sock) break;
339 
340         RemoteUser *user = new RemoteUser(sock);
341         connect(user, SIGNAL(userDestroyed()), this, SLOT(userDestroyed()));
342         d->users.insert(user->id(), user);
343 
344         // Immediately handle pending messages, if there are any.
345         user->handleIncomingPackets();
346     }
347 }
348 
userDestroyed()349 void ServerSystem::userDestroyed()
350 {
351     RemoteUser *u = static_cast<RemoteUser *>(sender());
352 
353     LOG_AS("ServerSystem");
354     LOGDEV_NET_VERBOSE("Removing user %s") << u->id();
355 
356     d->users.remove(u->id());
357 
358     LOG_NET_VERBOSE("Remaining user count: %i remote, %i shell, %i filesys")
359             << d->users.size()
360             << d->shellUsers.count()
361             << d->remoteFeedUsers.count();
362 }
363 
printStatus()364 void ServerSystem::printStatus()
365 {
366     d->printStatus();
367 }
368 
App_ServerSystem()369 ServerSystem &App_ServerSystem()
370 {
371     return ServerApp::serverSystem();
372 }
373 
374 //---------------------------------------------------------------------------
375 
Server_Register()376 void Server_Register()
377 {
378     C_VAR_CHARPTR("net-ip-address", &nptIPAddress, 0, 0, 0);
379     C_VAR_INT    ("net-ip-port",    &nptIPPort, CVF_NO_MAX, 0, 0);
380 
381 #ifdef _DEBUG
382     C_CMD("netfreq", NULL, NetFreqs);
383 #endif
384 }
385 
N_ServerOpen()386 dd_bool N_ServerOpen()
387 {
388     App_ServerSystem().start(Server_ListenPort());
389 
390     // The game module may have something that needs doing before we actually begin.
391     if (gx.NetServerStart)
392     {
393         gx.NetServerStart(true);
394     }
395 
396     Sv_StartNetGame();
397 
398     // The game DLL might want to do something now that the server is started.
399     if (gx.NetServerStart)
400     {
401         gx.NetServerStart(false);
402     }
403 
404     if (serverPublic)
405     {
406         // Let the master server know that we are running a public server.
407         N_MasterAnnounceServer(true);
408     }
409 
410     return true;
411 }
412 
N_ServerClose()413 dd_bool N_ServerClose()
414 {
415     if (!App_ServerSystem().isListening()) return true;
416 
417     if (serverPublic)
418     {
419         // Bye-bye, master server.
420         N_MAClear();
421         N_MasterAnnounceServer(false);
422     }
423 
424     if (gx.NetServerStop)
425     {
426         gx.NetServerStop(true);
427     }
428 
429     Net_StopGame();
430     Sv_StopNetGame();
431 
432     if (gx.NetServerStop)
433     {
434         gx.NetServerStop(false);
435     }
436 
437     App_ServerSystem().stop();
438     return true;
439 }
440 
N_PrintNetworkStatus()441 void N_PrintNetworkStatus()
442 {
443     App_ServerSystem().printStatus();
444 }
445