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
37 changes: 28 additions & 9 deletions masterserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,36 @@ 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)

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=<IP ADDRESS>` 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

Expand All @@ -34,15 +46,22 @@ $ 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
```

## /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

Expand Down
25 changes: 20 additions & 5 deletions masterserver/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
?>
6 changes: 5 additions & 1 deletion src/multiplayer_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 << ")";
Expand Down
1 change: 1 addition & 0 deletions src/multiplayer_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class GameServer : public Updatable
std::vector<uint32_t> ecs_entity_version;

string master_server_url;
string proxy_address;
std::thread master_server_update_thread;
MasterServerState master_server_state = MasterServerState::Disabled;
public:
Expand Down
Loading