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);