diff --git a/masterserver/README.md b/masterserver/README.md index 91a90383..7d306e09 100644 --- a/masterserver/README.md +++ b/masterserver/README.md @@ -6,10 +6,10 @@ Servers need to send frequent updates to let the master server know they are sti ## Prerequisites -- MySQL server (see `config.inc.php` for default configuration) -- PHP 5.?+ - - Socket PHP extension - - pdo_mysql extension +- MySQL server (see `config.inc.php` for default configuration) +- PHP 5.?+ + - Socket PHP extension + - pdo_mysql extension ## /register.php (POST) @@ -17,13 +17,25 @@ Register a game server by writing its details to the database. ### Parameters -All three parameters are required. +`port`, `version`, and `name` are required. `ip` is optional. - **port**: The game server's port, such as `35666`. -- **version**: The game server's version, such as `20191101`. +- **version**: The game server's version, such as `20241208`. - **name**: A name to identify the server to clients. +- **ip** _(optional)_: The IP address or hostname to advertise to clients. When omitted, the TCP connection's source address is used. -The game server's IP address is also recorded. + If advertising with a reverse proxy (running with the `server=listen` option on the proxy server, and with `serverproxy=` on the game server), supply the reverse proxy's address here so that clients connect to the proxy. The IP must contain only valid IPv4/IPv6/hostname characters and be at most 253 characters long. + +On first registration, the master server opens a TCP connection to the advertised address and port to verify the server is publicly reachable. Subsequent registrations from the same address and port act as a heartbeat and skip this check. + +### Responses + +| Body | Meaning | +| ---- | ------- | +| `OK` | Registered successfully | +| `CONNECT FAILED: ip:port` | Master server could not TCP-connect to the advertised address — port forwarding or the reverse proxy is not reachable | +| `'port' not set` (HTTP 400) | Missing required parameter | +| `Invalid 'ip'` (HTTP 400) | `ip` override contains disallowed characters or exceeds 253 characters | ### Example @@ -34,7 +46,14 @@ $ curl -d "port=35666&name=Test&version=20190111" -X POST http://127.0.0.1:8000/ OK ``` -If the registry server fails to connect to the game server, it responds: +Registering the server at 127.0.0.1 via a reverse proxy at 203.0.113.1: + +``` +$ curl -d "port=35666&name=Test&version=20190111&ip=203.0.113.1" -X POST http://127.0.0.1:8000/register.php +OK +``` + +If the master server fails to connect to the advertised address: ``` CONNECT FAILED: 127.0.0.1:35666 @@ -42,7 +61,7 @@ CONNECT FAILED: 127.0.0.1:35666 ## /list.php (GET) -Retrieve a list of registered servers, each in a colon-delimted line of (in order) its IP address, port, version, and name. +Retrieve a list of registered servers, each as a colon-delimited line of (in order) its IP address, port, version, and name. ### Example diff --git a/masterserver/register.php b/masterserver/register.php index e8084a56..90e47415 100644 --- a/masterserver/register.php +++ b/masterserver/register.php @@ -21,11 +21,26 @@ $name = $_POST['name']; $version = (int)($_POST['version']); +// Optional IP override: used when the game server tunnels through a reverse +// proxy and wants to advertise the proxy's address instead of its own NAT'd IP. +$ip = $_SERVER['REMOTE_ADDR']; +if (isset($_POST['ip'])) +{ + $candidate = trim($_POST['ip']); + if (strlen($candidate) > 0 && strlen($candidate) <= 253 && preg_match('/^[0-9a-zA-Z:.\-]+$/', $candidate)) + $ip = $candidate; + else + { + http_response_code(400); + die("Invalid 'ip'"); + } +} + $db = new PDO($database['dsn'], $database['user'], $database['password']); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->exec("CREATE TABLE IF NOT EXISTS `servers` ( - `ip` varchar(32) NOT NULL, + `ip` varchar(253) NOT NULL, `port` int(11) NOT NULL, `version` int(11) NOT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -34,22 +49,22 @@ );"); $q = $db->prepare("SELECT * FROM `servers` WHERE `ip` = :ip AND `port` = :port AND `version` = :version"); -$result = $q->execute(array("ip" => $_SERVER['REMOTE_ADDR'], "port" => $port, "version" => $version)); +$result = $q->execute(array("ip" => $ip, "port" => $port, "version" => $version)); if($q->fetch() === false) { //First time server is added. Try if we can connect to the server. If not, then port forwarding is most likely not setup. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - $result = @socket_connect($socket, $_SERVER['REMOTE_ADDR'], $port); + $result = @socket_connect($socket, $ip, $port); if ($result === false) { - die("CONNECT FAILED: " . $_SERVER['REMOTE_ADDR'] . ":" . $port); + die("CONNECT FAILED: " . $ip . ":" . $port); }else{ socket_close($socket); } } $q = $db->prepare("INSERT INTO `servers`(`ip`, `port`, `version`, `name`) VALUES (:ip, :port, :version, :name) ON DUPLICATE KEY UPDATE `name` = :name, `version` = :version, `last_update` = NOW()"); -$q->execute(array("ip" => $_SERVER['REMOTE_ADDR'], "port" => $port, "version" => $version, "name" => $name)); +$q->execute(array("ip" => $ip, "port" => $port, "version" => $version, "name" => $name)); echo "OK"; ?> diff --git a/src/multiplayer_server.cpp b/src/multiplayer_server.cpp index 18dac4b8..4b06bff4 100644 --- a/src/multiplayer_server.cpp +++ b/src/multiplayer_server.cpp @@ -84,6 +84,7 @@ void GameServer::connectToProxy(sp::io::network::Address address, int port) LOG(ERROR) << "Failed to connect to proxy"; return; } + proxy_address = address.getHumanReadable()[0]; ClientInfo info; socket->setBlocking(false); @@ -718,7 +719,10 @@ void GameServer::runMasterServerUpdateThread() sp::io::http::Request http(hostname, port); while(!isDestroyed() && master_server_url != "") { - auto response = http.post(uri, "port=" + string(listen_port) + "&name=" + server_name + "&version=" + string(version_number)); + string body = "port=" + string(listen_port) + "&name=" + server_name + "&version=" + string(version_number); + if (proxy_address != "") + body += "&ip=" + proxy_address; + auto response = http.post(uri, body); if (response.status != 200) { LOG(WARNING) << "Failed to register at master server " << master_server_url << " (status " << response.status << ")"; diff --git a/src/multiplayer_server.h b/src/multiplayer_server.h index 10a9e027..4f6cf5ed 100644 --- a/src/multiplayer_server.h +++ b/src/multiplayer_server.h @@ -92,6 +92,7 @@ class GameServer : public Updatable std::vector ecs_entity_version; string master_server_url; + string proxy_address; std::thread master_server_update_thread; MasterServerState master_server_state = MasterServerState::Disabled; public: