Make the player proxy follow the Factorio handshake and route by version

The proxy now parses the full message header, so confirms and fragmented
ConnectionRequestReplyConfirm messages (which carry the mod list) are
forwarded intact, with the server request id patched into the first
fragment. It resends the connection request until the instance answers,
starts over when a client reconnects from the same socket (as Factorio
does after LuaPlayer::connect_to_server), answers pings from the server
browser and tells clients the same 508 byte max packet size Factorio
servers use.

Routing is decided by the new playerProxyRoute controller hook, which is
given the name and version of the player, then a dedicated port set with
instance.player_proxy_port, the instance the player was last on (taken
from the user's instance stats instead of a separate routing store), and
controller.player_proxy_default_instance_id.

The proxy is now off by default, as port 34197 is in the default
host.factorio_port_range. Leftovers of the per-instance proxy on hosts
are removed.
This commit is contained in:
Danielv123 committed 2026-10-29 20:53:40 +01:00
1 parent 80f244fddd
commit b4856bdd55
13 files changed
+1334 -602

No files matched your search

@@ -2,6 +2,7 @@ import type Controller from "./Controller.js";
import type InstanceRecord from "./InstanceRecord.js";
import type ControlConnection from "./ControlConnection.js";
import type HostConnection from "./HostConnection.js";
import type { PlayerProxyRouteRequest } from "./PlayerProxyServer.js";
import * as lib from "@clusterio/lib";
export type ControllerPluginContext = lib.PluginLoadContext<{
@@ -28,6 +29,7 @@ export class ControllerHooks extends lib.AsyncHookCollection {
this.modsUpdated = this.newHook();
this.rolesUpdated = this.newHook();
this.playerEvent = this.newHook();
this.playerProxyRoute = this.newHook();
}
/**
@@ -266,6 +268,24 @@ export class ControllerHooks extends lib.AsyncHookCollection {
* @param event - Information about the event.
*/
readonly playerEvent: lib.AsyncHook<[instance: InstanceRecord, event: lib.PlayerEvent]>;
/**
* Called to pick the instance a player connecting through the player proxy goes to
*
* Invoked when a Factorio client has sent its name to the player proxy
* enabled with controller.player_proxy_port. Handlers return the ID of
* the instance to send the player to, or undefined to leave the choice
* to other plugins and the controller. If the instance is stopped the
* proxy starts it, which can take longer than the client is willing to
* wait.
*
* Factorio clients connect twice when joining a server they don't have a
* key for, so a handler must give the same answer for both attempts.
*
* @param request - Name, version and connection details of the player.
* @returns ID of instance to route the player to.
*/
readonly playerProxyRoute: lib.AsyncHook<[request: PlayerProxyRouteRequest], number | undefined>;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging