Skip to content

Commit

Permalink
Added STUN references + nat punch logic
Browse files Browse the repository at this point in the history
  • Loading branch information
trippyone committed Jun 8, 2024
1 parent 51e72b0 commit 3f5850e
Show file tree
Hide file tree
Showing 40 changed files with 2,112 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Fika.Core/Fika.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
<Reference Include="Unity.TextMeshPro" HintPath="..\References\Unity.TextMeshPro.dll" Private="false" />
<Reference Include="UnityEngine" HintPath="..\References\UnityEngine.dll" Private="false" />
<Reference Include="UnityEngine.UI" HintPath="..\References\UnityEngine.UI.dll" Private="false" />
<Reference Include="websocket-sharp">
<HintPath>..\References\websocket-sharp.dll</HintPath>
</Reference>
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(OS)' == 'Windows_NT' ">
Expand Down
21 changes: 21 additions & 0 deletions Fika.Core/Networking/FikaPingingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
using Fika.Core.Coop.Matchmaker;
using Fika.Core.Networking.Http;
using Fika.Core.Networking.Http.Models;
using Fika.Core.Networking.NatPunch;
using LiteNetLib;
using LiteNetLib.Utils;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Fika.Core.Networking
{
Expand All @@ -16,6 +18,7 @@ internal class FikaPingingClient(string serverId) : INetEventListener
private readonly string serverId = serverId;
private IPEndPoint remoteEndPoint;
private IPEndPoint localEndPoint;
private IPEndPoint remoteStunEndPoint;
public bool Received = false;

public bool Init()
Expand Down Expand Up @@ -54,6 +57,19 @@ public bool Init()
localEndPoint = new(IPAddress.Parse(localIp), port);
}

//TODO: add config to enable this

var localStunEndPoint = NatPunchUtils.CreateStunEndPoint(FikaPlugin.UDPPort.Value);

FikaNatPunchClient fikaNatPunchClient = new FikaNatPunchClient();

fikaNatPunchClient.Connect();

GetHostStunRequest getStunRequest = new GetHostStunRequest(localStunEndPoint.Remote.Address.ToString(), localStunEndPoint.Remote.Port);
GetHostStunResponse getStunResponse = fikaNatPunchClient.GetHostStun(getStunRequest).Result;

remoteStunEndPoint = new IPEndPoint(IPAddress.Parse(getStunResponse.StunIp), getStunResponse.StunPort);

NetClient.Start();

return true;
Expand All @@ -74,6 +90,11 @@ public void PingEndPoint()
{
NetClient.SendUnconnectedMessage(writer, localEndPoint);
}

if (remoteStunEndPoint != null)
{
NetClient.SendUnconnectedMessage(writer, remoteStunEndPoint);
}
}

public void OnConnectionRequest(ConnectionRequest request)
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 Fika.Core.Coop.Components;
using SPT.Common.Http;
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;
}
}
107 changes: 107 additions & 0 deletions Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using WebSocketSharp;

Check failure on line 1 in Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocketSharp' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 1 in Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocketSharp' could not be found (are you missing a using directive or an assembly reference?)
using System;
using Newtonsoft.Json;
using System.Threading.Tasks;
using Fika.Core.Networking.Http.Models;
using SPT.Common.Http;

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;

Check failure on line 16 in Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocket' could not be found (are you missing a using directive or an assembly reference?)
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)
{
EFT.UI.ConsoleScreen.Log("Connected to FikaNatPunchService as client");
}

private void WebSocket_OnMessage(object sender, MessageEventArgs e)

Check failure on line 54 in Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'MessageEventArgs' could not be found (are you missing a using directive or an assembly reference?)
{
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)

Check failure on line 71 in Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'ErrorEventArgs' could not be found (are you missing a using directive or an assembly reference?)
{
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);
}
}
}
146 changes: 146 additions & 0 deletions Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using LiteNetLib;
using System;
using System.Net;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using SPT.Common.Http;
using WebSocketSharp;

Check failure on line 7 in Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocketSharp' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 7 in Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocketSharp' could not be found (are you missing a using directive or an assembly reference?)
using Fika.Core.Networking.Http.Models;

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

private WebSocket _webSocket;

Check failure on line 19 in Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'WebSocket' could not be found (are you missing a using directive or an assembly reference?)
private NetManager _netManager;

public FikaNatPunchServer(NetManager netManager)
{
// 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;

_netManager = netManager;

StunIpEndPoint = NatPunchUtils.CreateStunEndPoint(FikaPlugin.UDPPort.Value);
}

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

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

private void WebSocket_OnOpen(object sender, EventArgs e)
{
EFT.UI.ConsoleScreen.Log("Connected to FikaNatPunchService as server");
}

private void WebSocket_OnMessage(object sender, MessageEventArgs e)

Check failure on line 59 in Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'MessageEventArgs' could not be found (are you missing a using directive or an assembly reference?)
{
if (e == null)
return;

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

ProcessMessage(e.Data);
}

private void WebSocket_OnError(object sender, ErrorEventArgs e)

Check failure on line 70 in Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'ErrorEventArgs' could not be found (are you missing a using directive or an assembly reference?)
{
EFT.UI.ConsoleScreen.LogError($"Websocket error {e}");
_webSocket.Close();
}

private void ProcessMessage(string data)
{
EFT.UI.ConsoleScreen.Log($"data: {data}");
var msgObj = GetRequestObject(data);

var msgObjType = msgObj.GetType().Name;

EFT.UI.ConsoleScreen.Log($"msgObj: {msgObjType}");

switch (msgObjType)
{
case "GetHostStunRequest":
var getHostStunRequest = (GetHostStunRequest)msgObj;
EFT.UI.ConsoleScreen.Log($"received request GetHostStunRequest: {getHostStunRequest.StunIp}:{getHostStunRequest.StunPort}");

if (StunIpEndPoint != null)
{
IPEndPoint clientIpEndPoint = new IPEndPoint(IPAddress.Parse(getHostStunRequest.StunIp), getHostStunRequest.StunPort);

EFT.UI.ConsoleScreen.Log($"parsed GetHostStunRequest: {clientIpEndPoint.Address.ToString()}:{clientIpEndPoint.Port}");

NatPunchUtils.PunchNat(_netManager, clientIpEndPoint);

EFT.UI.ConsoleScreen.Log($"PUNCHED");

EFT.UI.ConsoleScreen.Log($"Sending GetHostStunResponse...:");
SendHostStun(getHostStunRequest.ClientId, StunIpEndPoint);
EFT.UI.ConsoleScreen.Log($"Sent GetHostStunResponse...:");
}

break;
}
}

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

private object GetRequestObject(string data)
{
// We're doing this literally once, so this is fine. Might need to
// refactor to use StreamReader to detect request type later.
JObject obj = JObject.Parse(data);

EFT.UI.ConsoleScreen.Log(data.ToString());

if (!obj.ContainsKey("requestType"))
{
throw new NullReferenceException("requestType");
}

var requestType = obj["requestType"].ToString();

switch (requestType)
{
case "GetHostStunRequest":
return JsonConvert.DeserializeObject<GetHostStunRequest>(data);
default:
throw new ArgumentException("requestType");
}
}

public void SendHostStun(string clientId, StunIpEndPoint stunIpEndPoint)
{
var getHostStunResponse = new GetHostStunResponse(clientId, stunIpEndPoint.Remote.Address.ToString(), stunIpEndPoint.Remote.Port);
Send(getHostStunResponse);
}
}
}
Loading

0 comments on commit 3f5850e

Please sign in to comment.