diff --git a/win/gatuna-client/SessionManager.cs b/win/gatuna-client/SessionManager.cs index 6dda735..9d6dcf8 100644 --- a/win/gatuna-client/SessionManager.cs +++ b/win/gatuna-client/SessionManager.cs @@ -88,15 +88,48 @@ sealed class SessionManager : IDisposable } } + /// + /// Compute a deterministic mirror port from the server MAC and the + /// upstream port. XOR the upstream port with (mac[0]<<8 | mac[5]), + /// then ensure the result is outside the privileged range. + /// + static ushort ComputeMirrorPort(byte[] serverMac, ushort upstreamPort) + { + var k = (ushort)((serverMac[0] << 8) | serverMac[5]); + var port = (ushort)(upstreamPort ^ k); + if (port < 1024) + port += 1024; + return port; + } + /// /// Start a local TCP listener for the given upstream. Returns the mirror /// port, or 0 on failure. /// public int StartListener(UpstreamEntry upstream) { - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; + if (_serverMac == null) + return 0; + + var preferred = ComputeMirrorPort(_serverMac, upstream.Port); + + // Try the deterministic port first; fall back to OS assignment. + TcpListener listener; + int port; + try + { + listener = new TcpListener(IPAddress.Loopback, preferred); + listener.Start(); + port = ((IPEndPoint)listener.LocalEndpoint).Port; + } + catch + { + listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + port = ((IPEndPoint)listener.LocalEndpoint).Port; + Log?.Invoke($"port {preferred} in use, fell back to {port}"); + } + var state = new ListenerState(listener, upstream); _listeners[port] = state; _ = AcceptLoop(state);