Freeciv21
Develop your civilization from humble roots to a global empire
servers.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 1996-2021 Freeciv21 and Freeciv contributors. This file is
3  part of Freeciv21. Freeciv21 is free software: you can
4  ^oo^ redistribute it and/or modify it under the terms of the GNU
5  (..) General Public License as published by the Free Software
6  () () Foundation, either version 3 of the License, or (at your
7  ()__() option) any later version. You should have received
8  a copy of the GNU General Public License along with Freeciv21. If not,
9  see https://www.gnu.org/licenses/.
10  */
11 
12 #include <fc_config.h>
13 
14 #include "servers.h"
15 
16 // Qt
17 #include <QBuffer>
18 #include <QByteArray>
19 #include <QEventLoop>
20 #include <QNetworkAccessManager>
21 #include <QNetworkReply>
22 #include <QUdpSocket>
23 #include <QUrlQuery>
24 
25 // dependencies
26 #include "cvercmp.h"
27 
28 // utility
29 #include "net_types.h"
30 #include "registry_ini.h"
31 
32 // generated
33 #include "fc_version.h"
34 
35 // common
36 #include "capstr.h"
37 #include "dataio_raw.h"
38 #include "version.h"
39 
40 // client
41 #include "chatline.h"
42 #include "client_main.h"
43 
44 struct server_scan {
47 
48  struct server_list *servers;
49  int sock;
50 
51  // Only used for metaserver
52  struct {
54 
55  const char *urlpath;
56  QByteArray mem;
57  } meta;
58 };
59 
61 extern enum announce_type announce;
62 
63 static bool begin_metaserver_scan(struct server_scan *scan);
64 static void delete_server_list(struct server_list *server_list);
65 
66 fcUdpScan::fcUdpScan(QObject *parent) : QUdpSocket(parent)
67 {
68  fcudp_scan = nullptr;
69  connect(this, &QUdpSocket::readyRead, this,
71  connect(this, &QAbstractSocket::errorOccurred, this,
73 }
74 
80 {
81  if (!m_instance) {
82  m_instance = new fcUdpScan;
83  }
84  return m_instance;
85 }
86 
90 void fcUdpScan::sockError(QAbstractSocket::SocketError socketError)
91 {
92  Q_UNUSED(socketError)
93  char *errstr;
94  if (!fcudp_scan) {
95  return;
96  }
97  errstr = errorString().toLocal8Bit().data();
99 }
100 
105 {
106  delete m_instance;
107  m_instance = nullptr;
108 }
109 
116 {
117  const char *group;
118  struct raw_data_out dout;
119  char buffer[MAX_LEN_PACKET];
120  enum QHostAddress::SpecialAddress address_type;
121  size_t size;
122 
123  fcudp_scan = scan;
124  if (announce == ANNOUNCE_NONE) {
125  // Succeeded in doing nothing
126  return true;
127  }
129 
130  switch (announce) {
131  case ANNOUNCE_IPV6:
132  address_type = QHostAddress::AnyIPv6;
133  break;
134  case ANNOUNCE_IPV4:
135  default:
136  address_type = QHostAddress::AnyIPv4;
137  }
138 
139  if (!bind(address_type, SERVER_LAN_PORT + 1,
140  QAbstractSocket::ReuseAddressHint)) {
141  return false;
142  }
143 
144  joinMulticastGroup(QHostAddress(group));
145 
146  dio_output_init(&dout, buffer, sizeof(buffer));
148  size = dio_output_used(&dout);
149  writeDatagram(QByteArray(buffer, size), QHostAddress(group),
151  scan->servers = server_list_new();
152 
153  return true;
154 }
155 
157 {
158  while (hasPendingDatagrams()) {
159  bool add = true;
160  QNetworkDatagram qn = receiveDatagram();
161  for (auto const &d : qAsConst(datagram_list)) {
162  QByteArray d1 = d.data();
163  QByteArray d2 = qn.data();
164  if (d1.data() == d2.data()) {
165  add = false;
166  }
167  }
168  if (add) {
169  datagram_list.append(qn);
170  }
171  }
172 }
173 
179 {
180  int type;
181  struct data_in din;
182  char servername[512];
183  char portstr[256];
184  int port;
185  char version[256];
186  char status[256];
187  char players[256];
188  char humans[256];
189  char message[1024];
190  bool found_new = false;
191 
192  struct server *pserver;
193 
194  for (auto const &datagram : qAsConst(datagram_list)) {
195  if (datagram.isNull() || !datagram.isValid()) {
196  continue;
197  }
198  auto data = datagram.data();
199  dio_input_init(&din, data.constData(), data.size());
200 
203  dio_get_string_raw(&din, servername, sizeof(servername)),
205  fc_assert_ret_val(dio_get_string_raw(&din, portstr, sizeof(portstr)),
207  port = atoi(portstr);
210  fc_assert_ret_val(dio_get_string_raw(&din, status, sizeof(status)),
218 
219  if (!fc_strcasecmp("none", servername)) {
220  sz_strlcpy(servername,
221  datagram.senderAddress().toString().toLocal8Bit());
222  }
223 
224  log_debug("Received a valid announcement from a server on the LAN.");
225 
226  pserver = new server;
227  pserver->host = fc_strdup(servername);
228  pserver->port = port;
229  pserver->version = fc_strdup(version);
230  pserver->state = fc_strdup(status);
231  pserver->nplayers = atoi(players);
232  pserver->humans = atoi(humans);
233  pserver->message = fc_strdup(message);
234  pserver->players = nullptr;
235  found_new = true;
236 
237  server_list_prepend(scan->servers, pserver);
238  }
239  datagram_list.clear();
240  if (found_new) {
241  return SCAN_STATUS_PARTIAL;
242  }
243  return SCAN_STATUS_WAITING;
244 }
245 
250 static struct server_list *parse_metaserver_data(QIODevice *f)
251 {
252  struct server_list *server_list;
253  struct section_file *file;
254  int nservers, i, j;
255  const char *latest_ver;
256  const char *comment;
257 
258  // This call closes f.
259  if (!(file = secfile_from_stream(f, true))) {
260  return nullptr;
261  }
262 
263  latest_ver =
264  secfile_lookup_str_default(file, nullptr, "versions." FOLLOWTAG);
265  comment = secfile_lookup_str_default(file, nullptr,
266  "version_comments." FOLLOWTAG);
267 
268  if (latest_ver != nullptr) {
269  const char *my_comparable = freeciv21_version();
270  char vertext[2048];
271 
272  qDebug("Metaserver says latest '" FOLLOWTAG
273  "' version is '%s'; we have '%s'",
274  latest_ver, my_comparable);
275  if (cvercmp_greater(latest_ver, my_comparable)) {
276  const char *const followtag = "?vertag:" FOLLOWTAG;
277  fc_snprintf(vertext, sizeof(vertext),
278  /* TRANS: Type is version tag name like "stable", "S2_4",
279  * "win32" (which can also be localised -- msgids start
280  * '?vertag:') */
281  _("Latest %s release of Freeciv21 is %s, this is %s."),
282  Q_(followtag), latest_ver, my_comparable);
283 
284  version_message(vertext);
285  } else if (comment == nullptr) {
286  fc_snprintf(vertext, sizeof(vertext),
287  _("There is no newer %s release of Freeciv21 available."),
288  FOLLOWTAG);
289 
290  version_message(vertext);
291  }
292  }
293 
294  if (comment != nullptr) {
295  qDebug("Mesaserver comment about '" FOLLOWTAG "': %s", comment);
296  version_message(comment);
297  }
298 
299  server_list = server_list_new();
300  nservers = secfile_lookup_int_default(file, 0, "main.nservers");
301 
302  for (i = 0; i < nservers; i++) {
303  const char *host, *port, *version, *state, *message, *nplayers, *nhumans;
304  int n;
305  struct server *pserver = new server;
306 
307  host = secfile_lookup_str_default(file, "", "server%d.host", i);
308  pserver->host = fc_strdup(host);
309 
310  port = secfile_lookup_str_default(file, "", "server%d.port", i);
311  pserver->port = atoi(port);
312 
313  version = secfile_lookup_str_default(file, "", "server%d.version", i);
314  pserver->version = fc_strdup(version);
315 
316  state = secfile_lookup_str_default(file, "", "server%d.state", i);
317  pserver->state = fc_strdup(state);
318 
319  message = secfile_lookup_str_default(file, "", "server%d.message", i);
320  pserver->message = fc_strdup(message);
321 
322  nplayers = secfile_lookup_str_default(file, "0", "server%d.nplayers", i);
323  n = atoi(nplayers);
324  pserver->nplayers = n;
325 
326  nhumans = secfile_lookup_str_default(file, "-1", "server%d.humans", i);
327  n = atoi(nhumans);
328  pserver->humans = n;
329 
330  if (pserver->nplayers > 0) {
331  pserver->players = new str_players[pserver->nplayers];
332  } else {
333  pserver->players = nullptr;
334  }
335 
336  for (j = 0; j < pserver->nplayers; j++) {
337  const char *name, *nation, *type, *plrhost;
338 
339  name = secfile_lookup_str_default(file, "", "server%d.player%d.name",
340  i, j);
341  pserver->players[j].name = fc_strdup(name);
342 
343  type = secfile_lookup_str_default(file, "", "server%d.player%d.type",
344  i, j);
345  pserver->players[j].type = fc_strdup(type);
346 
347  plrhost = secfile_lookup_str_default(file, "",
348  "server%d.player%d.host", i, j);
349  pserver->players[j].host = fc_strdup(plrhost);
350 
351  nation = secfile_lookup_str_default(file, "",
352  "server%d.player%d.nation", i, j);
353  pserver->players[j].nation = fc_strdup(nation);
354  }
355 
356  server_list_append(server_list, pserver);
357  }
358 
359  secfile_destroy(file);
360 
361  return server_list;
362 }
363 
367 static bool meta_read_response(struct server_scan *scan)
368 {
369  char str[4096];
370  struct server_list *srvrs;
371 
372  auto *f = new QBuffer(&scan->meta.mem);
373  f->open(QIODevice::ReadWrite);
374 
375  // parse message body
376  srvrs = parse_metaserver_data(f);
377  scan->servers = srvrs;
378 
379  // 'f' (hence 'meta.mem') was closed in parse_metaserver_data().
380  scan->meta.mem.clear();
381 
382  if (nullptr == srvrs) {
383  fc_snprintf(str, sizeof(str),
384  _("Failed to parse the metaserver data from %s:\n"
385  "%s."),
386  qUtf8Printable(cmd_metaserver), secfile_error());
387  scan->error_func(scan, str);
388 
389  return false;
390  }
391 
392  return true;
393 }
394 
398 static void metaserver_scan(void *arg)
399 {
400  struct server_scan *scan = static_cast<server_scan *>(arg);
401 
402  if (!begin_metaserver_scan(scan)) {
403  scan->meta.status = SCAN_STATUS_ERROR;
404  } else {
405  if (!meta_read_response(scan)) {
406  scan->meta.status = SCAN_STATUS_ERROR;
407  } else {
408  if (scan->meta.status == SCAN_STATUS_WAITING) {
409  scan->meta.status = SCAN_STATUS_DONE;
410  }
411  }
412  }
413 }
414 
421 static bool begin_metaserver_scan(struct server_scan *scan)
422 {
423  // Create a network manager
424  auto *manager = new QNetworkAccessManager;
425 
426  // Post the request
427  QUrlQuery post;
428  post.addQueryItem(QStringLiteral("client_cap"),
429  QString::fromUtf8(our_capability));
430 
431  QNetworkRequest request(cmd_metaserver);
432  request.setHeader(QNetworkRequest::UserAgentHeader,
433  QLatin1String("Freeciv21/") + freeciv21_version());
434  request.setHeader(QNetworkRequest::ContentTypeHeader,
435  QLatin1String("application/x-www-form-urlencoded"));
436  auto *reply =
437  manager->post(request, post.toString(QUrl::FullyEncoded).toUtf8());
438 
439  // Read from the reply
440  bool retval = true;
441  QEventLoop loop; // Need an event loop for QNetworkReply to work
442 
443  QObject::connect(reply, &QNetworkReply::finished, [&] {
444  if (reply->error() == QNetworkReply::NoError) {
445  scan->meta.mem = reply->readAll();
446  } else {
447  // Error
448  scan->error_func(scan, _("Error connecting to metaserver"));
449  qCritical(_("Error message: %s"),
450  qUtf8Printable(reply->errorString()));
451 
452  scan->meta.mem.clear();
453  retval = false;
454  }
455 
456  // Clean up
457  reply->deleteLater();
458  manager->deleteLater();
459 
460  loop.quit();
461  });
462 
463  loop.exec();
464 
465  return retval;
466 }
467 
473 static void delete_server_list(struct server_list *server_list)
474 {
475  if (!server_list) {
476  return;
477  }
478 
479  server_list_iterate(server_list, ptmp)
480  {
481  int i;
482  int n = ptmp->nplayers;
483 
484  delete[] ptmp->host;
485  delete[] ptmp->version;
486  delete[] ptmp->state;
487  delete[] ptmp->message;
488 
489  if (ptmp->players) {
490  for (i = 0; i < n; i++) {
491  delete[] ptmp->players[i].name;
492  delete[] ptmp->players[i].type;
493  delete[] ptmp->players[i].host;
494  delete[] ptmp->players[i].nation;
495  }
496  delete[] ptmp->players;
497  }
498 
499  delete ptmp;
500  }
502 
503  server_list_destroy(server_list);
504 }
505 
520 {
521  struct server_scan *scan;
522 
523  scan = new server_scan;
524  scan->type = type;
525  scan->error_func = error_func;
526  scan->servers = nullptr;
527 
528  switch (type) {
529  case SERVER_SCAN_GLOBAL:
530  metaserver_scan(scan);
531  break;
532  case SERVER_SCAN_LOCAL:
533  fcUdpScan::i()->begin_scan(scan);
534  break;
535  default:
536  break;
537  }
538 
539  return scan;
540 }
541 
546 enum server_scan_type server_scan_get_type(const struct server_scan *scan)
547 {
548  if (!scan) {
549  return SERVER_SCAN_LAST;
550  }
551  return scan->type;
552 }
553 
570 {
571  if (!scan) {
572  return SCAN_STATUS_ERROR;
573  }
574 
575  switch (scan->type) {
576  case SERVER_SCAN_GLOBAL: {
578  status = scan->meta.status;
579  return status;
580  } break;
581  case SERVER_SCAN_LOCAL:
582  return fcUdpScan::i()->get_server_list(scan);
583  break;
584  default:
585  break;
586  }
587 
588  return SCAN_STATUS_ERROR;
589 }
590 
594 struct server_list *server_scan_get_list(struct server_scan *scan)
595 {
596  if (!scan) {
597  return nullptr;
598  }
599 
600  return scan->servers;
601 }
602 
607 void server_scan_finish(struct server_scan *scan)
608 {
609  if (!scan) {
610  return;
611  }
612 
613  if (scan->type == SERVER_SCAN_GLOBAL) {
614  // Signal metaserver scan thread to stop
615  scan->meta.status = SCAN_STATUS_ABORT;
616 
617  if (scan->servers) {
619  scan->servers = nullptr;
620  }
621  } else {
622  fcUdpScan::i()->drop();
623  if (scan->servers) {
625  scan->servers = nullptr;
626  }
627  }
628 
629  delete scan;
630 }
const char *const our_capability
Definition: capstr.cpp:28
void version_message(const QString &vertext)
Got version message from metaserver.
Definition: chatline.cpp:897
QList< QNetworkDatagram > datagram_list
Definition: servers.h:46
fcUdpScan(QObject *parent=0)
Definition: servers.cpp:66
static fcUdpScan * i()
returns fcUdpScan instance, use it to initizalize fcUdpScan or for calling methods
Definition: servers.cpp:79
static void drop()
deletes fcUdpScan
Definition: servers.cpp:104
bool begin_scan(struct server_scan *scan)
Broadcast an UDP package to all servers on LAN, requesting information about the server.
Definition: servers.cpp:115
void readPendingDatagrams()
Definition: servers.cpp:156
enum server_scan_status get_server_list(struct server_scan *scan)
Listens for UDP packets broadcasted from a server that responded to the request-packet sent from the ...
Definition: servers.cpp:178
struct server_scan * fcudp_scan
Definition: servers.h:43
static fcUdpScan * m_instance
Definition: servers.h:45
void sockError(QAbstractSocket::SocketError socketError)
Pass errors to main scan.
Definition: servers.cpp:90
QString cmd_metaserver
#define MAX_LEN_PACKET
Definition: connection.h:41
void dio_output_init(struct raw_data_out *dout, void *destination, size_t dest_size)
Initializes the output to the given output buffer and the given buffer size.
Definition: dataio_raw.cpp:144
void dio_put_uint8_raw(struct raw_data_out *dout, int value)
Insert value using 8 bits.
Definition: dataio_raw.cpp:229
bool dio_get_uint8_raw(struct data_in *din, int *dest)
Receive uint8 value to dest.
Definition: dataio_raw.cpp:492
size_t dio_output_used(struct raw_data_out *dout)
Return the maximum number of bytes used.
Definition: dataio_raw.cpp:157
void dio_input_init(struct data_in *din, const void *src, size_t src_size)
Initializes the input to the given input buffer and the given number of valid input bytes.
Definition: dataio_raw.cpp:169
bool dio_get_string_raw(struct data_in *din, char *dest, size_t max_dest_size)
Take string.
Definition: dataio_raw.cpp:725
#define Q_(String)
Definition: fcintl.h:53
#define _(String)
Definition: fcintl.h:50
const char * name
Definition: inputfile.cpp:118
#define fc_assert_ret_val(condition, val)
Definition: log.h:114
#define log_debug(message,...)
Definition: log.h:65
announce_type
Definition: net_types.h:19
@ ANNOUNCE_IPV6
Definition: net_types.h:19
@ ANNOUNCE_IPV4
Definition: net_types.h:19
@ ANNOUNCE_NONE
Definition: net_types.h:19
const char * secfile_error()
Returns the last error which occurred in a string.
void secfile_destroy(struct section_file *secfile)
Free a section file.
struct section_file * secfile_from_stream(QIODevice *stream, bool allow_duplicates)
Create a section file from a stream.
const char * secfile_lookup_str_default(const struct section_file *secfile, const char *def, const char *path,...)
Lookup a string value in the secfile.
int secfile_lookup_int_default(const struct section_file *secfile, int def, const char *path,...)
Lookup a integer value in the secfile.
#define SERVER_LAN_VERSION
Definition: sernet.h:29
#define SERVER_LAN_PORT
Definition: sernet.h:27
struct server_list * server_scan_get_list(struct server_scan *scan)
Returns the srv_list currently held by the scan (may be nullptr).
Definition: servers.cpp:594
static void delete_server_list(struct server_list *server_list)
Frees everything associated with a server list including the server list itself (so the server_list i...
Definition: servers.cpp:473
static bool begin_metaserver_scan(struct server_scan *scan)
Begin a metaserver scan for servers.
Definition: servers.cpp:421
static bool meta_read_response(struct server_scan *scan)
Read the reply string from the metaserver.
Definition: servers.cpp:367
enum announce_type announce
static void metaserver_scan(void *arg)
Metaserver scan thread entry point.
Definition: servers.cpp:398
struct server_scan * server_scan_begin(enum server_scan_type type, ServerScanErrorFunc error_func)
Creates a new server scan and returns it, or nullptr if impossible.
Definition: servers.cpp:518
void server_scan_finish(struct server_scan *scan)
Closes the socket listening on the scan, frees the list of servers, and frees the memory allocated fo...
Definition: servers.cpp:607
static struct server_list * parse_metaserver_data(QIODevice *f)
The server sends a stream in a registry 'ini' type format.
Definition: servers.cpp:250
enum server_scan_type server_scan_get_type(const struct server_scan *scan)
A simple query function to determine the type of a server scan (previously allocated in server_scan_b...
Definition: servers.cpp:546
enum server_scan_status server_scan_poll(struct server_scan *scan)
A function to query servers of the server scan.
Definition: servers.cpp:569
#define server_list_iterate_end
Definition: servers.h:75
server_scan_status
Definition: servers.h:21
@ SCAN_STATUS_WAITING
Definition: servers.h:23
@ SCAN_STATUS_ABORT
Definition: servers.h:26
@ SCAN_STATUS_PARTIAL
Definition: servers.h:24
@ SCAN_STATUS_ERROR
Definition: servers.h:22
@ SCAN_STATUS_DONE
Definition: servers.h:25
#define server_list_iterate(serverlist, pserver)
Definition: servers.h:73
server_scan_type
Definition: servers.h:79
@ SERVER_SCAN_LOCAL
Definition: servers.h:80
@ SERVER_SCAN_LAST
Definition: servers.h:82
@ SERVER_SCAN_GLOBAL
Definition: servers.h:81
void(* ServerScanErrorFunc)(struct server_scan *scan, const char *message)
Definition: servers.h:85
char * get_multicast_group(bool ipv6_preferred)
Returns string which gives the multicast group IP address for finding servers on the LAN,...
Definition: shared.cpp:1065
size_t size
Definition: specvec.h:64
struct civserver server
Definition: srv_main.cpp:121
ServerScanErrorFunc error_func
Definition: servers.cpp:46
struct server_list * servers
Definition: servers.cpp:48
enum server_scan_status status
Definition: servers.cpp:53
QByteArray mem
Definition: servers.cpp:56
enum server_scan_type type
Definition: servers.cpp:45
struct server_scan::@143 meta
const char * urlpath
Definition: servers.cpp:55
Definition: servers.h:55
int humans
Definition: servers.h:66
char * host
Definition: servers.h:56
char * version
Definition: servers.h:60
char * message
Definition: servers.h:63
char * state
Definition: servers.h:61
str_players * players
Definition: servers.h:64
int port
Definition: servers.h:57
int nplayers
Definition: servers.h:65
char * host
Definition: servers.h:52
char * name
Definition: servers.h:50
char * type
Definition: servers.h:51
char * nation
Definition: servers.h:53
int fc_snprintf(char *str, size_t n, const char *format,...)
See also fc_utf8_snprintf_trunc(), fc_utf8_snprintf_rep().
Definition: support.cpp:537
int fc_strcasecmp(const char *str0, const char *str1)
Compare strings like strcmp(), but ignoring case.
Definition: support.cpp:89
#define sz_strlcpy(dest, src)
Definition: support.h:140
#define fc_strdup(str)
Definition: support.h:111
const char * freeciv21_version()
Returns the raw version string.
Definition: version.cpp:29