Skip to content

Commit

Permalink
wip nat punch
Browse files Browse the repository at this point in the history
  • Loading branch information
trippyone committed May 17, 2024
1 parent ce6ed44 commit 1a53472
Show file tree
Hide file tree
Showing 43 changed files with 2,211 additions and 12 deletions.
3 changes: 3 additions & 0 deletions Fika.Core/Fika.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
<HintPath>..\References\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="websocket-sharp">
<HintPath>..\References\websocket-sharp.dll</HintPath>
</Reference>
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(OS)' == 'Windows_NT' ">
Expand Down
69 changes: 57 additions & 12 deletions Fika.Core/Networking/FikaClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// © 2024 Lacyway All Rights Reserved

using Aki.Common.Http;
using Aki.Custom.Airdrops;
using BepInEx.Logging;
using Comfort.Common;
Expand All @@ -17,8 +18,10 @@
using Fika.Core.Modding.Events;
using Fika.Core.Networking.Http;
using Fika.Core.Networking.Http.Models;
using Fika.Core.Networking.NatPunch;
using Fika.Core.Networking.Packets.GameWorld;
using Fika.Core.Networking.Packets.Player;
using Fika.Core.Networking.Websocket;
using HarmonyLib;
using LiteNetLib;
using LiteNetLib.Utils;
Expand All @@ -27,6 +30,7 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;

namespace Fika.Core.Networking
Expand All @@ -52,11 +56,13 @@ public NetManager NetClient
public NetPeer ServerConnection { get; private set; }
public string IP { get; private set; }
public int Port { get; private set; }
public string StunIP { get; private set; }
public int StunPort { get; private set; }
public bool SpawnPointsReceived { get; private set; } = false;
private readonly ManualLogSource clientLogger = BepInEx.Logging.Logger.CreateLogSource("Fika.Client");
public bool ClientReady = false;

protected void Start()
protected async void Start()
{
packetProcessor.SubscribeNetSerializable<PlayerStatePacket>(OnPlayerStatePacketReceived);
packetProcessor.SubscribeNetSerializable<GameTimerPacket>(OnGameTimerPacketReceived);
Expand Down Expand Up @@ -94,22 +100,61 @@ protected void Start()
EnableStatistics = true
};

_netClient.Start();
EFT.UI.ConsoleScreen.Log("getting stun endpoint");
var stunIpEndpoint = NatPunchUtils.CreateStunEndPoint();
EFT.UI.ConsoleScreen.Log($"got stun endpoint: {stunIpEndpoint.Remote.Address.ToString()}:{stunIpEndpoint.Remote.Port}");
EFT.UI.ConsoleScreen.Log($"got local port: {stunIpEndpoint.Local.Port}");

GetHostRequest body = new(CoopHandler.GetServerId());
GetHostResponse result = FikaRequestHandler.GetHost(body);
FikaNatPunchClient fikaNatPunchClient = new FikaNatPunchClient();

IP = result.Ip;
Port = result.Port;

if (string.IsNullOrEmpty(IP))
if (stunIpEndpoint != null)
{
Singleton<PreloaderUI>.Instance.ShowErrorScreen("Network Error", "Unable to connect to the server. IP and/or Port was empty when requesting data!");
fikaNatPunchClient.Connect();

EFT.UI.ConsoleScreen.Log("exchanging stun endpoint with server");
GetHostStunRequest getStunRequest = new GetHostStunRequest(stunIpEndpoint.Remote.Address.ToString(), stunIpEndpoint.Remote.Port);
GetHostStunResponse getStunResponse = await fikaNatPunchClient.GetHostStun(getStunRequest);

EFT.UI.ConsoleScreen.Log($"received stun endpoint from server: {getStunResponse.StunIp}:{getStunResponse.StunPort}");

StunIP = getStunResponse.StunIp;
StunPort = getStunResponse.StunPort;

if (string.IsNullOrEmpty(StunIP))
{
EFT.UI.ConsoleScreen.LogWarning("Failed to obtain STUN endpoint from host.");
}
else
{
_netClient.Start(stunIpEndpoint.Local.Port);

NatPunchUtils.PunchNat(_netClient, new IPEndPoint(IPAddress.Parse(StunIP), StunPort));

ServerConnection = _netClient.Connect(StunIP, StunPort, "fika.core");
}
}
else

await Task.Delay(2000);

if (_netClient.ConnectedPeersCount == 0)
{
ServerConnection = _netClient.Connect(IP, Port, "fika.core");
};
EFT.UI.ConsoleScreen.LogWarning("Connection to Stun endpoint failed, falling back to public ip");

GetHostRequest body = new(CoopHandler.GetServerId());
GetHostResponse result = FikaRequestHandler.GetHost(body);

IP = result.Ip;
Port = result.Port;

if (string.IsNullOrEmpty(IP))
{
Singleton<PreloaderUI>.Instance.ShowErrorScreen("Network Error", "Unable to connect to the server. IP and/or Port was empty when requesting data!");
}
else
{
ServerConnection = _netClient.Connect(IP, Port, "fika.core");
};
}

Singleton<FikaClient>.Create(this);
FikaEventDispatcher.DispatchEvent(new FikaClientCreatedEvent(this));
Expand Down
6 changes: 6 additions & 0 deletions Fika.Core/Networking/FikaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Fika.Core.Modding.Events;
using Fika.Core.Networking.Http;
using Fika.Core.Networking.Http.Models;
using Fika.Core.Networking.NatPunch;
using Fika.Core.Networking.Packets.GameWorld;
using Fika.Core.Networking.Packets.Player;
using LiteNetLib;
Expand Down Expand Up @@ -57,6 +58,7 @@ public NetManager NetServer
private readonly ManualLogSource serverLogger = BepInEx.Logging.Logger.CreateLogSource("Fika.Server");
public bool ServerReady = false;
private int _currentNetId;
public delegate void SimpleEventHandler();

public async void Start()
{
Expand Down Expand Up @@ -97,6 +99,10 @@ public async void Start()
EnableStatistics = true
};

var natPunchServer = new FikaNatPunchServer(_netServer);

natPunchServer.Listen();

if (FikaPlugin.UseUPnP.Value)
{
bool upnpFailed = false;
Expand Down
34 changes: 34 additions & 0 deletions Fika.Core/Networking/Models/GetHostStunRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Aki.Common.Http;
using Fika.Core.Coop.Components;
using System.Runtime.Serialization;

namespace Fika.Core.Networking.Http.Models
{
[DataContract]
public struct GetHostStunRequest
{
[DataMember(Name = "requestType")]
public string RequestType;

[DataMember(Name = "clientId")]
public string ClientId;

[DataMember(Name = "serverId")]
public string ServerId;

[DataMember(Name = "stunIp")]
public string StunIp;

[DataMember(Name = "stunPort")]
public int StunPort;

public GetHostStunRequest(string stunIp, int stunPort)
{
RequestType = GetType().Name;
ClientId = RequestHandler.SessionId;
ServerId = CoopHandler.GetServerId();
StunIp = stunIp;
StunPort = stunPort;
}
}
}
25 changes: 25 additions & 0 deletions Fika.Core/Networking/Models/GetHostStunResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Runtime.Serialization;

[DataContract]
public struct GetHostStunResponse
{
[DataMember(Name = "requestType")]
public string RequestType;

[DataMember(Name = "clientId")]
public string ClientId;

[DataMember(Name = "StunIp")]
public string StunIp;

[DataMember(Name = "StunPort")]
public int StunPort;

public GetHostStunResponse(string clientId, string stunIp, int stunPort)
{
RequestType = GetType().Name;
ClientId = clientId;
StunIp = stunIp;
StunPort = stunPort;
}
}
111 changes: 111 additions & 0 deletions Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using LiteNetLib;
using Aki.Common.Http;
using Fika.Core.Networking.Websocket;
using WebSocketSharp;
using System.Security.Policy;
using System;
using Newtonsoft.Json;
using System.Threading.Tasks;
using Fika.Core.Networking.Http.Models;

namespace Fika.Core.Networking.NatPunch
{
public class FikaNatPunchClient
{
public string Host { get; set; }
public string Url { get; set; }
public string SessionId { get; set; }

private WebSocket _webSocket;
private TaskCompletionSource<string> _receiveTaskCompletion;

public StunIpEndPoint StunIpEndPoint { get; set; }

public FikaNatPunchClient()
{
// Assuming http protocol is always used
Host = RequestHandler.Host.Replace("http", "ws");
SessionId = RequestHandler.SessionId;
Url = $"{Host}/{SessionId}?";

_webSocket = new WebSocket(Url)
{
WaitTime = TimeSpan.FromMinutes(1),
EmitOnPing = true
};

_webSocket.OnOpen += WebSocket_OnOpen;
_webSocket.OnError += WebSocket_OnError;
_webSocket.OnMessage += WebSocket_OnMessage;
}

public void Connect()
{
_webSocket.Connect();
}

public void Close()
{
_webSocket.Close();
}

private void WebSocket_OnOpen(object sender, EventArgs e)
{
//Logger.LogInfo($"Connected to NAT Helper");
EFT.UI.ConsoleScreen.Log("Connected to FikaNatPunchService as client");
}

private void WebSocket_OnMessage(object sender, MessageEventArgs e)
{
if (_receiveTaskCompletion == null)
return;

if (_receiveTaskCompletion.Task.Status == TaskStatus.RanToCompletion)
return;

if (e == null)
return;

if (string.IsNullOrEmpty(e.Data))
return;

_receiveTaskCompletion.SetResult(e.Data);
}

private void WebSocket_OnError(object sender, ErrorEventArgs e)
{
EFT.UI.ConsoleScreen.LogError($"Websocket error {e}");
_webSocket.Close();
}

private void Send<T1>(T1 o)
{
var data = JsonConvert.SerializeObject(o);
_webSocket.Send(data);
}

private async Task<T2> Receive<T2>()
{
var data = await _receiveTaskCompletion.Task;
var obj = JsonConvert.DeserializeObject<T2>(data);

return obj;
}

private async Task<T2> SendAndReceiveAsync<T1, T2>(T1 o)
{
_receiveTaskCompletion = new TaskCompletionSource<string>();

Send<T1>(o);

var data = await Receive<T2>();

return data;
}

public async Task<GetHostStunResponse> GetHostStun(GetHostStunRequest getHostStunRequest)
{
return await SendAndReceiveAsync<GetHostStunRequest, GetHostStunResponse>(getHostStunRequest);
}
}
}
Loading

0 comments on commit 1a53472

Please sign in to comment.