From 3f5850edd9bfddf3f6eb3e087c1266cf2163cbfb Mon Sep 17 00:00:00 2001
From: trippyone <137233897+trippyone@users.noreply.github.com>
Date: Sat, 8 Jun 2024 10:40:39 -0400
Subject: [PATCH] Added STUN references + nat punch logic
---
Fika.Core/Fika.Core.csproj | 3 +
Fika.Core/Networking/FikaPingingClient.cs | 21 ++
.../Networking/Models/GetHostStunRequest.cs | 34 ++
.../Networking/Models/GetHostStunResponse.cs | 25 ++
.../Networking/NatPunch/FikaNatPunchClient.cs | 107 ++++++
.../Networking/NatPunch/FikaNatPunchServer.cs | 146 ++++++++
.../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/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 ++++
40 files changed, 2112 insertions(+)
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/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
diff --git a/Fika.Core/Fika.Core.csproj b/Fika.Core/Fika.Core.csproj
index 5982ec6a..9ef82a59 100644
--- a/Fika.Core/Fika.Core.csproj
+++ b/Fika.Core/Fika.Core.csproj
@@ -60,6 +60,9 @@
+
+ ..\References\websocket-sharp.dll
+
diff --git a/Fika.Core/Networking/FikaPingingClient.cs b/Fika.Core/Networking/FikaPingingClient.cs
index 8dc5a14e..02219563 100644
--- a/Fika.Core/Networking/FikaPingingClient.cs
+++ b/Fika.Core/Networking/FikaPingingClient.cs
@@ -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
{
@@ -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()
@@ -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;
@@ -74,6 +90,11 @@ public void PingEndPoint()
{
NetClient.SendUnconnectedMessage(writer, localEndPoint);
}
+
+ if (remoteStunEndPoint != null)
+ {
+ NetClient.SendUnconnectedMessage(writer, remoteStunEndPoint);
+ }
}
public void OnConnectionRequest(ConnectionRequest request)
diff --git a/Fika.Core/Networking/Models/GetHostStunRequest.cs b/Fika.Core/Networking/Models/GetHostStunRequest.cs
new file mode 100644
index 00000000..b1211cf6
--- /dev/null
+++ b/Fika.Core/Networking/Models/GetHostStunRequest.cs
@@ -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;
+ }
+ }
+}
\ 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..7c992ab7
--- /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..9cf88136
--- /dev/null
+++ b/Fika.Core/Networking/NatPunch/FikaNatPunchClient.cs
@@ -0,0 +1,107 @@
+using WebSocketSharp;
+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;
+ 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)
+ {
+ 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..59232bff
--- /dev/null
+++ b/Fika.Core/Networking/NatPunch/FikaNatPunchServer.cs
@@ -0,0 +1,146 @@
+using LiteNetLib;
+using System;
+using System.Net;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using SPT.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)
+ {
+ 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)
+ {
+ 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 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(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/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