From d454a9eb245c7c068587a5962dd33377ca72920c Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Mon, 7 Feb 2022 00:11:51 +0100 Subject: [PATCH 1/7] Restore --bind
functionality This allows to bind the server to a specific address, which is often safer. Support for this option was broken when porting to Qt sockets. --- server/sernet.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/sernet.cpp b/server/sernet.cpp index f8c17267be..4e203414fe 100644 --- a/server/sernet.cpp +++ b/server/sernet.cpp @@ -382,7 +382,10 @@ QTcpServer *server_open_socket() srvarg.bind_addr.isNull() ? qUtf8Printable(srvarg.bind_addr) : "(any)", srvarg.port); - if (server->listen(QHostAddress::Any, srvarg.port)) { + if (server->listen(srvarg.bind_addr.isNull() + ? QHostAddress::Any + : QHostAddress(srvarg.bind_addr), + srvarg.port)) { break; } From 3cfdb99a0b4a682827f4acfbc2c0ced557f5b27d Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Mon, 7 Feb 2022 00:45:18 +0100 Subject: [PATCH 2/7] Add a --local server option to bind to local socket Local sockets won't trigger any firewall rules. --- server/civserver.cpp | 24 +++++++++++++++++++----- server/srv_main.h | 2 ++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/server/civserver.cpp b/server/civserver.cpp index 80802ac6d5..76d7436cb0 100644 --- a/server/civserver.cpp +++ b/server/civserver.cpp @@ -176,6 +176,9 @@ int main(int argc, char *argv[]) _("Listen for clients on ADDR"), // TRANS: Command-line argument _("ADDR")}, + {QStringLiteral("local"), _("Listens to the local socket NAME"), + // TRANS: Command-line argument + _("NAME")}, {{"d", _("debug")}, // TRANS: Do not translate "fatal", "critical", "warning", "info" or // "debug". It's exactly what the user must type. @@ -252,7 +255,7 @@ int main(int argc, char *argv[]) #endif // AI_MODULES }); if (!ok) { - qFatal("Adding command line arguments failed"); + qCritical("Adding command line arguments failed"); exit(EXIT_FAILURE); } @@ -289,18 +292,29 @@ int main(int argc, char *argv[]) if (parser.isSet(QStringLiteral("identity"))) { srvarg.ranklog_filename = parser.value(QStringLiteral("identity")); } + if (parser.isSet(QStringLiteral("local"))) { + srvarg.local_addr = parser.value(QStringLiteral("local")); + } if (parser.isSet(QStringLiteral("port"))) { + if (!srvarg.local_addr.isEmpty()) { + qCritical(_("Cannot use --port with --local")); + exit(EXIT_FAILURE); + } bool conversion_ok; srvarg.port = parser.value(QStringLiteral("port")).toUInt(&conversion_ok); srvarg.user_specified_port = true; if (!conversion_ok) { - qFatal(_("Invalid port number %s"), - qUtf8Printable(parser.value("port"))); + qCritical(_("Invalid port number %s"), + qUtf8Printable(parser.value("port"))); exit(EXIT_FAILURE); } } if (parser.isSet(QStringLiteral("bind"))) { + if (!srvarg.local_addr.isEmpty()) { + qCritical(_("Cannot use --bind with --local")); + exit(EXIT_FAILURE); + } srvarg.bind_addr = parser.value(QStringLiteral("bind")); } if (parser.isSet(QStringLiteral("Bind-meta"))) { @@ -314,8 +328,8 @@ int main(int argc, char *argv[]) srvarg.quitidle = parser.value(QStringLiteral("quitidle")).toUInt(&conversion_ok); if (!conversion_ok) { - qFatal(_("Invalid number %s"), - qUtf8Printable(parser.value("quitidle"))); + qCritical(_("Invalid number %s"), + qUtf8Printable(parser.value("quitidle"))); exit(EXIT_FAILURE); } } diff --git a/server/srv_main.h b/server/srv_main.h index efb5846ffa..b5f0714936 100644 --- a/server/srv_main.h +++ b/server/srv_main.h @@ -29,6 +29,8 @@ struct server_arguments { bool metaconnection_persistent; QString identity_name; unsigned short int metaserver_port; + // Address in case a local socket is used + QString local_addr; // address this server is to listen on (NULL => INADDR_ANY) QString bind_addr; // this server's listen port From d9c82abe74687f11d0fb504fc0276f347a67cb5c Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Mon, 7 Feb 2022 03:44:53 +0100 Subject: [PATCH 3/7] Allow the server to communicate through a local socket Local sockets correspond to UNIX domain sockets on UNIX and named pipes on Windows. No networking operations are attempted in this case (not event the UDP announcements), and the server is strictly restricted to serving the socket. --- client/clinet.cpp | 57 +++++++++------- client/clinet.h | 6 +- client/gui-qt/fc_client.cpp | 4 +- client/gui-qt/fc_client.h | 2 +- client/gui-qt/gui_main.cpp | 2 +- client/gui-qt/qtg_cxxside.h | 2 +- client/gui_interface.cpp | 2 +- client/gui_interface.h | 2 +- client/include/gui_main_g.h | 4 +- common/networking/connection.cpp | 21 ++++-- common/networking/connection.h | 9 +-- server/connecthand.cpp | 2 +- server/connecthand.h | 5 +- server/sernet.cpp | 35 +++++++--- server/sernet.h | 16 ++++- server/server.cpp | 109 +++++++++++++++++++++++-------- server/server.h | 9 ++- 17 files changed, 198 insertions(+), 89 deletions(-) diff --git a/client/clinet.cpp b/client/clinet.cpp index 324fe5e531..c5aacb11d8 100644 --- a/client/clinet.cpp +++ b/client/clinet.cpp @@ -16,6 +16,7 @@ #endif // Qt +#include #include #include #include @@ -133,37 +134,47 @@ static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) (void) fc_strlcpy( errbuf, _("Canceled previous connection, trying new connection."), errbufsize); - client.conn.sock->abort(); + if (auto tcp = qobject_cast(client.conn.sock)) { + tcp->abort(); + } else if (auto local = + qobject_cast(client.conn.sock)) { + local->abort(); + } connect_to = url_copy; } else { (void) fc_strlcpy(errbuf, _("Connection in progress."), errbufsize); return -1; } } + + // Reset + if (client.conn.sock) { + client.conn.sock->disconnect(client.conn.sock); + client.conn.sock->deleteLater(); + } client.conn.used = true; // Now there will be a connection :) - client.conn.sock->disconnect(client.conn.sock); // Connect if (!client.conn.sock) { - client.conn.sock = new QTcpSocket; + auto sock = new QTcpSocket; + client.conn.sock = sock; + QObject::connect( + sock, + QOverload::of(&QAbstractSocket::error), + [] { + if (client.conn.sock != nullptr) { + log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); + real_output_window_append(client.conn.sock->errorString(), NULL); + } + client.conn.used = false; + }); + QObject::connect(sock, &QTcpSocket::connected, + [userName = url.userName()] { + make_connection(client.conn.sock, userName); + }); + sock->connectToHost(url.host(), url.port()); } - QObject::connect( - client.conn.sock, - QOverload::of(&QAbstractSocket::error), - [] { - if (client.conn.sock != nullptr) { - log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); - real_output_window_append(client.conn.sock->errorString(), - nullptr); - } - client.conn.used = false; - }); - QObject::connect(client.conn.sock, &QTcpSocket::connected, - [userName = url.userName()] { - make_connection(client.conn.sock, userName); - }); - client.conn.sock->connectToHost(url.host(), url.port()); return 0; } } // namespace @@ -193,7 +204,7 @@ int connect_to_server(const QUrl &url, char *errbuf, int errbufsize) /** Called after a connection is completed (e.g., in try_to_connect). */ -void make_connection(QTcpSocket *sock, const QString &username) +void make_connection(QIODevice *sock, const QString &username) { struct packet_server_join_req req; @@ -261,7 +272,7 @@ void disconnect_from_server() */ static int read_from_connection(struct connection *pc, bool block) { - QTcpSocket *socket = pc->sock; + auto socket = pc->sock; bool have_data_for_server = (pc->used && pc->send_buffer && 0 < pc->send_buffer->ndata); @@ -277,7 +288,7 @@ static int read_from_connection(struct connection *pc, bool block) if (block) { // Wait (and block the main event loop) until we get some data - socket->waitForReadyRead(); + socket->waitForReadyRead(-1); } // Consume everything @@ -302,7 +313,7 @@ static int read_from_connection(struct connection *pc, bool block) This function is called when the client received a new input from the server. */ -void input_from_server(QTcpSocket *sock) +void input_from_server(QIODevice *sock) { int nb; diff --git a/client/clinet.h b/client/clinet.h index be71225eb9..d2e85e51fe 100644 --- a/client/clinet.h +++ b/client/clinet.h @@ -11,15 +11,15 @@ #pragma once // Forward declarations -class QTcpSocket; +class QIODevice; class QString; class QUrl; int connect_to_server(const QUrl &url, char *errbuf, int errbufsize); -void make_connection(QTcpSocket *sock, const QString &username); +void make_connection(QIODevice *sock, const QString &username); -void input_from_server(QTcpSocket *sock); +void input_from_server(QIODevice *sock); void disconnect_from_server(); void try_to_autoconnect(const QUrl &url); diff --git a/client/gui-qt/fc_client.cpp b/client/gui-qt/fc_client.cpp index 6765fe86de..3907853b55 100644 --- a/client/gui-qt/fc_client.cpp +++ b/client/gui-qt/fc_client.cpp @@ -276,7 +276,7 @@ enum client_pages fc_client::current_page() { return page; } /** Add notifier for server input */ -void fc_client::add_server_source(QTcpSocket *sock) +void fc_client::add_server_source(QIODevice *sock) { connect(sock, &QIODevice::readyRead, this, &fc_client::server_input); @@ -299,7 +299,7 @@ void fc_client::closeEvent(QCloseEvent *event) */ void fc_client::server_input() { - if (auto *socket = dynamic_cast(sender())) { + if (auto *socket = dynamic_cast(sender())) { input_from_server(socket); } } diff --git a/client/gui-qt/fc_client.h b/client/gui-qt/fc_client.h index 5745d788ae..201ae0b2f9 100644 --- a/client/gui-qt/fc_client.h +++ b/client/gui-qt/fc_client.h @@ -112,7 +112,7 @@ class fc_client : public QMainWindow { ~fc_client() override; QWidget *pages[static_cast(PAGE_GAME) + 2]; void fc_main(QApplication *); - void add_server_source(QTcpSocket *socket); + void add_server_source(QIODevice *socket); enum client_pages current_page(); diff --git a/client/gui-qt/gui_main.cpp b/client/gui-qt/gui_main.cpp index 38c644b070..6b18524790 100644 --- a/client/gui-qt/gui_main.cpp +++ b/client/gui-qt/gui_main.cpp @@ -172,7 +172,7 @@ void qtg_sound_bell() This function is called after the client succesfully has connected to the server. */ -void qtg_add_net_input(QTcpSocket *sock) { king()->add_server_source(sock); } +void qtg_add_net_input(QIODevice *sock) { king()->add_server_source(sock); } /** Stop waiting for any server network data. See add_net_input(). diff --git a/client/gui-qt/qtg_cxxside.h b/client/gui-qt/qtg_cxxside.h index c80551ca54..70b63118d3 100644 --- a/client/gui-qt/qtg_cxxside.h +++ b/client/gui-qt/qtg_cxxside.h @@ -56,7 +56,7 @@ void qtg_canvas_put_text(QPixmap *pcanvas, int canvas_x, int canvas_y, void qtg_set_rulesets(int num_rulesets, QStringList rulesets); void qtg_options_extra_init(); -void qtg_add_net_input(QTcpSocket *sock); +void qtg_add_net_input(QIODevice *sock); void qtg_remove_net_input(); void qtg_real_conn_list_dialog_update(void *unused); void qtg_close_connection_dialog(); diff --git a/client/gui_interface.cpp b/client/gui_interface.cpp index c634b15c80..227dd63b22 100644 --- a/client/gui_interface.cpp +++ b/client/gui_interface.cpp @@ -175,7 +175,7 @@ void options_extra_init(void) { funcs.options_extra_init(); } /** Call add_net_input callback */ -void add_net_input(QTcpSocket *sock) { funcs.add_net_input(sock); } +void add_net_input(QIODevice *sock) { funcs.add_net_input(sock); } /** Call remove_net_input callback diff --git a/client/gui_interface.h b/client/gui_interface.h index 5fc7e73d26..608ee98fa9 100644 --- a/client/gui_interface.h +++ b/client/gui_interface.h @@ -65,7 +65,7 @@ struct gui_funcs { void (*set_rulesets)(int num_rulesets, QStringList rulesets); void (*options_extra_init)(); - void (*add_net_input)(QTcpSocket *sock); + void (*add_net_input)(QIODevice *sock); void (*remove_net_input)(); void (*real_conn_list_dialog_update)(void *unused); void (*close_connection_dialog)(); diff --git a/client/include/gui_main_g.h b/client/include/gui_main_g.h index c2fa071432..121bba748f 100644 --- a/client/include/gui_main_g.h +++ b/client/include/gui_main_g.h @@ -11,7 +11,7 @@ #pragma once // Forward declarations -class QTcpSocket; +class QIODevice; // utility #include "support.h" // bool type @@ -28,7 +28,7 @@ GUI_FUNC_PROTO(void, options_extra_init, void) GUI_FUNC_PROTO(void, real_conn_list_dialog_update, void *) GUI_FUNC_PROTO(void, sound_bell, void) -GUI_FUNC_PROTO(void, add_net_input, QTcpSocket *) +GUI_FUNC_PROTO(void, add_net_input, QIODevice *) GUI_FUNC_PROTO(void, remove_net_input, void) GUI_FUNC_PROTO(void, set_unit_icon, int idx, struct unit *punit) diff --git a/common/networking/connection.cpp b/common/networking/connection.cpp index 3ad36ffdf2..0190612093 100644 --- a/common/networking/connection.cpp +++ b/common/networking/connection.cpp @@ -16,6 +16,7 @@ #endif // Qt +#include #include // utility @@ -54,7 +55,7 @@ static void default_conn_close_callback(struct connection *pconn) { fc_assert_msg(conn_close_callback != default_conn_close_callback, "Closing a socket (%s) before calling " - "close_socket_set_callback().", + "connections_set_close_callback().", conn_description(pconn)); } @@ -69,13 +70,13 @@ void connections_set_close_callback(conn_close_fn_t func) /** Call the conn_close_callback. */ -void connection_close(struct connection *pconn, const char *reason) +void connection_close(struct connection *pconn, const QString &reason) { fc_assert_ret(NULL != pconn); if (NULL != reason && pconn->closing_reason.isEmpty()) { // NB: we don't overwrite the original reason. - pconn->closing_reason = QString::fromUtf8(reason); + pconn->closing_reason = reason; } (*conn_close_callback)(pconn); @@ -110,7 +111,7 @@ static bool buffer_ensure_free_extra_space(struct socket_packet_buffer *buf, >0 : number of bytes read =0 : non-blocking sockets only; no data read, would block */ -int read_socket_data(QTcpSocket *sock, struct socket_packet_buffer *buffer) +int read_socket_data(QIODevice *sock, struct socket_packet_buffer *buffer) { int didget; @@ -187,7 +188,11 @@ void flush_connection_send_buffer_all(struct connection *pc) } } if (pc && pc->sock) { - pc->sock->flush(); + if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } else if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } } } @@ -204,7 +209,11 @@ static void flush_connection_send_buffer_packets(struct connection *pc) } } if (pc && pc->sock) { - pc->sock->flush(); + if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } else if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } } } diff --git a/common/networking/connection.h b/common/networking/connection.h index cbc95e857b..657becf9e6 100644 --- a/common/networking/connection.h +++ b/common/networking/connection.h @@ -30,7 +30,8 @@ #include "fc_types.h" // Forward declarations -class QTcpSocket; +class QIODevice; +class QString; struct conn_pattern_list; struct genhash; @@ -126,7 +127,7 @@ struct packet_header { ***********************************************************/ struct connection { int id; /* used for server/client communication */ - QTcpSocket *sock = nullptr; + QIODevice *sock = nullptr; bool used; bool established; // have negotiated initial packets struct packet_header packet_header; @@ -259,9 +260,9 @@ struct connection { typedef void (*conn_close_fn_t)(struct connection *pconn); void connections_set_close_callback(conn_close_fn_t func); -void connection_close(struct connection *pconn, const char *reason); +void connection_close(struct connection *pconn, const QString &reason); -int read_socket_data(QTcpSocket *sock, struct socket_packet_buffer *buffer); +int read_socket_data(QIODevice *sock, struct socket_packet_buffer *buffer); void flush_connection_send_buffer_all(struct connection *pc); bool connection_send_data(struct connection *pconn, const unsigned char *data, int len); diff --git a/server/connecthand.cpp b/server/connecthand.cpp index e973be6dcb..1a128a7798 100644 --- a/server/connecthand.cpp +++ b/server/connecthand.cpp @@ -957,7 +957,7 @@ bool connection_delegate_restore(struct connection *pconn) Close a connection. Use this in the server to take care of delegation stuff (reset the username of the controlled connection). */ -void connection_close_server(struct connection *pconn, const char *reason) +void connection_close_server(struct connection *pconn, const QString &reason) { // Restore possible delegations before the connection is closed. connection_delegate_restore(pconn); diff --git a/server/connecthand.h b/server/connecthand.h index 4c348fdc46..ff2e02cd72 100644 --- a/server/connecthand.h +++ b/server/connecthand.h @@ -15,6 +15,8 @@ #include "fc_types.h" +class QString; + struct connection; struct conn_list; struct packet_authentication_reply; @@ -44,4 +46,5 @@ bool connection_delegate_take(struct connection *pconn, struct player *pplayer); bool connection_delegate_restore(struct connection *pconn); -void connection_close_server(struct connection *pconn, const char *reason); +void connection_close_server(struct connection *pconn, + const QString &reason); diff --git a/server/sernet.cpp b/server/sernet.cpp index 4e203414fe..ad5d7df093 100644 --- a/server/sernet.cpp +++ b/server/sernet.cpp @@ -24,6 +24,7 @@ // Qt #include #include +#include #include #include #include @@ -126,7 +127,7 @@ void close_connections_and_socket() conn_list_destroy(game.all_connections); conn_list_destroy(game.est_connections); - if (srvarg.announce != ANNOUNCE_NONE) { + if (srvarg.announce != ANNOUNCE_NONE && udp_socket) { udp_socket->close(); delete udp_socket; udp_socket = nullptr; @@ -313,7 +314,8 @@ static const char *makeup_connection_name(int *id) Returns 0 on success, -1 on failure (bad accept(), or too many connections). */ -int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) +int server_make_connection(QIODevice *new_sock, const QString &client_addr, + const QString &ip_addr) { civtimer *timer; int i; @@ -345,8 +347,7 @@ int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) sz_strlcpy(pconn->username, makeup_connection_name(&pconn->id)); pconn->addr = client_addr; - sz_strlcpy(pconn->server.ipaddr, - qUtf8Printable(new_sock->peerAddress().toString())); + sz_strlcpy(pconn->server.ipaddr, qUtf8Printable(ip_addr)); conn_list_append(game.all_connections, pconn); @@ -372,9 +373,27 @@ int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) Open server socket to be used to accept client connections and open a server socket for server LAN announcements. */ -QTcpServer *server_open_socket() +optional_socket_server server_open_socket() { - auto *server = new QTcpServer; + // Local socket mode + if (!srvarg.local_addr.isEmpty()) { + auto server = std::make_unique(); + if (server->listen(srvarg.local_addr)) { + connections_set_close_callback(server_conn_close_callback); + + // Don't do LAN announcements + return server; + } else { + qCritical().noquote() + << QString(_("Server: cannot listen on local socket %1: %2")) + .arg(srvarg.local_addr) + .arg(server->errorString()); + return std::nullopt; + } + } + + // TCP socket mode + auto server = std::make_unique(); int max = srvarg.port + 100; for (; srvarg.port < max; ++srvarg.port) { @@ -399,7 +418,7 @@ QTcpServer *server_open_socket() _("Server: cannot listen on port %1: %2")) .arg(srvarg.port) .arg(server->errorString()))); - return server; + return std::nullopt; } } @@ -545,7 +564,7 @@ void get_lanserver_announcement() return; } - if (udp_socket->hasPendingDatagrams()) { + if (udp_socket && udp_socket->hasPendingDatagrams()) { QNetworkDatagram qnd = udp_socket->receiveDatagram(); auto data = qnd.data(); dio_input_init(&din, data.constData(), 1); diff --git a/server/sernet.h b/server/sernet.h index 3eefc7d60a..bd2fe4abc4 100644 --- a/server/sernet.h +++ b/server/sernet.h @@ -10,9 +10,14 @@ **************************************************************************/ #pragma once +#include +#include +#include + // Forward declarations +class QIODevice; +class QLocalServer; class QTcpServer; -class QTcpSocket; class QString; struct connection; @@ -23,13 +28,18 @@ struct connection; #define SERVER_LAN_TTL 1 #define SERVER_LAN_VERSION 2 -QTcpServer *server_open_socket(); +using socket_server = + std::variant, std::unique_ptr>; +using optional_socket_server = std::optional; + +optional_socket_server server_open_socket(); void flush_packets(); void incoming_client_packets(connection *pconn); void close_connections_and_socket(); void really_close_connections(); void init_connections(); -int server_make_connection(QTcpSocket *new_sock, const QString &client_addr); +int server_make_connection(QIODevice *new_sock, const QString &client_addr, + const QString &ip_addr); void connection_ping(struct connection *pconn); void handle_conn_pong(struct connection *pconn); void handle_client_heartbeat(struct connection *pconn); diff --git a/server/server.cpp b/server/server.cpp index db7a4d0faa..68e4fd8dd1 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -126,7 +126,7 @@ void fc_interface_init_server() /** Server initialization. */ -std::pair srv_prepare() +optional_socket_server srv_prepare() { // make sure it's initialized srv_init(); @@ -136,10 +136,9 @@ std::pair srv_prepare() con_log_init(srvarg.log_filename); // logging available after this point - auto *tcp_server = server_open_socket(); - if (!tcp_server->isListening()) { - // Don't even try to start a game. - return std::make_pair(tcp_server, false); + auto server = server_open_socket(); + if (!server) { + return std::nullopt; } #if IS_BETA_VERSION @@ -169,7 +168,7 @@ std::pair srv_prepare() success = fcdb_init(qUtf8Printable(srvarg.fcdb_conf)); if (!success) { - return std::make_pair(tcp_server, false); + return std::nullopt; } } @@ -181,7 +180,7 @@ std::pair srv_prepare() if (testfilename.isEmpty()) { qFatal(_("Ruleset directory \"%s\" not found"), qUtf8Printable(srvarg.ruleset)); - return std::make_pair(tcp_server, false); + return std::nullopt; } sz_strlcpy(game.server.rulesetdir, qUtf8Printable(srvarg.ruleset)); } @@ -205,11 +204,11 @@ std::pair srv_prepare() || !send_server_info_to_metaserver(META_INFO)) { con_write(C_FAIL, _("Not starting without explicitly requested " "metaserver connection.")); - return std::make_pair(tcp_server, false); + return std::nullopt; } } - return std::make_pair(tcp_server, true); + return server; } } // anonymous namespace @@ -257,20 +256,27 @@ server::server() // Now init the old C API fc_interface_init_server(); - bool success; - std::tie(m_tcp_server, success) = srv_prepare(); - if (!success) { + auto server = srv_prepare(); + if (!server) { // Could not listen on the specified port. Rely on the caller checking // our state and not starting the event loop. return; } - m_tcp_server->setParent(this); - connect(m_tcp_server, &QTcpServer::newConnection, this, - &server::accept_connections); - connect(m_tcp_server, &QTcpServer::acceptError, - [](QAbstractSocket::SocketError error) { - qCritical("Error accepting connection: %d", error); - }); + m_server = std::move(*server); + if (std::holds_alternative>(m_server)) { + auto &tcp = std::get>(m_server); + connect(tcp.get(), &QTcpServer::newConnection, this, + &server::accept_tcp_connections); + connect(tcp.get(), &QTcpServer::acceptError, + [](QAbstractSocket::SocketError error) { + qCritical("Error accepting connection: %d", error); + }); + } else if (std::holds_alternative>( + m_server)) { + auto &local = std::get>(m_server); + connect(local.get(), &QLocalServer::newConnection, this, + &server::accept_local_connections); + } m_eot_timer = timer_new(TIMER_CPU, TIMER_ACTIVE); @@ -352,15 +358,57 @@ void server::init_interactive() */ bool server::is_ready() const { return m_ready; } +/** + * Server accepts connections over local socket: + * Low level socket stuff, and basic-initialize the connection struct. + */ +void server::accept_local_connections() +{ + // We know it's safe: this method is only called for local connections + auto &server = std::get>(m_server); + + // There may be several connections available. + while (server->hasPendingConnections()) { + auto *socket = server->nextPendingConnection(); + socket->setParent(this); + + if (server_make_connection(socket, QStringLiteral("local"), + QStringLiteral("local")) + == 0) { + // Success making the connection, connect signals + connect(socket, &QIODevice::readyRead, this, &server::input_on_socket); + connect(socket, + QOverload::of( + &QLocalSocket::error), + this, &server::error_on_socket); + + // Prevents quitidle from firing immediately + m_someone_ever_connected = true; + + // Turn off the quitidle timeout if it's running + if (m_quitidle_timer != nullptr) { + m_quitidle_timer->stop(); + m_quitidle_timer->deleteLater(); + m_quitidle_timer = nullptr; + } + } else { + socket->deleteLater(); + } + } +} + /** Server accepts connections from client: Low level socket stuff, and basic-initialize the connection struct. */ -void server::accept_connections() +void server::accept_tcp_connections() { + // We know it's safe: this method is only called for TCP connections + auto &server = std::get>(m_server); + // There may be several connections available. - while (m_tcp_server->hasPendingConnections()) { - auto *socket = m_tcp_server->nextPendingConnection(); + while (server->hasPendingConnections()) { + auto *socket = server->nextPendingConnection(); socket->setParent(this); // Lookup the host name of the remote end. @@ -390,9 +438,9 @@ void server::accept_connections() { // Use TolerantConversion so one connections from the same address on // IPv4 and IPv6 are rejected as well. - if (socket->peerAddress().isEqual( - pconn->sock->peerAddress(), - QHostAddress::TolerantConversion)) { + if (const auto *other = qobject_cast(pconn->sock); + socket->peerAddress().isEqual( + other->peerAddress(), QHostAddress::TolerantConversion)) { continue; } if (++count >= game.server.maxconnectionsperhost) { @@ -407,11 +455,14 @@ void server::accept_connections() conn_list_iterate_end; if (!success) { + socket->deleteLater(); continue; } } - if (server_make_connection(socket, remote) == 0) { + if (server_make_connection(socket, remote, + socket->peerAddress().toString()) + == 0) { // Success making the connection, connect signals connect(socket, &QIODevice::readyRead, this, &server::input_on_socket); connect(socket, @@ -481,7 +532,7 @@ void server::error_on_socket() conn_list_iterate(game.all_connections, pconn) { if (pconn->sock == socket) { - connection_close_server(pconn, _("network exception")); + connection_close_server(pconn, socket->errorString()); break; } } @@ -497,7 +548,7 @@ void server::error_on_socket() void server::input_on_socket() { // Get the socket - auto *socket = dynamic_cast(sender()); + auto *socket = dynamic_cast(sender()); if (socket == nullptr) { return; } diff --git a/server/server.h b/server/server.h index 9dac70556d..e00e0d3d60 100644 --- a/server/server.h +++ b/server/server.h @@ -19,8 +19,12 @@ #pragma once +#include "sernet.h" + // Qt +#include #include +#include class civtimer; class QTcpServer; @@ -41,7 +45,8 @@ private slots: void error_on_socket(); void input_on_socket(); void input_on_stdin(); - void accept_connections(); + void accept_local_connections(); + void accept_tcp_connections(); void send_pings(); // Higher-level stuff @@ -63,7 +68,7 @@ private slots: bool m_interactive = false; QObject *m_stdin_notifier = nullptr; // Actual type is OS-dependent - QTcpServer *m_tcp_server = nullptr; + socket_server m_server = socket_server(); civtimer *m_eot_timer = nullptr, *m_between_turns_timer = nullptr; From 97bd62c6f7110a44e38acebfaeae017872795ee8 Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Mon, 7 Feb 2022 04:14:44 +0100 Subject: [PATCH 4/7] Add local socket support to the client This support is a bit hidden, it is enabled by using the fc21+local URL scheme. --- client/client_main.cpp | 3 +- client/clinet.cpp | 86 ++++++++++++++++++++++------------ client/gui-qt/page_network.cpp | 4 +- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/client/client_main.cpp b/client/client_main.cpp index 65a7df4460..edf58e68aa 100644 --- a/client/client_main.cpp +++ b/client/client_main.cpp @@ -474,7 +474,8 @@ int client_main(int argc, char *argv[]) auto positional = parser.positionalArguments(); if (positional.size() == 1) { url = QUrl(positional.constFirst()); - if (!url.isValid() || url.scheme() != QStringLiteral("fc21")) { + // Supported schemes: fc21://, fc21+local:// + if (!url.isValid() || (!url.scheme().startsWith("fc21"))) { // Try with the default protocol url = QUrl(QStringLiteral("fc21://") + positional.constFirst()); // Still no luck diff --git a/client/clinet.cpp b/client/clinet.cpp index c5aacb11d8..1b6371199d 100644 --- a/client/clinet.cpp +++ b/client/clinet.cpp @@ -100,6 +100,21 @@ static void client_conn_close_callback(struct connection *pconn) } namespace { +/// The URL we are presently connecting to. +QUrl connect_to; + +/** + * Called when there is an error on the socket. + */ +static void error_on_socket() +{ + if (client.conn.sock != nullptr) { + log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); + real_output_window_append(client.conn.sock->errorString(), NULL); + } + client.conn.used = false; +} + /** Try to connect to a server: - try to create a TCP socket to the given URL (default to @@ -112,9 +127,6 @@ namespace { message in ERRBUF and return the Unix error code (ie., errno, which will be non-zero). */ - -QUrl connect_to; - static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) { // Apply defaults @@ -155,24 +167,30 @@ static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) client.conn.used = true; // Now there will be a connection :) // Connect - if (!client.conn.sock) { - auto sock = new QTcpSocket; + if (url.scheme() == QStringLiteral("fc21")) { + QTcpSocket *sock = new QTcpSocket; client.conn.sock = sock; QObject::connect( sock, QOverload::of(&QAbstractSocket::error), - [] { - if (client.conn.sock != nullptr) { - log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); - real_output_window_append(client.conn.sock->errorString(), NULL); - } - client.conn.used = false; - }); + &error_on_socket); QObject::connect(sock, &QTcpSocket::connected, - [userName = url.userName()] { - make_connection(client.conn.sock, userName); - }); + [userName = url.userName()] { + make_connection(client.conn.sock, userName); + }); sock->connectToHost(url.host(), url.port()); + } else if (url.scheme() == QStringLiteral("fc21+local")) { + QLocalSocket *sock = new QLocalSocket; + client.conn.sock = sock; + QObject::connect( + sock, + QOverload::of(&QLocalSocket::error), + &error_on_socket); + QObject::connect(sock, &QLocalSocket::connected, + [userName = url.userName()] { + make_connection(client.conn.sock, userName); + }); + sock->connectToServer(url.path()); } return 0; @@ -371,20 +389,28 @@ void try_to_autoconnect(const QUrl &url) client.conn.sock = new QTcpSocket; } - QObject::connect( - client.conn.sock, - QOverload::of(&QAbstractSocket::error), - [url] { - if (client.conn.sock != nullptr) { - output_window_printf(ftc_client, - _("Failed to autoconnect to %s: %d of %d " - "attempts, retrying..."), - qUtf8Printable(url.toDisplayString()), count, - MAX_AUTOCONNECT_ATTEMPTS); - QTimer::singleShot(AUTOCONNECT_INTERVAL, - [url]() { try_to_autoconnect(url); }); - } - }); - try_to_connect(url, errbuf, sizeof(errbuf)); + + auto error_handler = [url] { + if (client.conn.sock != nullptr) { + output_window_printf(ftc_client, + _("Failed to autoconnect to %s: %d of %d " + "attempts, retrying..."), + qUtf8Printable(url.toDisplayString()), count, + MAX_AUTOCONNECT_ATTEMPTS); + QTimer::singleShot(AUTOCONNECT_INTERVAL, + [url]() { try_to_autoconnect(url); }); + } + }; + if (auto tcp = qobject_cast(client.conn.sock)) { + QObject::connect( + tcp, + QOverload::of(&QAbstractSocket::error), + error_handler); + } else if (auto local = qobject_cast(client.conn.sock)) { + QObject::connect( + local, + QOverload::of(&QLocalSocket::error), + error_handler); + } } diff --git a/client/gui-qt/page_network.cpp b/client/gui-qt/page_network.cpp index f592e0fc40..93a58ddefa 100644 --- a/client/gui-qt/page_network.cpp +++ b/client/gui-qt/page_network.cpp @@ -438,12 +438,12 @@ void page_network::slot_connect() switch (connection_status) { case LOGIN_TYPE: + client_url().setScheme(QStringLiteral("fc21")); client_url().setUserName(ui.connect_login_edit->text()); client_url().setHost(ui.connect_host_edit->text()); client_url().setPort(ui.connect_port_edit->text().toInt()); - if (connect_to_server(client_url(), errbuf, sizeof(errbuf)) != -1) { - } else { + if (connect_to_server(client_url(), errbuf, sizeof(errbuf)) < 0) { king->set_status_bar(QString::fromUtf8(errbuf)); output_window_append(ftc_client, errbuf); } From 83e39cb5c524bc9a1e484732c34518bfa88e94fe Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Mon, 7 Feb 2022 04:59:48 +0100 Subject: [PATCH 5/7] Use local sockets for client-spawned servers This looks much less suspicious to the Windows firewall. --- client/clinet.cpp | 32 +++++++++++--------------------- client/connectdlg_common.cpp | 12 +++++++----- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/client/clinet.cpp b/client/clinet.cpp index 1b6371199d..1e35edc07d 100644 --- a/client/clinet.cpp +++ b/client/clinet.cpp @@ -116,33 +116,23 @@ static void error_on_socket() } /** - Try to connect to a server: - - try to create a TCP socket to the given URL (default to - localhost:DEFAULT_SOCK_PORT). - - if successful: - - start monitoring the socket for packets from the server - - send a "login request" packet to the server - and - return 0 - - if unable to create the connection, close the socket, put an error - message in ERRBUF and return the Unix error code (ie., errno, which - will be non-zero). + * Try to connect to a server: + * - try to create a TCP socket to the given URL + * - if successful: + * - start monitoring the socket for packets from the server + * - send a "login request" packet to the server + * and - return 0 + * - if unable to create the connection, close the socket, put an error + * message in ERRBUF and return the Unix error code (ie., errno, which + * will be non-zero). */ static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) { - // Apply defaults - auto url_copy = url; - if (url_copy.host().isEmpty()) { - url_copy.setHost(QStringLiteral("localhost")); - } - if (url_copy.port() <= 0) { - url_copy.setPort(DEFAULT_SOCK_PORT); - } - connections_set_close_callback(client_conn_close_callback); // connection in progress? wait. if (client.conn.used) { - if (url_copy != connect_to) { + if (url != connect_to) { (void) fc_strlcpy( errbuf, _("Canceled previous connection, trying new connection."), errbufsize); @@ -152,7 +142,7 @@ static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) qobject_cast(client.conn.sock)) { local->abort(); } - connect_to = url_copy; + connect_to = url; } else { (void) fc_strlcpy(errbuf, _("Connection in progress."), errbufsize); return -1; diff --git a/client/connectdlg_common.cpp b/client/connectdlg_common.cpp index 280a416ee4..08ab90ec69 100644 --- a/client/connectdlg_common.cpp +++ b/client/connectdlg_common.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,9 @@ bool client_start_server(const QString &user_name) return false; } + // Unique name for this game + auto uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + ruleset = QString::fromUtf8(tileset_what_ruleset(tileset)); // Set up the command-line parameters. @@ -245,8 +249,7 @@ bool client_start_server(const QString &user_name) savesdir = QStringLiteral("%1/saves").arg(storage); scensdir = QStringLiteral("%1/scenarios").arg(storage); - arguments << QStringLiteral("-p") << port_buf << QStringLiteral("--bind") - << QStringLiteral("localhost") << QStringLiteral("-q") + arguments << QStringLiteral("--local") << uuid << QStringLiteral("-q") << QStringLiteral("1") << QStringLiteral("-e") << QStringLiteral("--saves") << savesdir << QStringLiteral("--scenarios") << scensdir @@ -297,10 +300,9 @@ bool client_start_server(const QString &user_name) // Local server URL auto url = QUrl(); - url.setScheme(QStringLiteral("fc21")); + url.setScheme(QStringLiteral("fc21+local")); url.setUserName(user_name); - url.setHost(QStringLiteral("localhost")); - url.setPort(internal_server_port); + url.setPath(uuid); // a reasonable number of tries while (connect_to_server( From 8bf6522ff7f9d3693e5625ad929151881438d31f Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Tue, 15 Feb 2022 00:55:22 +0100 Subject: [PATCH 6/7] Expand optional_socket_server -> std::optional As commented in #888, it didn't improve readability -- rather the opposite. --- server/sernet.cpp | 2 +- server/sernet.h | 3 +-- server/server.cpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/sernet.cpp b/server/sernet.cpp index ad5d7df093..c79324e5b8 100644 --- a/server/sernet.cpp +++ b/server/sernet.cpp @@ -373,7 +373,7 @@ int server_make_connection(QIODevice *new_sock, const QString &client_addr, Open server socket to be used to accept client connections and open a server socket for server LAN announcements. */ -optional_socket_server server_open_socket() +std::optional server_open_socket() { // Local socket mode if (!srvarg.local_addr.isEmpty()) { diff --git a/server/sernet.h b/server/sernet.h index bd2fe4abc4..4bf25200a2 100644 --- a/server/sernet.h +++ b/server/sernet.h @@ -30,9 +30,8 @@ struct connection; using socket_server = std::variant, std::unique_ptr>; -using optional_socket_server = std::optional; -optional_socket_server server_open_socket(); +std::optional server_open_socket(); void flush_packets(); void incoming_client_packets(connection *pconn); void close_connections_and_socket(); diff --git a/server/server.cpp b/server/server.cpp index 68e4fd8dd1..6e26bddcfe 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -126,7 +126,7 @@ void fc_interface_init_server() /** Server initialization. */ -optional_socket_server srv_prepare() +std::optional srv_prepare() { // make sure it's initialized srv_init(); From 99cc07613ad01cbbd898f8f82719108bd0f1aa09 Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Tue, 15 Feb 2022 01:10:16 +0100 Subject: [PATCH 7/7] Clarify a comment --- client/connectdlg_common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/connectdlg_common.cpp b/client/connectdlg_common.cpp index 08ab90ec69..b233bcd847 100644 --- a/client/connectdlg_common.cpp +++ b/client/connectdlg_common.cpp @@ -239,7 +239,7 @@ bool client_start_server(const QString &user_name) return false; } - // Unique name for this game + // Generate a (random) unique name for the local socket auto uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); ruleset = QString::fromUtf8(tileset_what_ruleset(tileset));