From 1a53472899ab85654c60177f7ae4e2c09cd5bce0 Mon Sep 17 00:00:00 2001
From: trippyone <137233897+trippyone@users.noreply.github.com>
Date: Fri, 17 May 2024 08:53:56 -0400
Subject: [PATCH] wip nat punch
---
Fika.Core/Fika.Core.csproj | 3 +
Fika.Core/Networking/FikaClient.cs | 69 +++-
Fika.Core/Networking/FikaServer.cs | 6 +
.../Networking/Models/GetHostStunRequest.cs | 34 ++
.../Networking/Models/GetHostStunResponse.cs | 25 ++
.../Networking/NatPunch/FikaNatPunchClient.cs | 111 ++++++
.../Networking/NatPunch/FikaNatPunchServer.cs | 153 ++++++++
.../Networking/NatPunch/NatPunchUtils.cs | 76 ++++
.../Attributes/STUNOtherAddressAttribute.cs | 10 +
.../Attributes/STUNResponseOriginAttribute.cs | 10 +
.../STUNXorMappedAddressAttribute.cs | 10 +
.../STUN/Attributes/StunAsciiTextAttribute.cs | 23 ++
.../Attributes/StunChangeRequestAttribute.cs | 51 +++
.../Attributes/StunChangedAddressAttribute.cs | 16 +
.../STUN/Attributes/StunEndPointAttribute.cs | 61 +++
.../STUN/Attributes/StunErrorCodeAttribute.cs | 24 ++
.../Attributes/StunMappedAddressAttribute.cs | 16 +
.../StunMessageIntegrityAttribute.cs | 21 ++
.../STUN/Attributes/StunPasswordAttribute.cs | 16 +
.../Attributes/StunReflectedFromAttribute.cs | 21 ++
.../StunResponseAddressAttribute.cs | 16 +
.../Attributes/StunSourceAddressAttribute.cs | 16 +
.../STUN/Attributes/StunUsernameAttribute.cs | 16 +
.../Networking/STUN/NATTypeDetectionRFC.cs | 8 +
Fika.Core/Networking/STUN/STUN.csproj | 21 ++
Fika.Core/Networking/STUN/STUNAttribute.cs | 84 +++++
.../Networking/STUN/STUNAttributeTypes.cs | 23 ++
Fika.Core/Networking/STUN/STUNBinaryReader.cs | 59 +++
Fika.Core/Networking/STUN/STUNBinaryWriter.cs | 57 +++
Fika.Core/Networking/STUN/STUNClient.cs | 89 +++++
Fika.Core/Networking/STUN/STUNErrorCode.cs | 21 ++
Fika.Core/Networking/STUN/STUNMessage.cs | 150 ++++++++
Fika.Core/Networking/STUN/STUNMessageTypes.cs | 18 +
Fika.Core/Networking/STUN/STUNNATType.cs | 43 +++
.../STUN/STUNNatFilteringBehavior.cs | 9 +
.../Networking/STUN/STUNNatMappingBehavior.cs | 9 +
Fika.Core/Networking/STUN/STUNQueryError.cs | 35 ++
Fika.Core/Networking/STUN/STUNQueryResult.cs | 59 +++
Fika.Core/Networking/STUN/STUNQueryType.cs | 25 ++
Fika.Core/Networking/STUN/STUNRfc3489.cs | 353 ++++++++++++++++++
Fika.Core/Networking/STUN/STUNRfc5780.cs | 251 +++++++++++++
Fika.Core/Networking/STUN/STUNUtils.cs | 80 ++++
Fika.sln | 25 ++
43 files changed, 2211 insertions(+), 12 deletions(-)
create mode 100644 Fika.Core/Networking/Models/GetHostStunRequest.cs
create mode 100644 Fika.Core/Networking/Models/GetHostStunResponse.cs
create mode 100644 Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
create mode 100644 Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs
create mode 100644 Fika.Core/Networking/NatPunch/NatPunchUtils.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/STUNOtherAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/STUNResponseOriginAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/STUNXorMappedAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunAsciiTextAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunChangeRequestAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunChangedAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunEndPointAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunErrorCodeAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunMappedAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunMessageIntegrityAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunPasswordAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunReflectedFromAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunResponseAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunSourceAddressAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/Attributes/StunUsernameAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/NATTypeDetectionRFC.cs
create mode 100644 Fika.Core/Networking/STUN/STUN.csproj
create mode 100644 Fika.Core/Networking/STUN/STUNAttribute.cs
create mode 100644 Fika.Core/Networking/STUN/STUNAttributeTypes.cs
create mode 100644 Fika.Core/Networking/STUN/STUNBinaryReader.cs
create mode 100644 Fika.Core/Networking/STUN/STUNBinaryWriter.cs
create mode 100644 Fika.Core/Networking/STUN/STUNClient.cs
create mode 100644 Fika.Core/Networking/STUN/STUNErrorCode.cs
create mode 100644 Fika.Core/Networking/STUN/STUNMessage.cs
create mode 100644 Fika.Core/Networking/STUN/STUNMessageTypes.cs
create mode 100644 Fika.Core/Networking/STUN/STUNNATType.cs
create mode 100644 Fika.Core/Networking/STUN/STUNNatFilteringBehavior.cs
create mode 100644 Fika.Core/Networking/STUN/STUNNatMappingBehavior.cs
create mode 100644 Fika.Core/Networking/STUN/STUNQueryError.cs
create mode 100644 Fika.Core/Networking/STUN/STUNQueryResult.cs
create mode 100644 Fika.Core/Networking/STUN/STUNQueryType.cs
create mode 100644 Fika.Core/Networking/STUN/STUNRfc3489.cs
create mode 100644 Fika.Core/Networking/STUN/STUNRfc5780.cs
create mode 100644 Fika.Core/Networking/STUN/STUNUtils.cs
create mode 100644 Fika.sln
diff --git a/Fika.Core/Fika.Core.csproj b/Fika.Core/Fika.Core.csproj
index 7d7b9d12..e3db2faf 100644
--- a/Fika.Core/Fika.Core.csproj
+++ b/Fika.Core/Fika.Core.csproj
@@ -117,6 +117,9 @@
..\References\UnityEngine.UI.dll
False
+
+ ..\References\websocket-sharp.dll
+
diff --git a/Fika.Core/Networking/FikaClient.cs b/Fika.Core/Networking/FikaClient.cs
index 4b67d96a..b83c9ced 100644
--- a/Fika.Core/Networking/FikaClient.cs
+++ b/Fika.Core/Networking/FikaClient.cs
@@ -1,5 +1,6 @@
// © 2024 Lacyway All Rights Reserved
+using Aki.Common.Http;
using Aki.Custom.Airdrops;
using BepInEx.Logging;
using Comfort.Common;
@@ -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;
@@ -27,6 +30,7 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
+using System.Threading.Tasks;
using UnityEngine;
namespace Fika.Core.Networking
@@ -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(OnPlayerStatePacketReceived);
packetProcessor.SubscribeNetSerializable(OnGameTimerPacketReceived);
@@ -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.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.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.Create(this);
FikaEventDispatcher.DispatchEvent(new FikaClientCreatedEvent(this));
diff --git a/Fika.Core/Networking/FikaServer.cs b/Fika.Core/Networking/FikaServer.cs
index 54c419cd..ee1d2db2 100644
--- a/Fika.Core/Networking/FikaServer.cs
+++ b/Fika.Core/Networking/FikaServer.cs
@@ -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;
@@ -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()
{
@@ -97,6 +99,10 @@ public async void Start()
EnableStatistics = true
};
+ var natPunchServer = new FikaNatPunchServer(_netServer);
+
+ natPunchServer.Listen();
+
if (FikaPlugin.UseUPnP.Value)
{
bool upnpFailed = false;
diff --git a/Fika.Core/Networking/Models/GetHostStunRequest.cs b/Fika.Core/Networking/Models/GetHostStunRequest.cs
new file mode 100644
index 00000000..688fd30d
--- /dev/null
+++ b/Fika.Core/Networking/Models/GetHostStunRequest.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/Models/GetHostStunResponse.cs b/Fika.Core/Networking/Models/GetHostStunResponse.cs
new file mode 100644
index 00000000..c07b644f
--- /dev/null
+++ b/Fika.Core/Networking/Models/GetHostStunResponse.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs b/Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
new file mode 100644
index 00000000..1f99606b
--- /dev/null
+++ b/Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
@@ -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 _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 o)
+ {
+ var data = JsonConvert.SerializeObject(o);
+ _webSocket.Send(data);
+ }
+
+ private async Task Receive()
+ {
+ var data = await _receiveTaskCompletion.Task;
+ var obj = JsonConvert.DeserializeObject(data);
+
+ return obj;
+ }
+
+ private async Task SendAndReceiveAsync(T1 o)
+ {
+ _receiveTaskCompletion = new TaskCompletionSource();
+
+ Send(o);
+
+ var data = await Receive();
+
+ return data;
+ }
+
+ public async Task GetHostStun(GetHostStunRequest getHostStunRequest)
+ {
+ return await SendAndReceiveAsync(getHostStunRequest);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs b/Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs
new file mode 100644
index 00000000..25d98219
--- /dev/null
+++ b/Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs
@@ -0,0 +1,153 @@
+using LiteNetLib;
+using LiteNetLib.Utils;
+using STUN;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net.Sockets;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using Aki.Common.Http;
+using WebSocketSharp;
+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;
+ 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)
+ {
+ //Logger.LogInfo($"Connected to NAT Helper");
+ EFT.UI.ConsoleScreen.Log("Connected to FikaNatPunchService as server");
+ }
+
+ private void WebSocket_OnMessage(object sender, MessageEventArgs e)
+ {
+ if (e == null)
+ return;
+
+ if (string.IsNullOrEmpty(e.Data))
+ return;
+
+ ProcessMessage(e.Data);
+ }
+
+ private void WebSocket_OnError(object sender, ErrorEventArgs e)
+ {
+ EFT.UI.ConsoleScreen.LogError($"Websocket error {e}");
+ _webSocket.Close();
+ }
+
+ private void ProcessMessage(string 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 o)
+ {
+ var data = JsonConvert.SerializeObject(o);
+ 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(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);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/NatPunch/NatPunchUtils.cs b/Fika.Core/Networking/NatPunch/NatPunchUtils.cs
new file mode 100644
index 00000000..8ec53813
--- /dev/null
+++ b/Fika.Core/Networking/NatPunch/NatPunchUtils.cs
@@ -0,0 +1,76 @@
+using LiteNetLib.Utils;
+using LiteNetLib;
+using STUN;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fika.Core.Networking.NatPunch
+{
+ public class StunIpEndPoint
+ {
+ public IPEndPoint Local { get; set; }
+ public IPEndPoint Remote { get; set; }
+
+ public StunIpEndPoint(IPEndPoint localIpEndPoint, IPEndPoint remoteIpEndPoint)
+ {
+ Local = localIpEndPoint;
+ Remote = remoteIpEndPoint;
+ }
+ }
+
+ public static class NatPunchUtils
+ {
+ public static StunIpEndPoint CreateStunEndPoint(int localPort = 0)
+ {
+ var stunUdpClient = new UdpClient();
+ var stunQueryResult = new STUNQueryResult();
+
+ try
+ {
+ if (localPort > 0)
+ stunUdpClient.Client.Bind(new IPEndPoint(IPAddress.Any, localPort));
+
+ IPAddress stunIp = Array.Find(Dns.GetHostEntry("stun.l.google.com").AddressList, a => a.AddressFamily == AddressFamily.InterNetwork);
+ int stunPort = 19302;
+
+ var stunQueryIpEndPoint = new IPEndPoint(stunIp, stunPort);
+
+ stunQueryResult = STUNClient.Query(stunUdpClient.Client, stunQueryIpEndPoint, STUNQueryType.ExactNAT, NATTypeDetectionRFC.Rfc3489);
+
+ if (stunQueryResult.PublicEndPoint != null)
+ {
+ var stunIpEndPointResult = new StunIpEndPoint((IPEndPoint)stunUdpClient.Client.LocalEndPoint, stunQueryResult.PublicEndPoint);
+ return stunIpEndPointResult;
+ }
+ }
+ catch (Exception ex)
+ {
+ //log exception
+ }
+ finally
+ {
+ stunUdpClient.Client.Close();
+ }
+
+ return null;
+ }
+
+ public static void PunchNat(NetManager netManager, IPEndPoint endPoint)
+ {
+ // bogus punch data
+ var resp = new NetDataWriter();
+ resp.Put(9999);
+
+ // send a couple of packets to punch a hole
+ for (int i = 0; i < 10; i++)
+ {
+ netManager.SendUnconnectedMessage(resp, endPoint);
+ }
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/STUNOtherAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/STUNOtherAddressAttribute.cs
new file mode 100644
index 00000000..0b2b48ed
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/STUNOtherAddressAttribute.cs
@@ -0,0 +1,10 @@
+namespace STUN.Attributes
+{
+ public class STUNOtherAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("OTHER-ADDRESS {0}", EndPoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/Attributes/STUNResponseOriginAttribute.cs b/Fika.Core/Networking/STUN/Attributes/STUNResponseOriginAttribute.cs
new file mode 100644
index 00000000..35975a9b
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/STUNResponseOriginAttribute.cs
@@ -0,0 +1,10 @@
+namespace STUN.Attributes
+{
+ public class STUNResponseOriginAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("RESPONSE-ORIGIN {0}", EndPoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/Attributes/STUNXorMappedAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/STUNXorMappedAddressAttribute.cs
new file mode 100644
index 00000000..b947ae63
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/STUNXorMappedAddressAttribute.cs
@@ -0,0 +1,10 @@
+namespace STUN.Attributes
+{
+ public class STUNXorMappedAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("XOR-MAPPED-ADDRESS {0}", EndPoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/Attributes/StunAsciiTextAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunAsciiTextAttribute.cs
new file mode 100644
index 00000000..596abf25
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunAsciiTextAttribute.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNAsciiTextAttribute : STUNAttribute
+ {
+ public string Text { get; set; }
+
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ Text = Encoding.ASCII.GetString(binary.ReadBytes(length));
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ binary.Write(Encoding.ASCII.GetBytes(Text));
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunChangeRequestAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunChangeRequestAttribute.cs
new file mode 100644
index 00000000..5bfba175
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunChangeRequestAttribute.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNChangeRequestAttribute : STUNAttribute
+ {
+ private static readonly byte[] Three0 = new byte[3];
+
+ public bool ChangeIP { get; set; }
+ public bool ChangePort { get; set; }
+
+ public STUNChangeRequestAttribute()
+ {
+
+ }
+
+ public STUNChangeRequestAttribute(bool ip, bool port)
+ {
+ ChangeIP = ip;
+ ChangePort = port;
+ }
+
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ binary.BaseStream.Position += 3;
+ var b = binary.ReadByte();
+ ChangeIP = ((b & 4) != 0);
+ ChangePort = ((b & 2) != 0);
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ binary.Write(Three0);
+
+ int i = 0;
+ if (ChangeIP) i |= 4;
+ if (ChangePort) i |= 2;
+
+ binary.Write((byte)i);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("CHANGE-REQUEST IP:{0} PORT:{1}", ChangeIP, ChangePort);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunChangedAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunChangedAddressAttribute.cs
new file mode 100644
index 00000000..d794f7d8
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunChangedAddressAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNChangedAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("CHANGE(D)-ADDRESS {0}", EndPoint);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunEndPointAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunEndPointAttribute.cs
new file mode 100644
index 00000000..fab21cae
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunEndPointAttribute.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN.Attributes
+{
+ public class STUNEndPointAttribute : STUNAttribute
+ {
+ public IPEndPoint EndPoint { get; set; }
+
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ binary.BaseStream.Position++;
+ var ipFamily = binary.ReadByte();
+ var port = binary.ReadUInt16();
+ IPAddress address;
+
+ if (ipFamily == 1)
+ {
+ address = new IPAddress(binary.ReadBytes(4));
+ }
+ else if (ipFamily == 2)
+ {
+ address = new IPAddress(binary.ReadBytes(16));
+ }
+ else
+ {
+ throw new Exception("Unsupported IP Family " + ipFamily.ToString());
+ }
+
+ EndPoint = new IPEndPoint(address, port);
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ binary.Write((byte)0);
+
+ if (EndPoint.AddressFamily == AddressFamily.InterNetwork)
+ {
+ binary.Write((byte)1);
+ }
+ else if (EndPoint.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ binary.Write((byte)2);
+ }
+ else
+ {
+ throw new Exception("Unsupported IP Family" + EndPoint.AddressFamily.ToString());
+ }
+
+ binary.Write((ushort)EndPoint.Port);
+
+ var addressBytes = EndPoint.Address.GetAddressBytes();
+ binary.Write(addressBytes);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunErrorCodeAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunErrorCodeAttribute.cs
new file mode 100644
index 00000000..c272c280
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunErrorCodeAttribute.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNErrorCodeAttribute : STUNAttribute
+ {
+ public STUNErrorCodes Error { get; set; }
+ public string Phrase { get; set; }
+
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunMappedAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunMappedAddressAttribute.cs
new file mode 100644
index 00000000..0379275c
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunMappedAddressAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNMappedAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("MAPPED-ADDRESS {0}", EndPoint);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunMessageIntegrityAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunMessageIntegrityAttribute.cs
new file mode 100644
index 00000000..52b27e36
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunMessageIntegrityAttribute.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNMessageIntegrityAttribute : STUNAttribute
+ {
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunPasswordAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunPasswordAttribute.cs
new file mode 100644
index 00000000..8a51fa79
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunPasswordAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNPasswordAttribute : STUNAsciiTextAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("PASSWORD \"{0}\"", Text);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunReflectedFromAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunReflectedFromAttribute.cs
new file mode 100644
index 00000000..576cf7f4
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunReflectedFromAttribute.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNReflectedFromAttribute : STUNAttribute
+ {
+ public override void Parse(STUNBinaryReader binary, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void WriteBody(STUNBinaryWriter binary)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunResponseAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunResponseAddressAttribute.cs
new file mode 100644
index 00000000..d291fb64
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunResponseAddressAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNResponseAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("RESPONSE-ADDRESS {0}", EndPoint);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunSourceAddressAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunSourceAddressAttribute.cs
new file mode 100644
index 00000000..46b0ccfb
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunSourceAddressAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNSourceAddressAttribute : STUNEndPointAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("SOURCE-ADDRESS {0}", EndPoint);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/Attributes/StunUsernameAttribute.cs b/Fika.Core/Networking/STUN/Attributes/StunUsernameAttribute.cs
new file mode 100644
index 00000000..e588f481
--- /dev/null
+++ b/Fika.Core/Networking/STUN/Attributes/StunUsernameAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN.Attributes
+{
+ public class STUNUsernameAttribute : STUNAsciiTextAttribute
+ {
+ public override string ToString()
+ {
+ return string.Format("USERNAME \"{0}\"", Text);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/NATTypeDetectionRFC.cs b/Fika.Core/Networking/STUN/NATTypeDetectionRFC.cs
new file mode 100644
index 00000000..e09be666
--- /dev/null
+++ b/Fika.Core/Networking/STUN/NATTypeDetectionRFC.cs
@@ -0,0 +1,8 @@
+namespace STUN
+{
+ public enum NATTypeDetectionRFC
+ {
+ Rfc3489,
+ Rfc5780
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUN.csproj b/Fika.Core/Networking/STUN/STUN.csproj
new file mode 100644
index 00000000..17c788c3
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUN.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netstandard1.3;netstandard2.0;net45
+ 0.5.0
+ Moien007
+
+
+ https://github.com/moien007/stun
+ https://github.com/moien007/stun
+ - .NET Standard Support
+ A tiny Session Traversal Utilities for NAT (STUN) client library based on deprecated RFC3489.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNAttribute.cs b/Fika.Core/Networking/STUN/STUNAttribute.cs
new file mode 100644
index 00000000..1b018a2a
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNAttribute.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using STUN.Attributes;
+
+namespace STUN
+{
+ public abstract class STUNAttribute
+ {
+ static Dictionary _knownAttributes;
+ static Dictionary _knownTypes;
+
+ static STUNAttribute()
+ {
+ _knownAttributes = new Dictionary();
+ _knownTypes = new Dictionary();
+
+ AddAttribute(1);
+ AddAttribute(2);
+ AddAttribute(3);
+ AddAttribute(4);
+ AddAttribute(5);
+ AddAttribute(6);
+ AddAttribute(7);
+ AddAttribute(8);
+ AddAttribute(9);
+ //AddAttribute<>(10);
+ AddAttribute(11);
+ AddAttribute(0x0020);
+ AddAttribute(0x802B);
+ AddAttribute(0x802C);
+ }
+
+ public abstract void Parse(STUNBinaryReader binary, int length);
+
+ public virtual void Write(STUNBinaryWriter binary)
+ {
+ binary.Write((ushort)GetAttribute(GetType()));
+ var lengthPos = binary.BaseStream.Position;
+ binary.Write((ushort)0);
+ var bodyPos = binary.BaseStream.Position;
+ WriteBody(binary);
+ var length = binary.BaseStream.Position - bodyPos;
+ var endPos = binary.BaseStream.Position;
+ binary.BaseStream.Position = lengthPos;
+ binary.Write((ushort)length);
+ binary.BaseStream.Position = endPos;
+ }
+
+ public abstract void WriteBody(STUNBinaryWriter binary);
+
+ public static void AddAttribute(int type) where T : STUNAttribute
+ {
+ _knownAttributes.Add(type, typeof(T));
+ _knownTypes.Add(typeof(T), type);
+ }
+
+ public static Type GetAttribute(int attribute)
+ {
+ Type type;
+
+ if (_knownAttributes.TryGetValue(attribute, out type))
+ {
+ return type;
+ }
+
+ return null;
+ }
+
+ public static int GetAttribute(Type type)
+ {
+ int attr;
+
+ if (_knownTypes.TryGetValue(type, out attr))
+ {
+ return attr;
+ }
+
+ return -1;
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNAttributeTypes.cs b/Fika.Core/Networking/STUN/STUNAttributeTypes.cs
new file mode 100644
index 00000000..640a9348
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNAttributeTypes.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN
+{
+ public enum StunAttributeTypes : ushort
+ {
+ MappedAddress,
+ ResponseAddress,
+ ChangeRequest,
+ SourceAddress,
+ ChangedAddress,
+ Username,
+ Password,
+ MessageIntegrity,
+ ErrorCode,
+ UnknownAttributes,
+ ReflectedFrom,
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNBinaryReader.cs b/Fika.Core/Networking/STUN/STUNBinaryReader.cs
new file mode 100644
index 00000000..b3bd7fa1
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNBinaryReader.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+
+namespace STUN
+{
+ public class STUNBinaryReader : BinaryReader
+ {
+ public STUNBinaryReader(Stream stream) : base(stream)
+ {
+
+ }
+
+ public override short ReadInt16()
+ {
+ return BitConverter.ToInt16(ReadNetworkBytes(sizeof(short)), 0);
+ }
+
+ public override ushort ReadUInt16()
+ {
+ return BitConverter.ToUInt16(ReadNetworkBytes(sizeof(ushort)), 0);
+ }
+
+ public override int ReadInt32()
+ {
+ return BitConverter.ToInt32(ReadNetworkBytes(sizeof(int)), 0);
+ }
+
+ public override uint ReadUInt32()
+ {
+ return BitConverter.ToUInt32(ReadNetworkBytes(sizeof(uint)), 0);
+ }
+
+ public override long ReadInt64()
+ {
+ return BitConverter.ToInt64(ReadNetworkBytes(sizeof(long)), 0);
+ }
+
+ public override ulong ReadUInt64()
+ {
+ return BitConverter.ToUInt64(ReadNetworkBytes(sizeof(ulong)), 0);
+ }
+
+ public byte[] ReadNetworkBytes(int count)
+ {
+ var bytes = base.ReadBytes(count);
+
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(bytes);
+ }
+
+ return bytes;
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNBinaryWriter.cs b/Fika.Core/Networking/STUN/STUNBinaryWriter.cs
new file mode 100644
index 00000000..2c68364a
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNBinaryWriter.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+
+namespace STUN
+{
+ public class STUNBinaryWriter : BinaryWriter
+ {
+ public STUNBinaryWriter(Stream stream) : base(stream)
+ {
+
+ }
+
+ public override void Write(short value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ public override void Write(ushort value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ public override void Write(int value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ public override void Write(uint value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ public override void Write(long value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ public override void Write(ulong value)
+ {
+ WriteNetworkBytes(BitConverter.GetBytes(value));
+ }
+
+ private void WriteNetworkBytes(byte[] buffer)
+ {
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(buffer);
+ }
+
+ base.Write(buffer);
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNClient.cs b/Fika.Core/Networking/STUN/STUNClient.cs
new file mode 100644
index 00000000..280a2cce
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNClient.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Collections;
+using STUN.Attributes;
+using System.IO;
+
+namespace STUN
+{
+ ///
+ /// Implements a RFC3489 STUN client.
+ ///
+ public static class STUNClient
+ {
+ ///
+ /// Period of time in miliseconds to wait for server response.
+ ///
+ public static int ReceiveTimeout = 5000;
+
+ /// Server address
+ /// Query type
+ ///
+ /// Set to true if created socket should closed after the query
+ /// else will leave open and can be used.
+ ///
+ public static Task QueryAsync(IPEndPoint server, STUNQueryType queryType, bool closeSocket)
+ {
+ return Task.Run(() => Query(server, queryType, closeSocket));
+ }
+
+ /// A UDP that will use for query. You can also use
+ /// Server address
+ /// Query type
+ public static Task QueryAsync(Socket socket, IPEndPoint server, STUNQueryType queryType,
+ NATTypeDetectionRFC natTypeDetectionRfc)
+ {
+ return Task.Run(() => Query(socket, server, queryType, natTypeDetectionRfc));
+ }
+
+ /// Server address
+ /// Query type
+ ///
+ /// Set to true if created socket should closed after the query
+ /// else will leave open and can be used.
+ ///
+ public static STUNQueryResult Query(IPEndPoint server, STUNQueryType queryType, bool closeSocket,
+ NATTypeDetectionRFC natTypeDetectionRfc = NATTypeDetectionRFC.Rfc3489)
+ {
+ Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ IPEndPoint bindEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ socket.Bind(bindEndPoint);
+
+ var result = Query(socket, server, queryType, natTypeDetectionRfc);
+
+ if (closeSocket)
+ {
+ socket.Dispose();
+ result.Socket = null;
+ }
+
+ return result;
+ }
+
+ /// A UDP that will use for query. You can also use
+ /// Server address
+ /// Query type
+ /// Rfc algorithm type
+ public static STUNQueryResult Query(Socket socket, IPEndPoint server, STUNQueryType queryType,
+ NATTypeDetectionRFC natTypeDetectionRfc)
+ {
+ if (natTypeDetectionRfc == NATTypeDetectionRFC.Rfc3489)
+ {
+ return STUNRfc3489.Query(socket, server, queryType, ReceiveTimeout);
+ }
+
+ if (natTypeDetectionRfc == NATTypeDetectionRFC.Rfc5780)
+ {
+ return STUNRfc5780.Query(socket, server, queryType, ReceiveTimeout);
+ }
+
+ return new STUNQueryResult();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNErrorCode.cs b/Fika.Core/Networking/STUN/STUNErrorCode.cs
new file mode 100644
index 00000000..a32d15a3
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNErrorCode.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN
+{
+ public enum STUNErrorCodes : ushort
+ {
+ BadRequest = 400,
+ Unauthorized = 401,
+ UnknownAttribute = 420,
+ StaleCredentials = 430,
+ IntegrityCheckFailure = 431,
+ MissingUsername = 432,
+ UseTLS = 433,
+ ServerError = 500,
+ GloablFailure = 600,
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNMessage.cs b/Fika.Core/Networking/STUN/STUNMessage.cs
new file mode 100644
index 00000000..5e370faa
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNMessage.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+using System.IO;
+
+namespace STUN
+{
+ public class STUNMessage
+ {
+ public STUNMessageTypes MessageType { get; set; }
+ public byte[] TransactionID { get; set; }
+ public List Attributes { get; set; }
+
+ public STUNMessage(STUNMessageTypes messageType, byte[] transactionID)
+ {
+ MessageType = messageType;
+ TransactionID = transactionID;
+ Attributes = new List();
+ }
+
+ public bool TryParse(byte[] buffer)
+ {
+ try
+ {
+ Parse(buffer);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public void Parse(byte[] buffer)
+ {
+ Parse(buffer, 0, buffer.Length);
+ }
+
+ public void Parse(byte[] buffer, int index, int count)
+ {
+ using (MemoryStream memory = new MemoryStream(buffer, index, count))
+ using (STUNBinaryReader binary = new STUNBinaryReader(memory))
+ {
+ Parse(binary);
+ }
+ }
+
+ public void Parse(STUNBinaryReader binary)
+ {
+ MessageType = (STUNMessageTypes)binary.ReadUInt16();
+ int messageLength = binary.ReadUInt16();
+ TransactionID = binary.ReadBytes(16);
+
+ Attributes = new List();
+
+ int attrType;
+ int attrLength;
+ int paddingLength;
+
+ while ((binary.BaseStream.Position - 20) < messageLength)
+ {
+ attrType = binary.ReadUInt16();
+ attrLength = binary.ReadUInt16();
+
+ if (attrLength % 4 == 0)
+ {
+ paddingLength = 0;
+ }
+ else
+ {
+ paddingLength = 4 - attrLength % 4;
+ }
+
+ var type = STUNAttribute.GetAttribute(attrType);
+
+ if (type != null)
+ {
+ var attr = Activator.CreateInstance(type) as STUNAttribute;
+ attr.Parse(binary, attrLength);
+ Attributes.Add(attr);
+ }
+ else
+ {
+ binary.BaseStream.Position += attrLength;
+ }
+
+ binary.BaseStream.Position += paddingLength;
+ }
+ }
+
+ public void Write(Stream stream)
+ {
+ using (STUNBinaryWriter binary = new STUNBinaryWriter(stream))
+ {
+ Write(binary);
+ }
+ }
+
+ public void Write(STUNBinaryWriter binary)
+ {
+ binary.Write((ushort)MessageType);
+ binary.Write((ushort)0);
+ binary.Write(TransactionID);
+
+ long length = 0;
+
+ foreach (var attribute in Attributes)
+ {
+ var startPos = binary.BaseStream.Position;
+ attribute.Write(binary);
+ length += binary.BaseStream.Position - startPos;
+ }
+
+ binary.BaseStream.Position = 2;
+ binary.Write((ushort)length);
+ }
+
+ public byte[] GetBytes()
+ {
+ using (MemoryStream memory = new MemoryStream())
+ {
+ Write(memory);
+ return memory.ToArray();
+ }
+ }
+
+ public static byte[] GenerateTransactionIDNewStun()
+ {
+ Guid guid = Guid.NewGuid();
+ var guidArray = guid.ToByteArray();
+ // Add magic_cookie as a part of transaction id
+ byte[] guidByte = new byte[16];
+ guidByte[0] = 0x21;
+ guidByte[1] = 0x12;
+ guidByte[2] = 0xA4;
+ guidByte[3] = 0x42;
+ Buffer.BlockCopy(guidArray, 0,guidByte, 4, 12);
+ return guidByte;
+ }
+
+ public static byte[] GenerateTransactionID()
+ {
+ Guid guid = Guid.NewGuid();
+ return guid.ToByteArray();
+ }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNMessageTypes.cs b/Fika.Core/Networking/STUN/STUNMessageTypes.cs
new file mode 100644
index 00000000..3f8f0460
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNMessageTypes.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace STUN
+{
+ public enum STUNMessageTypes : ushort
+ {
+ BindingRequest = 0x0001,
+ BindingResponse = 0x0101,
+ BindingErrorResponse = 0x0111,
+ SharedSecretRequest = 0x0002,
+ SharedSecretResponse = 0x0102,
+ SharedSecretErrorResponse = 0x0112
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNNATType.cs b/Fika.Core/Networking/STUN/STUNNATType.cs
new file mode 100644
index 00000000..62603925
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNNATType.cs
@@ -0,0 +1,43 @@
+namespace STUN
+{
+ public enum STUNNATType
+ {
+ ///
+ /// Unspecified NAT Type
+ ///
+ Unspecified,
+
+ ///
+ /// Open internet. for example Virtual Private Servers.
+ ///
+ OpenInternet,
+
+ ///
+ /// Full Cone NAT. Good to go.
+ ///
+ FullCone,
+
+ ///
+ /// Restricted Cone NAT.
+ /// It mean's client can only receive data only IP addresses that it sent a data before.
+ ///
+ Restricted,
+
+ ///
+ /// Port-Restricted Cone NAT.
+ /// Same as but port is included too.
+ ///
+ PortRestricted,
+
+ ///
+ /// Symmetric NAT.
+ /// It's means the client pick's a different port for every connection it made.
+ ///
+ Symmetric,
+
+ ///
+ /// Same as but only received data from addresses that it sent a data before.
+ ///
+ SymmetricUDPFirewall,
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNNatFilteringBehavior.cs b/Fika.Core/Networking/STUN/STUNNatFilteringBehavior.cs
new file mode 100644
index 00000000..e67d2ec1
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNNatFilteringBehavior.cs
@@ -0,0 +1,9 @@
+namespace STUN
+{
+ public enum STUNNatFilteringBehavior
+ {
+ EndpointIndependentFiltering,
+ AddressDependFiltering,
+ AddressAndPortDependFiltering
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNNatMappingBehavior.cs b/Fika.Core/Networking/STUN/STUNNatMappingBehavior.cs
new file mode 100644
index 00000000..861393ff
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNNatMappingBehavior.cs
@@ -0,0 +1,9 @@
+namespace STUN
+{
+ public enum STUNNatMappingBehavior
+ {
+ EndpointIndependentMapping,
+ AddressDependMapping,
+ AddressAndPortDependMapping
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNQueryError.cs b/Fika.Core/Networking/STUN/STUNQueryError.cs
new file mode 100644
index 00000000..77db035a
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNQueryError.cs
@@ -0,0 +1,35 @@
+namespace STUN
+{
+ public enum STUNQueryError
+ {
+ ///
+ /// Indicates querying was successful.
+ ///
+ Success,
+
+ ///
+ /// Indicates the server responsed with error.
+ /// In this case you have check and in query result.
+ ///
+ ServerError,
+
+ ///
+ /// Indicates the server responsed a bad\wrong\.. message. This error will returned in many cases.
+ ///
+ BadResponse,
+
+ ///
+ /// Indicates the server responsed a message that contains a different transcation ID
+ ///
+ BadTransactionID,
+
+ ///
+ /// Indicates the server didn't response a request within a time interval
+ ///
+ Timedout,
+ ///
+ /// Indicates the server did not support nat detection
+ ///
+ NotSupported
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNQueryResult.cs b/Fika.Core/Networking/STUN/STUNQueryResult.cs
new file mode 100644
index 00000000..8d75c5d2
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNQueryResult.cs
@@ -0,0 +1,59 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN
+{
+ ///
+ /// STUN client query result
+ ///
+ public class STUNQueryResult
+ {
+ ///
+ /// The query type that passed to method
+ ///
+ public STUNQueryType QueryType { get; set; }
+
+ ///
+ /// The query result error
+ ///
+ public STUNQueryError QueryError { get; set; }
+
+ ///
+ /// Contains the server error code that receive from server.
+ /// Presents if set too
+ ///
+ public STUNErrorCodes ServerError { get; set; }
+
+ ///
+ /// Contains the server error phrase that receive from server.
+ /// Presents if set to
+ ///
+ public string ServerErrorPhrase { get; set; }
+
+ ///
+ /// The socket that used to communicate with STUN server
+ ///
+ public Socket Socket { get; set; }
+
+ ///
+ /// Contains the server address
+ ///
+ public IPEndPoint ServerEndPoint { get; set; }
+
+ ///
+ /// Contains the queried NAT Type.
+ /// Presents if set to
+ ///
+ public STUNNATType NATType { get; set; }
+
+ ///
+ /// Contains the public endpoint that queried from server.
+ ///
+ public IPEndPoint PublicEndPoint { get; set; }
+
+ ///
+ /// Contains client's socket local endpoiont.
+ ///
+ public IPEndPoint LocalEndPoint { get; set; }
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNQueryType.cs b/Fika.Core/Networking/STUN/STUNQueryType.cs
new file mode 100644
index 00000000..a2d00362
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNQueryType.cs
@@ -0,0 +1,25 @@
+namespace STUN
+{
+ public enum STUNQueryType
+ {
+ ///
+ /// Indicates to client to just query IP address and return
+ ///
+ PublicIP,
+
+ ///
+ /// Indicates to client to stop the querying if NAT type is strict.
+ /// If the NAT is strict the NAT type will set too
+ /// Else the NAT type will set to one of following types
+ ///
+ ///
+ ///
+ ///
+ OpenNAT,
+
+ ///
+ /// Indicates to client to find the exact NAT type.
+ ///
+ ExactNAT,
+ }
+}
diff --git a/Fika.Core/Networking/STUN/STUNRfc3489.cs b/Fika.Core/Networking/STUN/STUNRfc3489.cs
new file mode 100644
index 00000000..7963f41d
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNRfc3489.cs
@@ -0,0 +1,353 @@
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using STUN.Attributes;
+
+namespace STUN.Attributes
+{
+ public class STUNRfc3489
+ {
+ public static STUNQueryResult Query(Socket socket, IPEndPoint server, STUNQueryType queryType, int ReceiveTimeout)
+ {
+ var result = new STUNQueryResult(); // the query result
+ result.Socket = socket;
+ result.ServerEndPoint = server;
+ result.NATType = STUNNATType.Unspecified;
+ result.QueryType = queryType;
+ var transID = STUNMessage.GenerateTransactionID(); // get a random trans id
+ var message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request
+ // send the request to server
+ socket.SendTo(message.GetBytes(), server);
+ // we set result local endpoint after calling SendTo,
+ // because if socket is unbound, the system will bind it after SendTo call.
+ result.LocalEndPoint = socket.LocalEndPoint as IPEndPoint;
+
+ // wait for response
+ var responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ // didn't receive anything
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.Timedout;
+ return result;
+ }
+
+ // try to parse message
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ // check trans id
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ // finds error-code attribute, used in case of binding error
+ var errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute)
+ as STUNErrorCodeAttribute;
+
+ // if server responsed our request with error
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ if (errorAttr == null)
+ {
+ // we count a binding error without error-code attribute as bad response (no?)
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ // return if receive something else binding response
+ if (message.MessageType != STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ // not used for now.
+ var changedAddr =
+ message.Attributes.FirstOrDefault(p => p is STUNChangedAddressAttribute) as
+ STUNChangedAddressAttribute;
+
+ // find mapped address attribue in message
+ // this attribue should present
+ var mappedAddressAttr = message.Attributes.FirstOrDefault(p => p is STUNMappedAddressAttribute)
+ as STUNMappedAddressAttribute;
+ if (mappedAddressAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+ else
+ {
+ result.PublicEndPoint = mappedAddressAttr.EndPoint;
+ }
+
+ // stop querying and return the public ip if user just wanted to know public ip
+ if (queryType == STUNQueryType.PublicIP)
+ {
+ result.QueryError = STUNQueryError.Success;
+ return result;
+ }
+
+ // if our local ip and port equals to mapped address
+ if (mappedAddressAttr.EndPoint.Equals(socket.LocalEndPoint))
+ {
+ // we send to a binding request again but with change-request attribute
+ // that tells to server to response us with different endpoint
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ message.Attributes.Add(new STUNChangeRequestAttribute(true, true));
+
+ socket.SendTo(message.GetBytes(), server);
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ // if we didnt receive a response
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.SymmetricUDPFirewall;
+ return result;
+ }
+
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ if (message.MessageType == STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.OpenInternet;
+ return result;
+ }
+
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ errorAttr =
+ message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as
+ STUNErrorCodeAttribute;
+
+ if (errorAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ // the message type is wrong
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ message.Attributes.Add(new STUNChangeRequestAttribute(true, true));
+
+ var testmsg = new STUNMessage(STUNMessageTypes.BindingRequest, null);
+ testmsg.Parse(message.GetBytes());
+
+ socket.SendTo(message.GetBytes(), server);
+
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (responseBuffer != null)
+ {
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ if (message.MessageType == STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.FullCone;
+ return result;
+ }
+
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ errorAttr =
+ message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as
+ STUNErrorCodeAttribute;
+
+ if (errorAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ // if user only wanted to know the NAT is open or not
+ if (queryType == STUNQueryType.OpenNAT)
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.Unspecified;
+ return result;
+ }
+
+ // we now need changed-address attribute
+ // because we send our request to this address instead of the first server address
+ if (changedAddr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+ else
+ {
+ server = changedAddr.EndPoint;
+ }
+
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ socket.SendTo(message.GetBytes(), server);
+
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.Timedout;
+ return result;
+ }
+
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ errorAttr =
+ message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute;
+
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ if (errorAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ if (message.MessageType != STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ mappedAddressAttr = message.Attributes.FirstOrDefault(p => p is STUNMappedAddressAttribute)
+ as STUNMappedAddressAttribute;
+
+ if (mappedAddressAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ if (!mappedAddressAttr.EndPoint.Equals(result.PublicEndPoint))
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.Symmetric;
+ result.PublicEndPoint = null;
+ return result;
+ }
+
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ message.Attributes.Add(new STUNChangeRequestAttribute(false, true)); // change port but not ip
+
+ socket.SendTo(message.GetBytes(), server);
+
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.PortRestricted;
+ return result;
+ }
+
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.Timedout;
+ return result;
+ }
+
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute)
+ as STUNErrorCodeAttribute;
+
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ if (errorAttr == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ if (message.MessageType != STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.Success;
+ result.NATType = STUNNATType.Restricted;
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNRfc5780.cs b/Fika.Core/Networking/STUN/STUNRfc5780.cs
new file mode 100644
index 00000000..ff6d112b
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNRfc5780.cs
@@ -0,0 +1,251 @@
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using STUN.Attributes;
+
+namespace STUN
+{
+ public class STUNRfc5780
+ {
+ public static STUNQueryResult Query(Socket socket, IPEndPoint server, STUNQueryType queryType, int ReceiveTimeout)
+ {
+ STUNNatMappingBehavior mappingBehavior = STUNNatMappingBehavior.EndpointIndependentMapping;
+ STUNNatFilteringBehavior filteringBehavior = STUNNatFilteringBehavior.EndpointIndependentFiltering;
+ var result = new STUNQueryResult(); // the query result
+ result.Socket = socket;
+ result.ServerEndPoint = server;
+ result.NATType = STUNNATType.Unspecified;
+ result.QueryType = queryType;
+
+ var transID = STUNMessage.GenerateTransactionIDNewStun(); // get a random trans id
+ var message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request
+ // send the request to server
+ socket.SendTo(message.GetBytes(), server);
+ // we set result local endpoint after calling SendTo,
+ // because if socket is unbound, the system will bind it after SendTo call.
+ result.LocalEndPoint = socket.LocalEndPoint as IPEndPoint;
+
+ // wait for response
+ var responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ // didn't receive anything
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.Timedout;
+ return result;
+ }
+
+ // try to parse message
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ // check trans id
+ if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID))
+ {
+ result.QueryError = STUNQueryError.BadTransactionID;
+ return result;
+ }
+
+ // finds error-code attribute, used in case of binding error
+ var errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute)
+ as STUNErrorCodeAttribute;
+
+ // if server responsed our request with error
+ if (message.MessageType == STUNMessageTypes.BindingErrorResponse)
+ {
+ if (errorAttr == null)
+ {
+ // we count a binding error without error-code attribute as bad response (no?)
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.QueryError = STUNQueryError.ServerError;
+ result.ServerError = errorAttr.Error;
+ result.ServerErrorPhrase = errorAttr.Phrase;
+ return result;
+ }
+
+ // return if receive something else binding response
+ if (message.MessageType != STUNMessageTypes.BindingResponse)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ var xorAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute)
+ as STUNXorMappedAddressAttribute;
+
+ if (xorAddressAttribute == null)
+ {
+ result.QueryError = STUNQueryError.BadResponse;
+ return result;
+ }
+
+ result.PublicEndPoint = xorAddressAttribute.EndPoint;
+
+ // stop querying and return the public ip if user just wanted to know public ip
+ if (queryType == STUNQueryType.PublicIP)
+ {
+ result.QueryError = STUNQueryError.Success;
+ return result;
+ }
+
+
+ if (xorAddressAttribute.EndPoint.Equals(socket.LocalEndPoint))
+ {
+ result.NATType = STUNNATType.OpenInternet;
+ }
+
+ var otherAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNOtherAddressAttribute)
+ as STUNOtherAddressAttribute;
+
+ var changedAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNChangedAddressAttribute)
+ as STUNChangedAddressAttribute;
+ // Check is next test should be performed and is support rfc5780 test
+ if (otherAddressAttribute == null)
+ {
+ if (changedAddressAttribute == null)
+ {
+ result.QueryError = STUNQueryError.NotSupported;
+ return result;
+ }
+
+ otherAddressAttribute = new STUNOtherAddressAttribute();
+ otherAddressAttribute.EndPoint = changedAddressAttribute.EndPoint;
+ }
+
+
+ // Make test 2 - bind different ip address but primary port
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request
+ IPEndPoint secondaryServer = new IPEndPoint(otherAddressAttribute.EndPoint.Address, server.Port);
+ socket.SendTo(message.GetBytes(), secondaryServer);
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ // Secondary server presented but is down
+ if (responseBuffer == null)
+ {
+ result.QueryError = STUNQueryError.NotSupported;
+ return result;
+ }
+
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.NotSupported;
+ return result;
+ }
+
+ var xorAddressAttribute2 = message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute)
+ as STUNXorMappedAddressAttribute;
+
+ if (xorAddressAttribute2 != null)
+ {
+ if (xorAddressAttribute.EndPoint.Equals(xorAddressAttribute2.EndPoint))
+ {
+ mappingBehavior = STUNNatMappingBehavior.EndpointIndependentMapping;
+ }
+
+ // Make test 3
+ else
+ {
+ IPEndPoint secondaryServerPort = new IPEndPoint(otherAddressAttribute.EndPoint.Address,
+ otherAddressAttribute.EndPoint.Port);
+
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request
+ socket.SendTo(message.GetBytes(), secondaryServerPort);
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (!message.TryParse(responseBuffer))
+ {
+ result.QueryError = STUNQueryError.NotSupported;
+ return result;
+ }
+
+ var xorAddressAttribute3 =
+ message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute)
+ as STUNXorMappedAddressAttribute;
+
+ if (xorAddressAttribute3 != null)
+ {
+ if (xorAddressAttribute3.EndPoint.Equals(xorAddressAttribute2.EndPoint))
+ {
+ mappingBehavior = STUNNatMappingBehavior.AddressDependMapping;
+ }
+
+ else
+ {
+ mappingBehavior = STUNNatMappingBehavior.AddressAndPortDependMapping;
+ }
+ }
+ }
+ }
+
+ // Now make a filtering behavioral test
+ // We already made a test 1 for mapping behavioral
+ // so jump to test 2
+
+ // Send message to primary server.
+ // Try receive from another server and port
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ message.Attributes.Add(new STUNChangeRequestAttribute(true, true));
+
+ socket.SendTo(message.GetBytes(), server);
+
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (responseBuffer != null)
+ {
+ filteringBehavior = STUNNatFilteringBehavior.EndpointIndependentFiltering;
+ }
+
+ // Test 3 - send request to original server with change port attribute
+ else
+ {
+ message = new STUNMessage(STUNMessageTypes.BindingRequest, transID);
+ message.Attributes.Add(new STUNChangeRequestAttribute(false, true));
+
+ socket.SendTo(message.GetBytes(), server);
+
+ responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout);
+
+ if (responseBuffer != null)
+ {
+ filteringBehavior = STUNNatFilteringBehavior.AddressDependFiltering;
+ }
+
+ else
+ {
+ filteringBehavior = STUNNatFilteringBehavior.AddressAndPortDependFiltering;
+ }
+ }
+
+ if (filteringBehavior == STUNNatFilteringBehavior.AddressAndPortDependFiltering &&
+ mappingBehavior == STUNNatMappingBehavior.AddressAndPortDependMapping)
+ {
+ result.NATType = STUNNATType.Symmetric;
+ }
+
+ if (filteringBehavior == STUNNatFilteringBehavior.EndpointIndependentFiltering &&
+ mappingBehavior == STUNNatMappingBehavior.EndpointIndependentMapping)
+ {
+ result.NATType = STUNNATType.FullCone;
+ }
+
+ if (filteringBehavior == STUNNatFilteringBehavior.EndpointIndependentFiltering &&
+ mappingBehavior == STUNNatMappingBehavior.AddressDependMapping)
+ {
+ result.NATType = STUNNATType.Restricted;
+ }
+
+ if (result.NATType == STUNNATType.Unspecified)
+ {
+ result.NATType = STUNNATType.PortRestricted;
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.Core/Networking/STUN/STUNUtils.cs b/Fika.Core/Networking/STUN/STUNUtils.cs
new file mode 100644
index 00000000..2106124c
--- /dev/null
+++ b/Fika.Core/Networking/STUN/STUNUtils.cs
@@ -0,0 +1,80 @@
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN.Attributes
+{
+ public class STUNUtils
+ {
+ public static byte[] Receive(Socket socket, int timeout)
+ {
+ if (!socket.Poll(timeout * 1000, SelectMode.SelectRead))
+ {
+ return null;
+ }
+
+ EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
+
+ byte[] buffer = new byte[1024 * 2];
+ int bytesRead = 0;
+
+ bytesRead = socket.ReceiveFrom(buffer, ref endPoint);
+
+ return buffer.Take(bytesRead).ToArray();
+ }
+
+ public static bool TryParseHostAndPort(string hostAndPort, out IPEndPoint endPoint)
+ {
+ if (string.IsNullOrWhiteSpace(hostAndPort))
+ {
+ endPoint = null;
+ return false;
+ }
+
+ var split = hostAndPort.Split(':');
+
+ if (split.Length != 2)
+ {
+ endPoint = null;
+ return false;
+ }
+
+ if (!ushort.TryParse(split[1], out ushort port))
+ {
+ endPoint = null;
+ return false;
+ }
+
+ if (!IPAddress.TryParse(split[0], out IPAddress address))
+ {
+ try
+ {
+#if NETSTANDARD1_3
+ address = Dns.GetHostEntryAsync(split[0]).GetAwaiter().GetResult().AddressList.First();
+#else
+ address = Dns.GetHostEntry(split[0]).AddressList.First();
+#endif
+ }
+ catch
+ {
+ endPoint = null;
+ return false;
+ }
+ }
+
+ endPoint = new IPEndPoint(address, port);
+ return true;
+ }
+
+ public static bool ByteArrayCompare(byte[] b1, byte[] b2)
+ {
+ if (b1 == b2)
+ return true;
+
+ if (b1.Length != b2.Length)
+ return false;
+
+ return b1.SequenceEqual(b2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fika.sln b/Fika.sln
new file mode 100644
index 00000000..7041dc4f
--- /dev/null
+++ b/Fika.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33205.214
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fika.Core", "Fika.Core\Fika.Core.csproj", "{79F0E889-A195-42B4-8656-4F35685BBB80}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ GoldMaster|Any CPU = GoldMaster|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.GoldMaster|Any CPU.ActiveCfg = GoldMaster|Any CPU
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.GoldMaster|Any CPU.Build.0 = GoldMaster|Any CPU
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {79F0E889-A195-42B4-8656-4F35685BBB80}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal