diff --git a/rsx-server-core/src/main/java/net/rustnotmc/server/Server.java b/rsx-server-core/src/main/java/net/rustnotmc/server/Server.java index 94d3b16..9bd0ac8 100644 --- a/rsx-server-core/src/main/java/net/rustnotmc/server/Server.java +++ b/rsx-server-core/src/main/java/net/rustnotmc/server/Server.java @@ -62,6 +62,17 @@ public final class Server { private volatile net.rustnotmc.server.player.PlayerData playerData = new net.rustnotmc.server.player.PlayerData(null); private volatile net.rustnotmc.server.world.LodGenerator lod; + + /** + * The address the server listens on. + * + * By default the server listens on every address, so that it can be + * reached from the network. When the game is reached through a tunnel + * (bore, chisel), the tunnel connects to the loopback, and the port + * should not hang open on the local network: then the address is + * "127.0.0.1". + */ + private volatile String bindAddress = "0.0.0.0"; private volatile HitboxRegistry hitboxes = new HitboxRegistry(null); private volatile ServerSocket listener; @@ -255,6 +266,15 @@ public final class Server { } } + /** Where the server listens: "0.0.0.0" for the whole network. */ + public String bindAddress() { + return this.bindAddress; + } + + public void bindAddress(String address) { + this.bindAddress = address; + } + /** The far view of the world. */ public net.rustnotmc.server.world.LodGenerator lod() { return this.lod; @@ -392,7 +412,7 @@ public final class Server { public void start() throws IOException { this.listener = new ServerSocket(); this.listener.setReuseAddress(true); - this.listener.bind(new InetSocketAddress("0.0.0.0", this.port)); + this.listener.bind(new InetSocketAddress(this.bindAddress, this.port)); this.running = true; this.acceptThread = new Thread(this::acceptLoop, "rsx-accept"); diff --git a/rsx-server-core/src/main/java/net/rustnotmc/server/ServerMain.java b/rsx-server-core/src/main/java/net/rustnotmc/server/ServerMain.java index 975fad0..708cb42 100644 --- a/rsx-server-core/src/main/java/net/rustnotmc/server/ServerMain.java +++ b/rsx-server-core/src/main/java/net/rustnotmc/server/ServerMain.java @@ -15,7 +15,7 @@ import net.rustnotmc.server.world.World; * * run-server.ps1 [--port N] [--accounts path] [--name text] [--motd text] * [--world path] [--seed N] [--token-key text] [--audience name] - * [--admins path] [--gamemode survival|creative] + * [--admins path] [--gamemode survival|creative] [--bind address] * run-server.ps1 --register Player [--password text] * run-server.ps1 --issue-token Player * @@ -53,6 +53,7 @@ public final class ServerMain { Path hitboxes = null; String tokenKey = null; String audience = "default"; + String bindAddress = "0.0.0.0"; Path adminsFile = null; String issueToken = null; String devToken = null; @@ -86,7 +87,9 @@ public final class ServerMain { launcherApi = value; } else if ("--allow-local-accounts".equals(flag)) { allowLocalAccounts = true; - } else if ("--gamemode".equals(flag)) { + } else if ("--bind".equals(flag)) { + bindAddress = value; + } else if ("--gamemode".equals(flag)) { gameMode = value; } else if ("--name".equals(flag)) { name = value; @@ -111,6 +114,9 @@ public final class ServerMain { server.gameMode("creative".equalsIgnoreCase(gameMode) ? net.rustnotmc.protocol.Packets.GAMEMODE_CREATIVE : net.rustnotmc.protocol.Packets.GAMEMODE_SURVIVAL); + // where the server listens: every address by default, the loopback + // alone when the game is reached through a tunnel + server.bindAddress(bindAddress); if (launcherApi != null) { // the launcher is where accounts live, so it is the way in. The diff --git a/tools/tunnel-game.ps1 b/tools/tunnel-game.ps1 new file mode 100644 index 0000000..50927d4 --- /dev/null +++ b/tools/tunnel-game.ps1 @@ -0,0 +1,83 @@ +# Opens the game server to the internet through a tunnel. +# +# tools\tunnel-game.ps1 [-Port 25599] [-Remote 9000] [-Tool bore|chisel] +# +# The tunnel runs on this machine, the game server listens on the loopback, +# and the far side gets a port it can connect to. The address to put into the +# launcher is printed at the end. +# +# The bore server that serves ru.zern.cc listens on 7835 and holds the secret +# that the client and the server share. The old bat file on the desktop points +# at zernmc.ru, which is no longer reachable; ru.zern.cc is the one that works. +# +# ASCII only: PowerShell 5.1 reads a .ps1 without a byte order mark using the +# system ANSI code page. + +param( + [int]$Port = 25599, + [int]$Remote = 9000, + [string]$Tool = 'bore', + [string]$Host_ = 'ru.zern.cc', + [string]$Secret = 'skyzernvintageforever', + [string]$Bore = "$env:USERPROFILE\Desktop\bore.exe", + [string]$Chisel = "$env:USERPROFILE\Downloads\chisel.exe" +) + +$ErrorActionPreference = 'Stop' +$Root = Split-Path -Parent $PSScriptRoot +if (-not $Root) { $Root = 'G:\RustNotMC' } +$LogDir = Join-Path $Root 'logs' +New-Item -ItemType Directory -Force -Path $LogDir | Out-Null +$Log = Join-Path $LogDir 'tunnel.log' + +# the tunnel that is already running goes first +Get-Process bore, chisel -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "stopping the old tunnel (pid $($_.Id))" + Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue +} +Start-Sleep -Seconds 1 + +if ($Tool -eq 'chisel') { + if (-not (Test-Path $Chisel)) { throw "no chisel at $Chisel" } + # chisel client --server ssh://user:pass@host:port --remote-forward ... + $user = 'tunnel' + $args = @( + 'client', + "--server=ssh://$user`:$Secret@$Host_`:7835", + '--remote-forward', "tcp:0.0.0.0`:$Remote`:127.0.0.1`:$Port", + '--keepalive-interval', '10' + ) + Write-Host "chisel: $($args -join ' ')" +} else { + if (-not (Test-Path $Bore)) { throw "no bore at $Bore" } + $args = @('local', "$Port", '--secret', $Secret, '--to', $Host_, '-p', "$Remote") + Write-Host "bore: $($args -join ' ')" +} + +Remove-Item $Log, "$Log.err" -ErrorAction SilentlyContinue +$proc = Start-Process -FilePath $(if ($Tool -eq 'chisel') { $Chisel } else { $Bore }) ` + -ArgumentList $args -RedirectStandardOutput $Log -RedirectStandardError "$Log.err" ` + -WindowStyle Hidden -PassThru +Write-Host "tunnel pid $($proc.Id), log $Log" + +# the tunnel says when it is up +$up = $false +for ($i = 0; $i -lt 30; $i++) { + Start-Sleep -Milliseconds 500 + $text = (Get-Content $Log -Raw -ErrorAction SilentlyContinue) + + (Get-Content "$Log.err" -Raw -ErrorAction SilentlyContinue) + if ($text -match 'listening|Connected|connected|remote_port') { $up = $true; break } +} +if (-not $up) { + Write-Host 'the tunnel did not say that it is up. The log:' + Get-Content $Log, "$Log.err" -ErrorAction SilentlyContinue + exit 1 +} + +Write-Host '' +Write-Host " the game is at $Host_`:$Remote" +Write-Host " the local port is $Port, the server must listen on 127.0.0.1" +Write-Host " the game client asks for the host and the port; the token must be" +Write-Host " for the audience the server expects (by default 'default')" +Write-Host '' +Get-Content $Log -ErrorAction SilentlyContinue