From 3b437580543de1bd9e3c7e706b06a239bf26c8dd Mon Sep 17 00:00:00 2001 From: Thomas Woischnig Date: Mon, 4 Dec 2023 01:58:46 +0100 Subject: [PATCH] new method to query client ip --- .../Runtime/LobbyClient.dll | Bin 34304 -> 35840 bytes LobbyClient/LobbyClient.cs | 60 +++++++++++++++++- LobbyClient/UdpEchoServer.cs | 12 ++++ LobbyClientTest/Program.cs | 13 ++-- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll b/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll index ec17251edefc708ec7da8b561aab4673e8a0f314..f867f383143510a688a030ea0c0f00b170099d16 100644 GIT binary patch literal 35840 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 literal 34304 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 { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); }); } - + /// + /// This callback is called if the nat punch requires the game server/client udp sockets sends a packet to a server so that + /// the external mapped port by the firewall can be seen by the external point. + /// + /// The endpoint to send data to + /// The message byte buffer to send + /// Length of the data to send public delegate void SendUdpMessageCallback(IPEndPoint remoteEndpoint, byte[] messageBuffer, int messageLength); - public void RequestLobbyNatPunch(Guid lobbyId, string? password, SendUdpMessageCallback sendUdpCallback) + + /// + /// This function requests a nat punch for this client from the game host. First the external port for our game client is detected by + /// sending a udp packet to the lobby server who then forwards the nat punch request to the host with the seen external ip and port. + /// The game host then sends a udp packet to the client to open it's nat bridge and when done sends a event to the client via the lobby server. + /// The client receives the external port and ip seen by the lobby server for the host and can connect to the host. + /// + /// The lobby to request a nat punch for + /// Optional password of lobby + /// A callback to send udp data if you have a udp game client ready and bound + public void RequestLobbyNatPunch(Guid lobbyId, string? password, SendUdpMessageCallback sendUdpToGetExternalPortMappingCallback) { byte[]? passwordHash = null; if (!string.IsNullOrEmpty(password) && lobbyInformation.ContainsKey(lobbyId) && lobbyInformation[lobbyId].PasswordSalt != null) @@ -133,7 +149,45 @@ namespace Lobbies Task.Run(() => { - QueryExternalIpAndPort(sendUdpCallback); + QueryExternalIpAndPort(sendUdpToGetExternalPortMappingCallback); + var lobbyRequestNatPunch = new LobbyRequestNatPunch() + { + LobbyId = lobbyId, + PasswordHash = passwordHash, + ClientIp = externalIp, + ClientPort = externalPort + }; + + byte[] messageData = bufferRental.Rent(); + var len = lobbyRequestNatPunch.Serialize(messageData); + _ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); }); + }); + } + + /// + /// This function requests a nat punch for this client from the game host. First the external port for our game client is detected by + /// sending a udp packet to the lobby server who then forwards the nat punch request to the host with the seen external ip and port. + /// The game host then sends a udp packet to the client to open it's nat bridge and when done sends a event to the client via the lobby server. + /// The client receives the external port and ip seen by the lobby server for the host and can connect to the host. + /// + /// The lobby to request a nat punch for + /// Optional password of lobby + /// The port the game client will later use. We create a udp socket on it and send a packet to the lobby server to get the firewall to map that port for a short period of time + /// after that the udp client will be disposed + public void RequestLobbyNatPunch(Guid lobbyId, string? password, int port = 0) + { + byte[]? passwordHash = null; + if (!string.IsNullOrEmpty(password) && lobbyInformation.ContainsKey(lobbyId) && lobbyInformation[lobbyId].PasswordSalt != null) + passwordHash = PasswordHash.Hash(password, lobbyInformation[lobbyId].PasswordSalt!); + + Task.Run(() => + { + using (var udpEchoServer = new UdpEchoServer()) + { + udpEchoServer.Start(port); + QueryExternalIpAndPort(udpEchoServer.Send); + } + var lobbyRequestNatPunch = new LobbyRequestNatPunch() { LobbyId = lobbyId, diff --git a/LobbyClient/UdpEchoServer.cs b/LobbyClient/UdpEchoServer.cs index db63b30..b00d937 100644 --- a/LobbyClient/UdpEchoServer.cs +++ b/LobbyClient/UdpEchoServer.cs @@ -40,6 +40,18 @@ namespace Lobbies serverSocketV6.Send(magicRequest, magicRequest.Length, remoteEndpoint); } + public void Send(IPEndPoint remoteEndpoint, byte[] messageData, int messageLength) + { + if (!running || serverSocketV4 == null || serverSocketV6 == null) + throw new Exception("Listener not running!"); + + for (int i = 0; i < 16; i++) + if (remoteEndpoint.AddressFamily == AddressFamily.InterNetwork) + serverSocketV4.Send(messageData, messageLength, remoteEndpoint); + else + serverSocketV6.Send(messageData, messageLength, remoteEndpoint); + } + /// /// Listen to requests and fire events /// diff --git a/LobbyClientTest/Program.cs b/LobbyClientTest/Program.cs index 8d044a0..16bed99 100644 --- a/LobbyClientTest/Program.cs +++ b/LobbyClientTest/Program.cs @@ -21,12 +21,12 @@ int myExternalPort = -1; bool running = true; bool connected = false; -_ = Task.Run(async () => +_ = Task.Run(() => { while (running) { foreach (var lobbyEvent in lobbyClient.ReadEvents(20)) - { + { switch (lobbyEvent.EventType) { case LobbyClientEventTypes.Connected: @@ -80,7 +80,7 @@ _ = Task.Run(async () => Console.WriteLine($"Direct connection to {result.IPAddress!.ToString()} possible, using direct connection!"); Console.WriteLine($"Connecting game client!"); fakeGameHost.Send(new IPEndPoint(result.IPAddress!, currentLobbyHostInfo!.HostPort), "Hello from Game Client!"); - Console.Write(">"); + Console.Write(">"); } else { @@ -130,9 +130,9 @@ _ = Task.Run(async () => var p = Console.GetCursorPosition(); Console.SetCursorPosition(0, p.Top); - Console.WriteLine($"Received my external ip {seenExternalIpAndPort!.Ip}:{seenExternalIpAndPort.Port}"); + Console.WriteLine($"Received my external ip {seenExternalIpAndPort!.Ip}:{seenExternalIpAndPort.Port}"); Console.Write(">"); - + connected = true; } } @@ -146,7 +146,8 @@ _ = Task.Run(async () => _ = Task.Run(() => { - lobbyClient.QueryExternalIpAndPort((remoteEndpoint, messageData, messageLength) => { + lobbyClient.QueryExternalIpAndPort((remoteEndpoint, messageData, messageLength) => + { fakeGameHost.Send(remoteEndpoint, messageData, messageLength); });