From de42135532274928de922521b03997146f9760b3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 21 Apr 2024 01:12:51 -0700 Subject: [PATCH] implement IPv4 range bans --- CMakeLists.txt | 1 + src/DNSServer.cc | 17 +++++---- src/DNSServer.hh | 20 +++++++---- src/IPStackSimulator.cc | 13 ++++--- src/IPStackSimulator.hh | 49 ++++++++++++------------- src/IPV4RangeSet.cc | 73 ++++++++++++++++++++++++++++++++++++++ src/IPV4RangeSet.hh | 18 ++++++++++ src/Main.cc | 18 +++++----- src/PatchServer.cc | 17 +++++++-- src/PatchServer.hh | 2 ++ src/ProxyServer.cc | 7 ++++ src/ReceiveCommands.cc | 4 +-- src/Server.cc | 40 +++++++++++++-------- src/Server.hh | 1 + src/ServerState.cc | 65 ++++++++++++++++++++++++++++++++- src/ServerState.hh | 10 +++++- system/config.example.json | 11 ++++++ tests/config.json | 1 + 18 files changed, 296 insertions(+), 71 deletions(-) create mode 100644 src/IPV4RangeSet.cc create mode 100644 src/IPV4RangeSet.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f94120..bfcfb34f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ set(SOURCES src/HTTPServer.cc src/IPFrameInfo.cc src/IPStackSimulator.cc + src/IPV4RangeSet.cc src/ItemCreator.cc src/ItemData.cc src/ItemNameIndex.cc diff --git a/src/DNSServer.cc b/src/DNSServer.cc index 387eb3af..c50f6bd1 100644 --- a/src/DNSServer.cc +++ b/src/DNSServer.cc @@ -19,10 +19,13 @@ using namespace std; DNSServer::DNSServer( shared_ptr base, - uint32_t local_connect_address, uint32_t external_connect_address) + uint32_t local_connect_address, + uint32_t external_connect_address, + shared_ptr banned_ipv4_ranges) : base(base), local_connect_address(local_connect_address), - external_connect_address(external_connect_address) {} + external_connect_address(external_connect_address), + banned_ipv4_ranges(banned_ipv4_ranges) {} DNSServer::~DNSServer() { for (const auto& it : this->fd_to_receive_event) { @@ -55,8 +58,7 @@ void DNSServer::dispatch_on_receive_message(evutil_socket_t fd, reinterpret_cast(ctx)->on_receive_message(fd, events); } -string DNSServer::response_for_query( - const void* vdata, size_t size, uint32_t resolved_address) { +string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) { if (size < 0x0C) { throw invalid_argument("query too small"); } @@ -82,7 +84,7 @@ string DNSServer::response_for_query( void DNSServer::on_receive_message(int fd, short) { for (;;) { - sockaddr_in remote; + struct sockaddr_storage remote; socklen_t remote_size = sizeof(sockaddr_in); memset(&remote, 0, remote_size); @@ -104,9 +106,10 @@ void DNSServer::on_receive_message(int fd, short) { dns_server_log.warning("input query too small"); print_data(stderr, input.data(), bytes); - } else { + } else if (!this->banned_ipv4_ranges->check(remote)) { input.resize(bytes); - uint32_t remote_address = ntohl(remote.sin_addr.s_addr); + const sockaddr_in* remote_sin = reinterpret_cast(&remote); + uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr); uint32_t connect_address = is_local_address(remote_address) ? this->local_connect_address : this->external_connect_address; diff --git a/src/DNSServer.hh b/src/DNSServer.hh index 37eed4af..7b7a0530 100644 --- a/src/DNSServer.hh +++ b/src/DNSServer.hh @@ -7,29 +7,37 @@ #include #include +#include "IPV4RangeSet.hh" + class DNSServer { public: - DNSServer(std::shared_ptr base, - uint32_t local_connect_address, uint32_t external_connect_address); + DNSServer( + std::shared_ptr base, + uint32_t local_connect_address, + uint32_t external_connect_address, + std::shared_ptr banned_ipv4_ranges); DNSServer(const DNSServer&) = delete; DNSServer(DNSServer&&) = delete; virtual ~DNSServer(); + inline void set_banned_ipv4_ranges(std::shared_ptr banned_ipv4_ranges) { + this->banned_ipv4_ranges = banned_ipv4_ranges; + } + void listen(const std::string& socket_path); void listen(const std::string& addr, int port); void listen(int port); void add_socket(int fd); - static std::string response_for_query( - const void* vdata, size_t size, uint32_t resolved_address); - static std::string response_for_query( - const std::string& query, uint32_t resolved_address); + static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address); + static std::string response_for_query(const std::string& query, uint32_t resolved_address); private: std::shared_ptr base; std::unordered_map> fd_to_receive_event; uint32_t local_connect_address; uint32_t external_connect_address; + std::shared_ptr banned_ipv4_ranges; static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx); void on_receive_message(int fd, short event); diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index caa7eace..2fd9a529 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -235,12 +235,17 @@ void IPStackSimulator::disconnect_client(struct bufferevent* bev) { void IPStackSimulator::dispatch_on_listen_accept( struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx) { - reinterpret_cast(ctx)->on_listen_accept( - listener, fd, address, socklen); + reinterpret_cast(ctx)->on_listen_accept(listener, fd, address, socklen); } -void IPStackSimulator::on_listen_accept(struct evconnlistener* listener, - evutil_socket_t fd, struct sockaddr*, int) { +void IPStackSimulator::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { + struct sockaddr_storage remote_addr; + get_socket_addresses(fd, nullptr, &remote_addr); + if (this->state->banned_ipv4_ranges->check(remote_addr)) { + close(fd); + return; + } + int listen_fd = evconnlistener_get_fd(listener); const ListeningSocket* listening_socket; diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 4bd09f74..be9a9a6b 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -22,22 +22,6 @@ public: HDLC_RAW, }; - IPStackSimulator( - std::shared_ptr base, - std::shared_ptr state); - ~IPStackSimulator(); - - void listen(const std::string& name, const std::string& socket_path, Protocol protocol); - void listen(const std::string& name, const std::string& addr, int port, Protocol protocol); - void listen(const std::string& name, int port, Protocol protocol); - void add_socket(const std::string& name, int fd, Protocol protocol); - - static uint32_t connect_address_for_remote_address(uint32_t remote_addr); - -private: - std::shared_ptr base; - std::shared_ptr state; - using unique_listener = std::unique_ptr; using unique_bufferevent = std::unique_ptr; using unique_evbuffer = std::unique_ptr; @@ -92,6 +76,28 @@ private: void on_idle_timeout(); }; + IPStackSimulator( + std::shared_ptr base, + std::shared_ptr state); + ~IPStackSimulator(); + + void listen(const std::string& name, const std::string& socket_path, Protocol protocol); + void listen(const std::string& name, const std::string& addr, int port, Protocol protocol); + void listen(const std::string& name, int port, Protocol protocol); + void add_socket(const std::string& name, int fd, Protocol protocol); + + static uint32_t connect_address_for_remote_address(uint32_t remote_addr); + + inline const std::unordered_map>& all_clients() const { + return this->bev_to_client; + } + + void disconnect_client(struct bufferevent* bev); + +private: + std::shared_ptr base; + std::shared_ptr state; + struct ListeningSocket { std::string name; Protocol protocol; @@ -111,20 +117,16 @@ private: FILE* pcap_text_log_file; - void disconnect_client(struct bufferevent* bev); - static uint64_t tcp_conn_key_for_connection(const IPClient::TCPConnection& conn); static uint64_t tcp_conn_key_for_client_frame(const IPv4Header& ipv4, const TCPHeader& tcp); static uint64_t tcp_conn_key_for_client_frame(const FrameInfo& fi); static std::string str_for_ipv4_netloc(uint32_t addr, uint16_t port); - static std::string str_for_tcp_connection(std::shared_ptr c, - const IPClient::TCPConnection& conn); + static std::string str_for_tcp_connection(std::shared_ptr c, const IPClient::TCPConnection& conn); static void dispatch_on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); - void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, - struct sockaddr* address, int socklen); + void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen); static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx); void on_listen_error(struct evconnlistener* listener); @@ -158,8 +160,7 @@ private: struct evbuffer* src_buf = nullptr, size_t src_bytes = 0); - void open_server_connection( - std::shared_ptr c, IPClient::TCPConnection& conn); + void open_server_connection(std::shared_ptr c, IPClient::TCPConnection& conn); void log_frame(const std::string& data) const; }; diff --git a/src/IPV4RangeSet.cc b/src/IPV4RangeSet.cc new file mode 100644 index 00000000..316935ae --- /dev/null +++ b/src/IPV4RangeSet.cc @@ -0,0 +1,73 @@ +#include "IPV4RangeSet.hh" + +#include + +using namespace std; + +IPV4RangeSet::IPV4RangeSet(const JSON& json) { + for (const auto& it : json.as_list()) { + // String should be of the form a.b.c.d or a.b.c.d/e + auto tokens = split(it->as_string(), '/'); + + size_t mask_bits; + if (tokens.size() == 1) { + mask_bits = 32; + } else if (tokens.size() == 2) { + mask_bits = stoul(tokens[1], nullptr, 10); + if (mask_bits > 32) { + throw runtime_error("invalid IPv4 address range"); + } + } else { + throw runtime_error("invalid IPv4 address range"); + } + + auto addr_tokens = split(tokens[0], '.'); + if (addr_tokens.size() != 4) { + throw runtime_error("invalid IPv4 address"); + } + uint32_t addr = 0; + for (size_t z = 0; z < 4; z++) { + size_t end_pos = 0; + size_t new_byte = stoul(addr_tokens[z], &end_pos, 10); + if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) { + throw runtime_error("invalid IPv4 address"); + } + addr = (addr << 8) | new_byte; + } + addr &= (0xFFFFFFFF << (32 - mask_bits)); + + this->ranges.emplace(addr, mask_bits); + } +} + +JSON IPV4RangeSet::json() const { + auto ret = JSON::list(); + for (const auto& it : this->ranges) { + uint32_t addr = it.first; + uint8_t mask_bits = it.second; + ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu", + static_cast((addr >> 24) & 0xFF), + static_cast((addr >> 16) & 0xFF), + static_cast((addr >> 8) & 0xFF), + static_cast(addr & 0xFF), + mask_bits)); + } + return ret; +} + +bool IPV4RangeSet::check(uint32_t addr) const { + auto it = this->ranges.upper_bound(addr); + if (it == this->ranges.begin()) { + return false; // addr is before any range + } + const auto& range = *(--it); + return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0); +} + +bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const { + if (ss.ss_family != AF_INET) { + return false; + } + const sockaddr_in* sin = reinterpret_cast(&ss); + return this->check(ntohl(sin->sin_addr.s_addr)); +} diff --git a/src/IPV4RangeSet.hh b/src/IPV4RangeSet.hh new file mode 100644 index 00000000..6a67ffa6 --- /dev/null +++ b/src/IPV4RangeSet.hh @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class IPV4RangeSet { +public: + IPV4RangeSet() = default; + explicit IPV4RangeSet(const JSON& json); + + JSON json() const; + + bool check(uint32_t addr) const; + bool check(const struct sockaddr_storage& ss) const; + +protected: + std::map ranges; // {addr: mask_bits} +}; diff --git a/src/Main.cc b/src/Main.cc index 41954665..0b26a56c 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2458,22 +2458,21 @@ Action a_run_server_replay_log( auto state = make_shared(base, get_config_filename(args), is_replay); state->load_all(); - shared_ptr dns_server; if (state->dns_server_port && !is_replay) { 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_addr, state->dns_server_port); + state->dns_server = make_shared( + base, state->local_address, state->external_address, state->banned_ipv4_ranges); + state->dns_server->listen(state->dns_server_addr, state->dns_server_port); } else { config_log.info("DNS server is disabled"); } shared_ptr shell; shared_ptr replay_session; - shared_ptr ip_stack_simulator; shared_ptr http_server; if (is_replay) { config_log.info("Starting proxy server"); @@ -2548,21 +2547,24 @@ Action a_run_server_replay_log( if (!state->ip_stack_addresses.empty() || !state->ppp_stack_addresses.empty() || !state->ppp_raw_addresses.empty()) { config_log.info("Starting IP/PPP stack simulator"); - ip_stack_simulator = make_shared(base, state); + state->ip_stack_simulator = make_shared(base, state); for (const auto& it : state->ip_stack_addresses) { auto netloc = parse_netloc(it); string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : string_printf("T-IPS-%hu", netloc.second); - ip_stack_simulator->listen(spec, netloc.first, netloc.second, IPStackSimulator::Protocol::ETHERNET_TAPSERVER); + state->ip_stack_simulator->listen( + spec, netloc.first, netloc.second, IPStackSimulator::Protocol::ETHERNET_TAPSERVER); } for (const auto& it : state->ppp_stack_addresses) { auto netloc = parse_netloc(it); string spec = (netloc.second == 0) ? ("T-PPPST-" + netloc.first) : string_printf("T-PPPST-%hu", netloc.second); - ip_stack_simulator->listen(spec, netloc.first, netloc.second, IPStackSimulator::Protocol::HDLC_TAPSERVER); + state->ip_stack_simulator->listen( + spec, netloc.first, netloc.second, IPStackSimulator::Protocol::HDLC_TAPSERVER); } for (const auto& it : state->ppp_raw_addresses) { auto netloc = parse_netloc(it); string spec = (netloc.second == 0) ? ("T-PPPSR-" + netloc.first) : string_printf("T-PPPSR-%hu", netloc.second); - ip_stack_simulator->listen(spec, netloc.first, netloc.second, IPStackSimulator::Protocol::HDLC_RAW); + state->ip_stack_simulator->listen( + spec, netloc.first, netloc.second, IPStackSimulator::Protocol::HDLC_RAW); if (netloc.second) { if (state->local_address == state->external_address) { config_log.info( diff --git a/src/PatchServer.cc b/src/PatchServer.cc index dbeb1887..8fbf760c 100644 --- a/src/PatchServer.cc +++ b/src/PatchServer.cc @@ -320,6 +320,13 @@ void PatchServer::dispatch_on_listen_error( } void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { + struct sockaddr_storage remote_addr; + get_socket_addresses(fd, nullptr, &remote_addr); + if (this->config->banned_ipv4_ranges->check(remote_addr)) { + close(fd); + return; + } + int listen_fd = evconnlistener_get_fd(listener); ListeningSocket* listening_socket; try { @@ -461,7 +468,11 @@ void PatchServer::thread_fn() { } void PatchServer::set_config(std::shared_ptr config) { - forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() { - s->config = config; - }); + if (this->base_is_shared) { + this->config = config; + } else { + forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() { + s->config = config; + }); + } } diff --git a/src/PatchServer.hh b/src/PatchServer.hh index 144b94a3..d59c887c 100644 --- a/src/PatchServer.hh +++ b/src/PatchServer.hh @@ -10,6 +10,7 @@ #include "Account.hh" #include "Channel.hh" +#include "IPV4RangeSet.hh" #include "PatchFileIndex.hh" #include "Version.hh" @@ -22,6 +23,7 @@ public: std::string message; std::shared_ptr account_index; std::shared_ptr patch_file_index; + std::shared_ptr banned_ipv4_ranges; std::shared_ptr shared_base; }; diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 871ae4fb..0328bfa1 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -98,6 +98,13 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error( } void ProxyServer::ListeningSocket::on_listen_accept(int fd) { + struct sockaddr_storage remote_addr; + get_socket_addresses(fd, nullptr, &remote_addr); + if (this->server->state->banned_ipv4_ranges->check(remote_addr)) { + close(fd); + return; + } + this->log.info("Client connected on fd %d (port %hu, version %s)", fd, this->port, name_for_enum(this->version)); auto* bev = bufferevent_socket_new(this->server->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); this->server->on_client_connect(bev, this->port, this->version, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index df330a58..93f808b5 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -5181,8 +5181,8 @@ typedef void (*on_command_t)(shared_ptr c, uint16_t command, uint32_t fl // Command handler table, indexed by command number and game version. Null // entries in this table cause on_unimplemented_command to be called, which // disconnects the client. -static_assert(NUM_VERSIONS - 2 == 12, "Don\'t forget to update the ReceiveCommands handler table"); -static on_command_t handlers[0x100][NUM_VERSIONS - 2] = { +static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the ReceiveCommands handler table"); +static on_command_t handlers[0x100][NUM_NON_PATCH_VERSIONS] = { // clang-format off // DC_NTE DC_112000 DCV1 DCV2 PC_NTE PC GCNTE GC EP3TE EP3 XB BB /* 00 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, diff --git a/src/Server.cc b/src/Server.cc index 2d7400d3..7b1b477b 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -85,17 +85,20 @@ void Server::destroy_clients() { void Server::dispatch_on_listen_accept( struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx) { - reinterpret_cast(ctx)->on_listen_accept(listener, fd, address, - socklen); + reinterpret_cast(ctx)->on_listen_accept(listener, fd, address, socklen); } -void Server::dispatch_on_listen_error( - struct evconnlistener* listener, void* ctx) { +void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) { reinterpret_cast(ctx)->on_listen_error(listener); } -void Server::on_listen_accept( - struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { +void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { + struct sockaddr_storage remote_addr; + get_socket_addresses(fd, nullptr, &remote_addr); + if (this->state->banned_ipv4_ranges->check(remote_addr)) { + close(fd); + return; + } int listen_fd = evconnlistener_get_fd(listener); ListeningSocket* listening_socket; @@ -108,8 +111,7 @@ void Server::on_listen_accept( return; } - struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, - BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); auto c = make_shared(this->shared_from_this(), bev, listening_socket->version, listening_socket->behavior); c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; @@ -298,34 +300,34 @@ vector> Server::get_clients_by_identifier(const string& ident for (const auto& it : this->state->channel_to_client) { auto c = it.second; if (c->login && c->login->account->account_id == account_id_hex) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } if (c->login && c->login->account->account_id == account_id_dec) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } if (c->login && c->login->bb_license && c->login->bb_license->username == ident) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } auto p = c->character(false, false); if (p && p->disp.name.eq(ident, p->inventory.language)) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } if (c->channel.name == ident) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } if (starts_with(c->channel.name, ident + " ")) { - results.emplace_back(std::move(c)); + results.emplace_back(c); continue; } } @@ -333,6 +335,14 @@ vector> Server::get_clients_by_identifier(const string& ident return results; } +vector> Server::all_clients() const { + vector> ret; + for (const auto& it : this->state->channel_to_client) { + ret.emplace_back(it.second); + } + return ret; +} + shared_ptr Server::get_base() const { return this->base; } diff --git a/src/Server.hh b/src/Server.hh index c8736f2d..a8101178 100644 --- a/src/Server.hh +++ b/src/Server.hh @@ -31,6 +31,7 @@ public: std::shared_ptr get_client() const; std::vector> get_clients_by_identifier(const std::string& ident) const; + std::vector> all_clients() const; std::shared_ptr get_base() const; inline std::shared_ptr get_state() const { diff --git a/src/ServerState.cc b/src/ServerState.cc index 539a442d..b40c6ab4 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -682,6 +682,13 @@ void ServerState::load_config_early() { this->all_addresses.erase(""); this->all_addresses.emplace("", this->external_address); + try { + this->banned_ipv4_ranges = make_shared(this->config_json->at("BannedIPV4Ranges")); + this->disconnect_all_banned_clients(); + } catch (const out_of_range&) { + this->banned_ipv4_ranges = make_shared(); + } + this->client_ping_interval_usecs = this->config_json->get_int("ClientPingInterval", 30000000); this->client_idle_timeout_usecs = this->config_json->get_int("ClientIdleTimeout", 60000000); this->patch_client_idle_timeout_usecs = this->config_json->get_int("PatchClientIdleTimeout", 300000000); @@ -1108,6 +1115,8 @@ void ServerState::load_config_early() { } } catch (const out_of_range&) { } + + this->update_dependent_server_configs(); } void ServerState::load_config_late() { @@ -1278,6 +1287,7 @@ void ServerState::load_accounts(bool from_non_event_thread) { auto set = [s = this->shared_from_this(), new_index = std::move(new_index)]() { s->account_index = std::move(new_index); + s->update_dependent_server_configs(); }; this->forward_or_call(from_non_event_thread, std::move(set)); } @@ -1324,6 +1334,7 @@ void ServerState::load_patch_indexes(bool from_non_event_thread) { s->bb_data_gsl = std::move(bb_data_gsl); s->pc_patch_file_index = std::move(pc_patch_file_index); s->bb_patch_file_index = std::move(bb_patch_file_index); + s->update_dependent_server_configs(); }; this->forward_or_call(from_non_event_thread, std::move(set)); } @@ -1850,15 +1861,67 @@ shared_ptr ServerState::generate_patch_server_config(bool i ret->idle_timeout_usecs = this->patch_client_idle_timeout_usecs; ret->message = is_bb ? this->bb_patch_server_message : this->pc_patch_server_message; ret->account_index = this->account_index; + ret->banned_ipv4_ranges = this->banned_ipv4_ranges; ret->patch_file_index = is_bb ? this->bb_patch_file_index : this->pc_patch_file_index; return ret; } -void ServerState::update_patch_server_configs() const { +void ServerState::update_dependent_server_configs() const { if (this->pc_patch_server) { this->pc_patch_server->set_config(this->generate_patch_server_config(false)); } if (this->bb_patch_server) { this->bb_patch_server->set_config(this->generate_patch_server_config(true)); } + if (this->dns_server) { + this->dns_server->set_banned_ipv4_ranges(this->banned_ipv4_ranges); + } +} + +void ServerState::disconnect_all_banned_clients() { + uint64_t now_usecs = now(); + + if (this->game_server) { + for (const auto& c : this->game_server->all_clients()) { + if ((c->login && (c->login->account->ban_end_time > now_usecs)) || + this->banned_ipv4_ranges->check(c->channel.remote_addr)) { + this->game_server->disconnect_client(c); + } + } + } + + // Proxy server + if (this->proxy_server) { + vector sessions_to_close; + for (const auto& it : this->proxy_server->all_sessions()) { + auto ses = it.second; + if ((ses->login && (ses->login->account->ban_end_time > now_usecs)) || + this->banned_ipv4_ranges->check(ses->client_channel.remote_addr)) { + sessions_to_close.emplace_back(it.first); + } + } + for (uint32_t ses_id : sessions_to_close) { + this->proxy_server->delete_session(ses_id); + } + } + + // IP stack simulator (IP bans only; account bans will presumably be handled + // by one of the above cases) + if (this->ip_stack_simulator) { + vector bevs_to_disconnect; + for (const auto& it : this->ip_stack_simulator->all_clients()) { + int fd = bufferevent_getfd(it.first); + if (fd < 0) { + continue; + } + struct sockaddr_storage remote_ss; + get_socket_addresses(fd, nullptr, &remote_ss); + if (this->banned_ipv4_ranges->check(remote_ss)) { + bevs_to_disconnect.emplace_back(it.first); + } + } + for (auto* bev : bevs_to_disconnect) { + this->ip_stack_simulator->disconnect_client(bev); + } + } } diff --git a/src/ServerState.hh b/src/ServerState.hh index 3f33bf03..4382f685 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -14,11 +14,13 @@ #include "Account.hh" #include "Client.hh" #include "CommonItemSet.hh" +#include "DNSServer.hh" #include "Episode3/DataIndexes.hh" #include "Episode3/Tournament.hh" #include "EventUtils.hh" #include "FunctionCompiler.hh" #include "GSLArchive.hh" +#include "IPV4RangeSet.hh" #include "ItemNameIndex.hh" #include "ItemParameterTable.hh" #include "LevelTable.hh" @@ -33,6 +35,7 @@ // Forward declarations due to reference cycles class ProxyServer; class Server; +class IPStackSimulator; struct PortConfiguration { std::string name; @@ -215,6 +218,7 @@ struct ServerState : public std::enable_shared_from_this { std::vector ep3_lobby_banners; std::shared_ptr account_index; + std::shared_ptr banned_ipv4_ranges; std::shared_ptr team_index; JSON team_reward_defs_json; @@ -253,6 +257,8 @@ struct ServerState : public std::enable_shared_from_this { bool proxy_allow_save_files = true; bool proxy_enable_login_options = false; + std::shared_ptr ip_stack_simulator; + std::shared_ptr dns_server; std::shared_ptr proxy_server; std::shared_ptr game_server; std::shared_ptr pc_patch_server; @@ -343,7 +349,7 @@ struct ServerState : public std::enable_shared_from_this { } std::shared_ptr generate_patch_server_config(bool is_bb) const; - void update_patch_server_configs() const; + void update_dependent_server_configs() const; // The following functions may only be called from a non-event thread if they // take a from_non_event_thread argument; any function that does not have this @@ -379,4 +385,6 @@ struct ServerState : public std::enable_shared_from_this { void enqueue_destroy_lobbies(); static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx); + + void disconnect_all_banned_clients(); }; diff --git a/system/config.example.json b/system/config.example.json index f04c65e5..6bc45e85 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -176,6 +176,17 @@ // entries in this list is the same as for IPStackListen and PPPStackListen. "HTTPListen": [], + // Banned IP address ranges. If a client whose remote IPv4 address is in any + // of these ranges connects to the server, they are immediately disconnected + // with no message. Entries in this list may be individiual IP addresses + // (e.g. "1.2.3.4") or CIDR ranges (e.g. "1.2.3.0/24"). This field takes + // effect immediately when `reload config` is run in the shell; any existing + // clients who are now banned are disconnected. + // Note that this setting does not apply to the HTTP server; it is not + // possible to ban IP ranges from the HTTP server. (It is also inadvisable + // to expose the HTTP server to the public Internet.) + "BannedIPV4Ranges": [], + // Other servers to support proxying to. If this is empty for any game // version, the proxy server is disabled for that version. Entries in these // dictionaries should be of the form "name": "address:port"; the names are diff --git a/tests/config.json b/tests/config.json index ae8c4b33..6f019c77 100644 --- a/tests/config.json +++ b/tests/config.json @@ -46,6 +46,7 @@ "IPStackListen": [], "PPPStackListen": [], "HTTPListen": [], + "BannedIPV4Ranges": [], "Episode3BehaviorFlags": 0xFA, "EnableEpisode3SendFunctionCall": false, "EnableV3V4ProtectedSubcommands": false,