diff --git a/config.ini b/config.ini index d051ffba..2a89f2bc 100644 --- a/config.ini +++ b/config.ini @@ -30,6 +30,11 @@ dbsaveinterval=240 # Shard Server configuration [shard] port=23001 +# the address the client will be told to connect to for the game world. +# it can be a literal IPv4 address or a hostname (e.g. a dynamic-DNS address); +# hostnames are resolved by the server, since the client can only use literal +# IPs. on Linux builds with the sandbox enabled, the hostname is only resolved +# at startup, so a DNS change requires a server restart. ip=127.0.0.1 # distance at which other players and NPCs become visible. # this value is used for calculating chunk size diff --git a/src/main.cpp b/src/main.cpp index e57faa19..776f0018 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -112,6 +112,14 @@ int main() { initsignals(); settings::init(); + + /* + * If the shard IP is a hostname, resolve it to a literal IPv4 address now. + * This must happen before the sandbox goes up, since the login server isn't + * allowed to open new sockets (like the DNS query sockets) afterwards. + */ + settings::resolveShardIP(); + Database::init(); Rand::init(getTime()); TableData::init(); diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index fda4387f..6d7a90d8 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -480,7 +480,16 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) { std::cout << "Connecting to shard server" << std::endl; ) - const char* shard_ip = settings::SHARDSERVERIP.c_str(); + /* + * On builds that can do DNS lookups at runtime (Windows, and Linux builds + * without the sandbox), re-resolve the shard address on every character + * select so that changes to a dynamic-DNS address are picked up without a + * restart. Sandboxed Linux builds resolve the address at startup instead. + */ + if (settings::canResolveShardIP()) + settings::resolveShardIP(); + + std::string shard_ip = settings::SHARDSERVERIP; /* * Work around the issue of not being able to connect to a local server if @@ -490,9 +499,12 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) { if (settings::LOCALHOSTWORKAROUND && sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) shard_ip = "127.0.0.1"; - memcpy(resp.g_FE_ServerIP, shard_ip, strlen(shard_ip)); + // g_FE_ServerIP is only 16 bytes and the client expects a null-terminated + // string, so make sure we never write past it + size_t ipLen = std::min(shard_ip.size(), sizeof(resp.g_FE_ServerIP) - 1); + memcpy(resp.g_FE_ServerIP, shard_ip.c_str(), ipLen); + resp.g_FE_ServerIP[ipLen] = '\0'; - resp.g_FE_ServerIP[strlen(shard_ip)] = '\0'; resp.g_FE_ServerPort = settings::SHARDPORT; LoginMetadata *lm = new LoginMetadata(); diff --git a/src/settings.cpp b/src/settings.cpp index 0e72ecf3..28ef378e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -6,6 +6,14 @@ #include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + // defaults :) int settings::VERBOSITY = 1; bool settings::SANDBOX = true; @@ -157,3 +165,33 @@ void settings::init() { } } } + +void settings::resolveShardIP() { + // if it's already a literal IPv4 address, there's nothing to do + in_addr addr = {}; + if (inet_pton(AF_INET, SHARDSERVERIP.c_str(), &addr) == 1) + return; + + addrinfo hints = {}; + hints.ai_family = AF_INET; // the client can only handle IPv4 addresses + hints.ai_socktype = SOCK_STREAM; + + addrinfo* result = nullptr; + if (getaddrinfo(SHARDSERVERIP.c_str(), nullptr, &hints, &result) != 0) { + std::cerr << "[WARN] Could not resolve shard IP \"" << SHARDSERVERIP + << "\". Check the \"ip\" setting in the [shard] section of config.ini" << std::endl; + return; + } + + // use the first IPv4 address that was resolved + char resolved[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &((sockaddr_in*)result->ai_addr)->sin_addr, resolved, sizeof(resolved)); + freeaddrinfo(result); + + std::string resolvedIP = resolved; + + if (resolvedIP != SHARDSERVERIP) { + std::cout << "[INFO] Resolved shard IP \"" << SHARDSERVERIP << "\" to " << resolvedIP << std::endl; + SHARDSERVERIP = resolvedIP; + } +} diff --git a/src/settings.hpp b/src/settings.hpp index 7aea34fd..69b36d6a 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -52,4 +52,30 @@ namespace settings { extern bool REMOVEEXPIREDITEMSFROMBANK; void init(); + + /* + * Whether the shard IP can be resolved at runtime, after the sandbox is up. + * The Linux seccomp sandbox blocks opening new sockets, which getaddrinfo() + * needs, so hostnames can only be resolved at startup there. Every other + * platform, and Linux builds without the sandbox, can resolve any time. + */ + inline bool canResolveShardIP() { +#ifdef __linux__ +#ifdef CONFIG_NOSANDBOX + return true; +#else + return !SANDBOX; +#endif +#else + return true; +#endif + } + + /* + * The client only understands literal IPv4 addresses in the shard select + * packet, so if the configured shard IP is a hostname it gets resolved here. + * Safe to call repeatedly; it does nothing if the address is already a + * literal IP or the hostname has already been resolved. + */ + void resolveShardIP(); }