瀏覽代碼

Add binding test

Bruce Wayne 5 年之前
父節點
當前提交
c56789a728

+ 15 - 16
STUN/Client/StunClient3489.cs

@@ -18,20 +18,20 @@ namespace STUN.Client
     /// </summary>
     public class StunClient3489 : IStunClient
     {
-        public IPEndPoint LocalEndPoint => (IPEndPoint)_udpClient.Client.LocalEndPoint;
+        public IPEndPoint LocalEndPoint => (IPEndPoint)UdpClient.Client.LocalEndPoint;
 
         public TimeSpan Timeout
         {
-            get => TimeSpan.FromMilliseconds(_udpClient.Client.ReceiveTimeout);
-            set => _udpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
+            get => TimeSpan.FromMilliseconds(UdpClient.Client.ReceiveTimeout);
+            set => UdpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
         }
 
-        private readonly UdpClient _udpClient;
+        protected readonly UdpClient UdpClient;
 
-        private readonly IPAddress _server;
-        private readonly ushort _port;
+        protected readonly IPAddress Server;
+        protected readonly ushort Port;
 
-        public IPEndPoint RemoteEndPoint => _server == null ? null : new IPEndPoint(_server, _port);
+        public IPEndPoint RemoteEndPoint => Server == null ? null : new IPEndPoint(Server, Port);
 
         public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null, IDnsQuery dnsQuery = null)
         {
@@ -55,19 +55,19 @@ namespace STUN.Client
                 dnsQuery1 = new DefaultDnsQuery().Query;
             }
 
-            _server = dnsQuery1(server);
-            if (_server == null)
+            Server = dnsQuery1(server);
+            if (Server == null)
             {
                 throw new ArgumentException(@"Wrong STUN server !");
             }
-            _port = port;
+            Port = port;
 
-            _udpClient = local == null ? new UdpClient() : new UdpClient(local);
+            UdpClient = local == null ? new UdpClient() : new UdpClient(local);
 
             Timeout = TimeSpan.FromSeconds(1.6);
         }
 
-        public IStunResult Query()
+        public virtual IStunResult Query()
         {
             // test I
             var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
@@ -146,12 +146,11 @@ namespace STUN.Client
             return new ClassicStunResult(NatType.PortRestrictedCone, mappedAddress12);
         }
 
-        public Task<IStunResult> QueryAsync()
+        public virtual Task<IStunResult> QueryAsync()
         {
             throw new NotImplementedException();
         }
 
-
         /// <returns>
         /// (StunMessage, Remote, Local)
         /// </returns>
@@ -168,7 +167,7 @@ namespace STUN.Client
                 {
                     try
                     {
-                        var (receive1, ipe, local) = _udpClient.UdpReceive(b1, remote, receive);
+                        var (receive1, ipe, local) = UdpClient.UdpReceive(b1, remote, receive);
 
                         var message = new StunMessage5389();
                         if (message.TryParse(receive1) &&
@@ -192,7 +191,7 @@ namespace STUN.Client
 
         public void Dispose()
         {
-            _udpClient?.Dispose();
+            UdpClient?.Dispose();
         }
     }
 }

+ 97 - 0
STUN/Client/StunClient5389UDP.cs

@@ -0,0 +1,97 @@
+using STUN.Enums;
+using STUN.Interfaces;
+using STUN.Message;
+using STUN.StunResult;
+using STUN.Utils;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace STUN.Client
+{
+    public class StunClient5389UDP : StunClient3489
+    {
+        public StunClient5389UDP(string server, ushort port = 3478, IPEndPoint local = null, IDnsQuery dnsQuery = null)
+        : base(server, port, local, dnsQuery)
+        {
+            Timeout = TimeSpan.FromSeconds(3);
+        }
+
+        public override IStunResult Query()
+        {
+            throw new NotImplementedException();
+        }
+
+        public override async Task<IStunResult> QueryAsync()
+        {
+            return await BindingTestAsync();
+        }
+
+        public async Task<StunResult5389> BindingTestAsync()
+        {
+            BindingTestResult res;
+
+            var test = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest };
+            var (response1, _, local1) = await TestAsync(test, RemoteEndPoint, RemoteEndPoint);
+            var mappedAddress1 = AttributeExtensions.GetXorMappedAddressAttribute(response1);
+
+            if (response1 == null)
+            {
+                res = BindingTestResult.Fail;
+            }
+            else if (mappedAddress1 == null)
+            {
+                res = BindingTestResult.UnsupportedServer;
+            }
+            else
+            {
+                res = BindingTestResult.Success;
+            }
+
+            return new StunResult5389
+            {
+                BindingTestResult = res,
+                LocalEndPoint = local1 == null ? null : new IPEndPoint(local1, LocalEndPoint.Port),
+                PublicEndPoint = mappedAddress1
+            };
+        }
+
+        private async Task<(StunMessage5389, IPEndPoint, IPAddress)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
+        {
+            try
+            {
+                var b1 = sendMessage.Bytes.ToArray();
+                //var t = DateTime.Now;
+
+                // Simple retransmissions
+                //https://tools.ietf.org/html/rfc5389#section-7.2.1
+                //while (t + TimeSpan.FromSeconds(6) > DateTime.Now)
+                {
+                    try
+                    {
+
+                        var (receive1, ipe, local) = await UdpClient.UdpReceiveAsync(b1, remote, receive);
+
+                        var message = new StunMessage5389();
+                        if (message.TryParse(receive1) &&
+                            message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
+                        {
+                            return (message, ipe, local);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        Debug.WriteLine(ex);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine(ex);
+            }
+            return (null, null, null);
+        }
+    }
+}

+ 10 - 0
STUN/Enums/BindingTestResult.cs

@@ -0,0 +1,10 @@
+namespace STUN.Enums
+{
+    public enum BindingTestResult
+    {
+        Unknown,
+        UnsupportedServer,
+        Success,
+        Fail
+    }
+}

+ 14 - 0
STUN/StunResult/StunResult5389.cs

@@ -0,0 +1,14 @@
+using STUN.Enums;
+using STUN.Interfaces;
+using System.Net;
+
+namespace STUN.StunResult
+{
+    public class StunResult5389 : IStunResult
+    {
+        public IPEndPoint PublicEndPoint { get; set; }
+        public IPEndPoint LocalEndPoint { get; set; }
+        public BindingTestResult BindingTestResult { get; set; } = BindingTestResult.Unknown;
+
+    }
+}

+ 24 - 2
STUN/Utils/AttributeExtensions.cs

@@ -1,8 +1,8 @@
-using STUN.Message;
+using STUN.Enums;
+using STUN.Message;
 using STUN.Message.Attributes;
 using System.Linq;
 using System.Net;
-using STUN.Enums;
 
 namespace STUN.Utils
 {
@@ -37,5 +37,27 @@ namespace STUN.Utils
             var address = (ChangedAddressAttribute)changedAddressAttribute.Value;
             return new IPEndPoint(address.Address, address.Port);
         }
+
+        public static IPEndPoint GetXorMappedAddressAttribute(StunMessage5389 response)
+        {
+            var mappedAddressAttribute = response?.Attributes.FirstOrDefault(t => t.Type == AttributeType.XorMappedAddress) ??
+                                         response?.Attributes.FirstOrDefault(t => t.Type == AttributeType.MappedAddress);
+
+            if (mappedAddressAttribute == null) return null;
+
+            var mapped = (AddressAttribute)mappedAddressAttribute.Value;
+            return new IPEndPoint(mapped.Address, mapped.Port);
+        }
+
+        public static IPEndPoint GetOtherAddressAttribute(StunMessage5389 response)
+        {
+            var addressAttribute = response?.Attributes.FirstOrDefault(t => t.Type == AttributeType.OtherAddress)
+                                   ?? response?.Attributes.FirstOrDefault(t => t.Type == AttributeType.ChangedAddress);
+
+            if (addressAttribute == null) return null;
+
+            var address = (AddressAttribute)addressAttribute.Value;
+            return new IPEndPoint(address.Address, address.Port);
+        }
     }
 }

+ 21 - 0
STUN/Utils/NetUtils.cs

@@ -5,6 +5,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Net;
 using System.Net.Sockets;
+using System.Threading.Tasks;
 
 namespace STUN.Utils
 {
@@ -78,5 +79,25 @@ namespace STUN.Utils
                     (IPEndPoint)receive
                     , local);
         }
+
+        public static async Task<(byte[], IPEndPoint, IPAddress)> UdpReceiveAsync(this UdpClient client, byte[] bytes, IPEndPoint remote, EndPoint receive)
+        {
+            var localEndPoint = (IPEndPoint)client.Client.LocalEndPoint;
+
+            Debug.WriteLine($@"{localEndPoint} => {remote} {bytes.Length} 字节");
+
+            await client.SendAsync(bytes, bytes.Length, remote);
+            var res = new ArraySegment<byte>(new byte[ushort.MaxValue]);
+
+            var result = await client.Client.ReceiveMessageFromAsync(res, SocketFlags.None, receive);
+
+            var local = result.PacketInformation.Address;
+
+            Debug.WriteLine($@"{(IPEndPoint)result.RemoteEndPoint} => {local} {result.ReceivedBytes} 字节");
+
+            return (res.Take(result.ReceivedBytes).ToArray(),
+                    (IPEndPoint)result.RemoteEndPoint
+                    , local);
+        }
     }
 }

+ 14 - 0
UnitTest/UnitTest.cs

@@ -1,8 +1,10 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using STUN.Client;
 using STUN.Enums;
 using STUN.Message.Attributes;
 using System.Linq;
 using System.Net;
+using System.Threading.Tasks;
 
 namespace UnitTest
 {
@@ -62,5 +64,17 @@ namespace UnitTest
 
             Assert.IsTrue(_ipv6Response.SequenceEqual(t.Bytes));
         }
+
+        [TestMethod]
+        public async Task BindingTest()
+        {
+            var client = new StunClient5389UDP(@"stun.syncthing.net", 3478, new IPEndPoint(IPAddress.Any, 0));
+            var result = await client.BindingTestAsync();
+
+            Assert.AreEqual(result.BindingTestResult, BindingTestResult.Success);
+            Assert.IsNotNull(result.LocalEndPoint);
+            Assert.IsNotNull(result.PublicEndPoint);
+            Assert.AreNotEqual(result.LocalEndPoint.Address, IPAddress.Any);
+        }
     }
 }