From 4ff4c86047186c3902dade24d69b152cb8d2b77f Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 31 Dec 2023 22:21:00 -0800 Subject: [PATCH] add ability to specify listening interfaces --- src/Main.cc | 16 ++++++++++------ src/ProxyServer.cc | 11 +++++++---- src/ProxyServer.hh | 3 ++- src/ServerState.cc | 26 +++++++++++++++++++++++--- src/ServerState.hh | 5 +++++ system/config.example.json | 10 +++++++++- 6 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/Main.cc b/src/Main.cc index fbfe1e47..477d855c 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1890,9 +1890,13 @@ Action a_run_server_replay_log( shared_ptr dns_server; if (state->dns_server_port && !is_replay) { - config_log.info("Starting DNS server on port %hu", state->dns_server_port); + if (!state->dns_server_addr.empty()) { + config_log.info("Starting DNS server on %s:%hu", state->dns_server_addr.c_str(), state->dns_server_port); + } else { + config_log.info("Starting DNS server on port %hu", state->dns_server_port); + } dns_server = make_shared(base, state->local_address, state->external_address); - dns_server->listen("", state->dns_server_port); + dns_server->listen(state->dns_server_addr, state->dns_server_port); } else { config_log.info("DNS server is disabled"); } @@ -1934,14 +1938,14 @@ Action a_run_server_replay_log( auto [ss, size] = make_sockaddr_storage( state->proxy_destination_patch.first, state->proxy_destination_patch.second); - state->proxy_server->listen(pc->port, pc->version, &ss); + state->proxy_server->listen(pc->addr, pc->port, pc->version, &ss); } else if (is_v4(pc->version)) { auto [ss, size] = make_sockaddr_storage( state->proxy_destination_bb.first, state->proxy_destination_bb.second); - state->proxy_server->listen(pc->port, pc->version, &ss); + state->proxy_server->listen(pc->addr, pc->port, pc->version, &ss); } else { - state->proxy_server->listen(pc->port, pc->version); + state->proxy_server->listen(pc->addr, pc->port, pc->version); } } } else { @@ -1950,7 +1954,7 @@ Action a_run_server_replay_log( state->game_server = make_shared(base, state); } string spec = string_printf("T-%hu-%s-%s-%s", pc->port, name_for_enum(pc->version), pc->name.c_str(), name_for_enum(pc->behavior)); - state->game_server->listen(spec, "", pc->port, pc->version, pc->behavior); + state->game_server->listen(spec, pc->addr, pc->port, pc->version, pc->behavior); } } diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 86555a2f..6e639944 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -43,20 +43,23 @@ ProxyServer::ProxyServer( state(state), next_unlicensed_session_id(0xFF00000000000001) {} -void ProxyServer::listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination) { - auto socket_obj = make_shared(this, port, version, default_destination); - auto l = this->listeners.emplace(port, socket_obj).first->second; +void ProxyServer::listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination) { + auto socket_obj = make_shared(this, addr, port, version, default_destination); + if (!this->listeners.emplace(port, socket_obj).second) { + throw runtime_error("duplicate port in proxy server configuration"); + } } ProxyServer::ListeningSocket::ListeningSocket( ProxyServer* server, + const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination) : server(server), log(string_printf("[ProxyServer:ListeningSocket:%hu] ", port), proxy_server_log.min_level), port(port), - fd(::listen("", port, SOMAXCONN)), + fd(::listen(addr, port, SOMAXCONN)), listener(nullptr, evconnlistener_free), version(version) { if (!this->fd.is_open()) { diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 841ecec7..8ad98496 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -26,7 +26,7 @@ public: std::shared_ptr state); virtual ~ProxyServer() = default; - void listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr); + void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr); void connect_client(struct bufferevent* bev, uint16_t server_port); @@ -207,6 +207,7 @@ private: ListeningSocket( ProxyServer* server, + const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination); diff --git a/src/ServerState.cc b/src/ServerState.cc index f0125b5c..bc1ef562 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -472,13 +472,28 @@ shared_ptr ServerState::load_bb_file( } } -static vector parse_port_configuration(const JSON& json) { +pair ServerState::parse_port_spec(const JSON& json) const { + if (json.is_list()) { + string addr = json.at(0).as_string(); + try { + addr = string_for_address(this->all_addresses.at(addr)); + } catch (const out_of_range&) { + } + return make_pair(addr, json.at(1).as_int()); + } else { + return make_pair("", json.as_int()); + } +} + +vector ServerState::parse_port_configuration(const JSON& json) const { vector ret; for (const auto& item_json_it : json.as_dict()) { const auto& item_list = item_json_it.second; PortConfiguration& pc = ret.emplace_back(); pc.name = item_json_it.first; - pc.port = item_list->at(0).as_int(); + auto spec = this->parse_port_spec(item_list->at(0)); + pc.addr = std::move(spec.first); + pc.port = spec.second; pc.version = enum_for_name(item_list->at(1).as_string().c_str()); pc.behavior = enum_for_name(item_list->at(2).as_string().c_str()); } @@ -537,7 +552,12 @@ void ServerState::load_config() { } this->set_port_configuration(parse_port_configuration(json.at("PortConfiguration"))); - this->dns_server_port = json.get_int("DNSServerPort", this->dns_server_port); + try { + auto spec = this->parse_port_spec(json.at("DNSServerPort")); + this->dns_server_addr = std::move(spec.first); + this->dns_server_port = spec.second; + } catch (const out_of_range&) { + } try { for (const auto& item : json.at("IPStackListen").as_list()) { if (item->is_int()) { diff --git a/src/ServerState.hh b/src/ServerState.hh index 83f8e19e..c14b6715 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -35,6 +35,7 @@ class Server; struct PortConfiguration { std::string name; + std::string addr; // Blank = listen on all interfaces (default) uint16_t port; Version version; ServerBehavior behavior; @@ -74,6 +75,7 @@ struct ServerState : public std::enable_shared_from_this { std::unordered_map> name_to_port_config; std::unordered_map> number_to_port_config; std::string username; + std::string dns_server_addr; uint16_t dns_server_port = 0; std::vector ip_stack_addresses; std::vector ppp_stack_addresses; @@ -283,6 +285,9 @@ struct ServerState : public std::enable_shared_from_this { const std::string& gsl_filename = "", const std::string& bb_directory_filename = "") const; + std::pair parse_port_spec(const JSON& json) const; + std::vector parse_port_configuration(const JSON& json) const; + void create_load_step_graph(); void create_default_lobbies(); void collect_network_addresses(); diff --git a/system/config.example.json b/system/config.example.json index 074ca0c9..b517174b 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -37,7 +37,9 @@ "ExternalAddress": "10.0.1.5", // Port to listen for DNS queries on. To disable the DNS server, comment this - // out or set it to zero. + // out or set it to zero. By default, the DNS server listens on all + // interfaces, but you can specify an interface by replacing this with a list + // of [interface_addr_or_name, port]. "DNSServerPort": 53, // Ports to listen for game connections on. @@ -45,6 +47,12 @@ // Format of entries in this dictionary: // name: [port, version, behavior] + // port is normally just an integer (which will cause the server to listen + // on that port on all interfaces), but you can also replace the integer + // with a 2-list of [address, port] to listen in a specific port. For + // example, that might look like: + // "xb-login": [["en0", 9500], "xb", "login_server"], + // Various versions of PSO hardcode these ports in the clients. Don't change // these unless you don't want to support certain versions of PSO. // Note: The pc_console_detect behavior is used for separating PSO PC and