Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 15 additions & 3 deletions src/servers/CNLoginServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand Down
38 changes: 38 additions & 0 deletions src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

#include <iostream>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <arpa/inet.h>
#endif

// defaults :)
int settings::VERBOSITY = 1;
bool settings::SANDBOX = true;
Expand Down Expand Up @@ -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;
}
}
26 changes: 26 additions & 0 deletions src/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}