diff --git a/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll b/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll index ec17251..f867f38 100644 Binary files a/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll and b/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll differ diff --git a/LobbyClient/LobbyClient.cs b/LobbyClient/LobbyClient.cs index ab6c703..caf24bf 100644 --- a/LobbyClient/LobbyClient.cs +++ b/LobbyClient/LobbyClient.cs @@ -123,9 +123,25 @@ namespace Lobbies _ = Task.Run(async () => { 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); });