Bladeren bron

Use new client

Bruce Wayne 5 jaren geleden
bovenliggende
commit
44cf022fca

+ 35 - 0
STUN/Client/ClassicStunResult.cs

@@ -0,0 +1,35 @@
+using STUN.Client.Enums;
+using STUN.Client.Interfaces;
+using System.Net;
+
+namespace STUN.Client
+{
+    public class ClassicStunResult : IStunResult
+    {
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        /// <param name="natType">Specifies UDP network type.</param>
+        /// <param name="publicEndPoint">Public IP end point.</param>
+        public ClassicStunResult(NatType natType, IPEndPoint publicEndPoint)
+        {
+            NatType = natType;
+            PublicEndPoint = publicEndPoint;
+        }
+
+        #region Properties Implementation
+
+        /// <summary>
+        /// Gets UDP network type.
+        /// </summary>
+        public NatType NatType { get; }
+
+        /// <summary>
+        /// Gets public IP end point. This value is null if failed to get network type.
+        /// </summary>
+        public IPEndPoint PublicEndPoint { get; }
+
+        #endregion
+
+    }
+}

+ 66 - 0
STUN/Client/Enums/NatType.cs

@@ -0,0 +1,66 @@
+namespace STUN.Client.Enums
+{
+    /// <summary>
+    /// https://tools.ietf.org/html/rfc3489#section-5
+    /// https://tools.ietf.org/html/rfc3489#section-10.1
+    /// </summary>
+    public enum NatType
+    {
+        /// <summary>
+        /// Unknown
+        /// </summary>
+        Unknown,
+
+        /// <summary>
+        /// Server is not unsupported for testing NAT type
+        /// </summary>
+        UnsupportedServer,
+
+        /// <summary>
+        /// UDP is always blocked.
+        /// </summary>
+        UdpBlocked,
+
+        /// <summary>
+        /// No NAT, public IP, no firewall.
+        /// </summary>
+        OpenInternet,
+
+        /// <summary>
+        /// No NAT, public IP, but symmetric UDP firewall.
+        /// </summary>
+        SymmetricUdpFirewall,
+
+        /// <summary>
+        /// A full cone NAT is one where all requests from the same internal IP address and port are 
+        /// mapped to the same external IP address and port. Furthermore, any external host can send 
+        /// a packet to the internal host, by sending a packet to the mapped external address.
+        /// </summary>
+        FullCone,
+
+        /// <summary>
+        /// A restricted cone NAT is one where all requests from the same internal IP address and 
+        /// port are mapped to the same external IP address and port. Unlike a full cone NAT, an external
+        /// host (with IP address X) can send a packet to the internal host only if the internal host 
+        /// had previously sent a packet to IP address X.
+        /// </summary>
+        RestrictedCone,
+
+        /// <summary>
+        /// A port restricted cone NAT is like a restricted cone NAT, but the restriction 
+        /// includes port numbers. Specifically, an external host can send a packet, with source IP
+        /// address X and source port P, to the internal host only if the internal host had previously 
+        /// sent a packet to IP address X and port P.
+        /// </summary>
+        PortRestrictedCone,
+
+        /// <summary>
+        /// A symmetric NAT is one where all requests from the same internal IP address and port, 
+        /// to a specific destination IP address and port, are mapped to the same external IP address and
+        /// port.  If the same host sends a packet with the same source address and port, but to 
+        /// a different destination, a different mapping is used. Furthermore, only the external host that
+        /// receives a packet can send a UDP packet back to the internal host.
+        /// </summary>
+        Symmetric
+    }
+}

+ 10 - 0
STUN/Client/Interfaces/IStunClient.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace STUN.Client.Interfaces
+{
+    public interface IStunClient : IDisposable
+    {
+        public IStunResult Query();
+        public IStunResult QueryAsync();
+    }
+}

+ 9 - 0
STUN/Client/Interfaces/IStunResult.cs

@@ -0,0 +1,9 @@
+using System.Net;
+
+namespace STUN.Client.Interfaces
+{
+    public interface IStunResult
+    {
+        public IPEndPoint PublicEndPoint { get; }
+    }
+}

+ 0 - 55
STUN/Client/NatType.cs

@@ -1,55 +0,0 @@
-namespace STUN.Client
-{
-	/// <summary>
-	/// Specifies UDP network type.
-	/// </summary>
-	public enum NatType
-	{
-		/// <summary>
-		/// UDP is always blocked.
-		/// </summary>
-		UdpBlocked,
-
-		/// <summary>
-		/// No NAT, public IP, no firewall.
-		/// </summary>
-		OpenInternet,
-
-		/// <summary>
-		/// No NAT, public IP, but symmetric UDP firewall.
-		/// </summary>
-		SymmetricUdpFirewall,
-
-		/// <summary>
-		/// A full cone NAT is one where all requests from the same internal IP address and port are 
-		/// mapped to the same external IP address and port. Furthermore, any external host can send 
-		/// a packet to the internal host, by sending a packet to the mapped external address.
-		/// </summary>
-		FullCone,
-
-		/// <summary>
-		/// A restricted cone NAT is one where all requests from the same internal IP address and 
-		/// port are mapped to the same external IP address and port. Unlike a full cone NAT, an external
-		/// host (with IP address X) can send a packet to the internal host only if the internal host 
-		/// had previously sent a packet to IP address X.
-		/// </summary>
-		RestrictedCone,
-
-		/// <summary>
-		/// A port restricted cone NAT is like a restricted cone NAT, but the restriction 
-		/// includes port numbers. Specifically, an external host can send a packet, with source IP
-		/// address X and source port P, to the internal host only if the internal host had previously 
-		/// sent a packet to IP address X and port P.
-		/// </summary>
-		PortRestrictedCone,
-
-		/// <summary>
-		/// A symmetric NAT is one where all requests from the same internal IP address and port, 
-		/// to a specific destination IP address and port, are mapped to the same external IP address and
-		/// port.  If the same host sends a packet with the same source address and port, but to 
-		/// a different destination, a different mapping is used. Furthermore, only the external host that
-		/// receives a packet can send a UDP packet back to the internal host.
-		/// </summary>
-		Symmetric
-	}
-}

+ 0 - 281
STUN/Client/StunClient.cs

@@ -1,281 +0,0 @@
-using STUN.Message;
-using STUN.Message.Enums;
-using STUN.Utils;
-using System;
-using System.Net;
-using System.Net.Sockets;
-
-namespace STUN.Client
-{
-    /// <summary>
-    /// This class implements STUN client. Defined in RFC 3489.
-    /// </summary>
-    /// <example>
-    /// <code>
-    /// // Create new socket for STUN client.
-    /// Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
-    /// socket.Bind(new IPEndPoint(IPAddress.Any,0));
-    /// 
-    /// // Query STUN server
-    /// STUN_Result result = STUN_Client.Query("stun.ekiga.net",3478,socket);
-    /// if(result.NetType != STUN_NetType.UdpBlocked){
-    ///     // UDP blocked or !!!! bad STUN server
-    /// }
-    /// else{
-    ///     IPEndPoint publicEP = result.PublicEndPoint;
-    ///     // Do your stuff
-    /// }
-    /// </code>
-    /// </example>
-    public static class StunClient
-    {
-        #region static method Query
-
-        /// <summary>
-        /// Gets NAT info from STUN server.
-        /// </summary>
-        /// <param name="host">STUN server name or IP.</param>
-        /// <param name="port">STUN server port. Default port is 3478.</param>
-        /// <param name="socket">UDP socket to use.</param>
-        /// <returns>Returns UDP network info.</returns>
-        /// <exception cref="Exception">Throws exception if unexpected error happens.</exception>
-        public static StunResult Query(string host, int port, Socket socket)
-        {
-            if (host == null)
-            {
-                throw new ArgumentNullException(nameof(host));
-            }
-            if (socket == null)
-            {
-                throw new ArgumentNullException(nameof(socket));
-            }
-            if (port < 1)
-            {
-                throw new ArgumentException(@"Port value must be >= 1 !");
-            }
-            if (socket.ProtocolType != ProtocolType.Udp)
-            {
-                throw new ArgumentException(@"Socket must be UDP socket !");
-            }
-
-            var remoteEndPoint = new IPEndPoint(Dns.GetHostAddresses(host)[0], port);
-
-            /*
-                In test I, the client sends a STUN Binding Request to a server, without any flags set in the
-                CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute. This causes the server 
-                to send the response back to the address and port that the request came from.
-            
-                In test II, the client sends a Binding Request with both the "change IP" and "change port" flags
-                from the CHANGE-REQUEST attribute set.  
-              
-                In test III, the client sends a Binding Request with only the "change port" flag set.
-                          
-                                    +--------+
-                                    |  Test  |
-                                    |   I    |
-                                    +--------+
-                                         |
-                                         |
-                                         V
-                                        /\              /\
-                                     N /  \ Y          /  \ Y             +--------+
-                      UDP     <-------/Resp\--------->/ IP \------------->|  Test  |
-                      Blocked         \ ?  /          \Same/              |   II   |
-                                       \  /            \? /               +--------+
-                                        \/              \/                    |
-                                                         | N                  |
-                                                         |                    V
-                                                         V                    /\
-                                                     +--------+  Sym.      N /  \
-                                                     |  Test  |  UDP    <---/Resp\
-                                                     |   II   |  Firewall   \ ?  /
-                                                     +--------+              \  /
-                                                         |                    \/
-                                                         V                     |Y
-                              /\                         /\                    |
-               Symmetric  N  /  \       +--------+   N  /  \                   V
-                  NAT  <--- / IP \<-----|  Test  |<--- /Resp\               Open
-                            \Same/      |   I    |     \ ?  /               Internet
-                             \? /       +--------+      \  /
-                              \/                         \/
-                              |                           |Y
-                              |                           |
-                              |                           V
-                              |                           Full
-                              |                           Cone
-                              V              /\
-                          +--------+        /  \ Y
-                          |  Test  |------>/Resp\---->Restricted
-                          |   III  |       \ ?  /
-                          +--------+        \  /
-                                             \/
-                                              |N
-                                              |       Port
-                                              +------>Restricted
-
-            */
-
-            try
-            {
-                // Test I
-                var test1 = new StunMessage { Type = StunMessageType.BindingRequest };
-                var test1Response = DoTransaction(test1, socket, remoteEndPoint, 1600);
-
-                // UDP blocked.
-                if (test1Response == null)
-                {
-                    return new StunResult(NatType.UdpBlocked, null);
-                }
-                else
-                {
-                    // Test II
-                    var test2 = new StunMessage
-                    {
-                        Type = StunMessageType.BindingRequest,
-                        ChangeRequest = new StunChangeRequest(true, true)
-                    };
-
-                    // No NAT.
-                    if (socket.LocalEndPoint.Equals(test1Response.MappedAddress))
-                    {
-                        var test2Response = DoTransaction(test2, socket, remoteEndPoint, 1600);
-                        // Open Internet.
-                        if (test2Response != null)
-                        {
-                            return new StunResult(NatType.OpenInternet, test1Response.MappedAddress);
-                        }
-                        // Symmetric UDP firewall.
-                        else
-                        {
-                            return new StunResult(NatType.SymmetricUdpFirewall, test1Response.MappedAddress);
-                        }
-                    }
-                    // NAT
-                    else
-                    {
-                        var test2Response = DoTransaction(test2, socket, remoteEndPoint, 1600);
-
-                        // Full cone NAT.
-                        if (test2Response != null)
-                        {
-                            return new StunResult(NatType.FullCone, test1Response.MappedAddress);
-                        }
-                        else
-                        {
-                            /*
-                                If no response is received, it performs test I again, but this time, does so to 
-                                the address and port from the CHANGED-ADDRESS attribute from the response to test I.
-                            */
-
-                            // Test I(II)
-                            var test12 = new StunMessage { Type = StunMessageType.BindingRequest };
-
-                            var test12Response = DoTransaction(test12, socket, test1Response.ChangedAddress, 1600);
-                            if (test12Response == null)
-                            {
-                                throw new Exception(@"STUN Test I(II) didn't get response !");
-                            }
-                            else
-                            {
-                                // Symmetric NAT
-                                if (!test12Response.MappedAddress.Equals(test1Response.MappedAddress))
-                                {
-                                    return new StunResult(NatType.Symmetric, test1Response.MappedAddress);
-                                }
-                                else
-                                {
-                                    // Test III
-                                    var test3 = new StunMessage
-                                    {
-                                        Type = StunMessageType.BindingRequest,
-                                        ChangeRequest = new StunChangeRequest(false, true)
-                                    };
-
-                                    var test3Response = DoTransaction(test3, socket, test1Response.ChangedAddress, 1600);
-                                    // Restricted
-                                    if (test3Response != null)
-                                    {
-                                        return new StunResult(NatType.RestrictedCone, test1Response.MappedAddress);
-                                    }
-                                    // Port restricted
-                                    else
-                                    {
-                                        return new StunResult(NatType.PortRestrictedCone, test1Response.MappedAddress);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            finally
-            {
-                // Junk all late responses.
-                var startTime = DateTime.Now;
-                while (startTime.AddMilliseconds(200) > DateTime.Now)
-                {
-                    // We got response.
-                    if (socket.Poll(1, SelectMode.SelectRead))
-                    {
-                        var receiveBuffer = new byte[512];
-                        socket.Receive(receiveBuffer);
-                    }
-                }
-            }
-        }
-
-        #endregion
-
-        #region method DoTransaction
-
-        /// <summary>
-        /// Does STUN transaction. Returns transaction response or null if transaction failed.
-        /// </summary>
-        /// <param name="request">STUN message.</param>
-        /// <param name="socket">Socket to use for send/receive.</param>
-        /// <param name="remoteEndPoint">Remote end point.</param>
-        /// <param name="timeout">Timeout in milliseconds.</param>
-        /// <returns>Returns transaction response or null if transaction failed.</returns>
-        private static StunMessage DoTransaction(StunMessage request, Socket socket, IPEndPoint remoteEndPoint, int timeout)
-        {
-            var requestBytes = request.ToByteData();
-            var startTime = DateTime.Now;
-            // Retransmit with 500 ms.
-            while (startTime.AddMilliseconds(timeout) > DateTime.Now)
-            {
-                try
-                {
-                    socket.SendTo(requestBytes, remoteEndPoint);
-
-                    // We got response.
-                    if (socket.Poll(500 * 1000, SelectMode.SelectRead))
-                    {
-                        var receiveBuffer = new byte[512];
-                        socket.Receive(receiveBuffer);
-
-                        // Parse message
-                        var response = new StunMessage();
-                        response.Parse(receiveBuffer);
-
-                        // Check that transaction ID matches or not response what we want.
-                        if (request.TransactionId.IsEqual(response.TransactionId))
-                        {
-                            return response;
-                        }
-                    }
-                }
-                catch
-                {
-                    // ignored
-                }
-            }
-
-            return null;
-        }
-
-        #endregion
-
-        // TODO: Update to RFC 5389
-
-    }
-}

+ 159 - 0
STUN/Client/StunClient3489.cs

@@ -0,0 +1,159 @@
+using STUN.Client.Enums;
+using STUN.Client.Interfaces;
+using STUN.Message;
+using STUN.Message.Enums;
+using STUN.Utils;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace STUN.Client
+{
+    /// <summary>
+    /// https://tools.ietf.org/html/rfc3489#section-10.1
+    /// https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
+    /// </summary>
+    public class StunClient3489 : IStunClient
+    {
+        private readonly UdpClient _udpClient;
+        public IPEndPoint LocalEndPoint => (IPEndPoint)_udpClient.Client.LocalEndPoint;
+
+        private readonly string _server;
+        private readonly ushort _port;
+
+        public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null)
+        {
+            if (string.IsNullOrEmpty(server))
+            {
+                throw new ArgumentException(@"Please specify STUN server !");
+            }
+
+            if (port < 1)
+            {
+                throw new ArgumentException(@"Port value must be >= 1 !");
+            }
+
+            _server = server;
+            _port = port;
+
+            _udpClient = local == null ? new UdpClient() : new UdpClient(local);
+
+            _udpClient.Client.ReceiveTimeout = TimeSpan.FromSeconds(1.6).Milliseconds;
+        }
+
+        public IStunResult Query()
+        {
+            // test I
+            var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
+
+            var (response1, remote1) = Test(test1);
+            var mappedAddress1 = AttributeExtensions.GetMappedAddressAttribute(response1);
+            var changedAddress1 = AttributeExtensions.GetChangedAddressAttribute(response1);
+            if (mappedAddress1 == null || changedAddress1 == null)
+            {
+                return new ClassicStunResult(NatType.UdpBlocked, null);
+            }
+
+            var test2 = new StunMessage5389
+            {
+                StunMessageType = StunMessageType.BindingRequest,
+                MagicCookie = 0,
+                Attributes = new[] { AttributeExtensions.BuildChangeRequest(true, true) }
+            };
+
+            // test II
+            var (response2, remote2) = Test(test2);
+            var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
+            var changedAddress2 = AttributeExtensions.GetChangedAddressAttribute(response2);
+
+            if (Equals(mappedAddress1, LocalEndPoint))
+            {
+                // No NAT
+                var type = mappedAddress2 == null ? NatType.SymmetricUdpFirewall : NatType.OpenInternet;
+                return new ClassicStunResult(type, mappedAddress2);
+            }
+
+            // NAT
+            if (mappedAddress2 != null && changedAddress2 != null)
+            {
+                // 有些单 IP 服务器并不能测 NAT 类型,比如 Google 的
+                var type = Equals(remote1.Address, remote2.Address) || Equals(remote1.Port, remote2.Port) ? NatType.UnsupportedServer : NatType.FullCone;
+                return new ClassicStunResult(type, mappedAddress2);
+            }
+
+            // Test I(#2)
+            var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
+            var (response12, _) = Test(test12, changedAddress1);
+            var mappedAddress12 = AttributeExtensions.GetMappedAddressAttribute(response12);
+
+            if (mappedAddress12 != null)
+            {
+                if (!Equals(mappedAddress12, mappedAddress1))
+                {
+                    return new ClassicStunResult(NatType.Symmetric, mappedAddress12);
+                }
+
+                // Test III
+                var test3 = new StunMessage5389
+                {
+                    StunMessageType = StunMessageType.BindingRequest,
+                    MagicCookie = 0,
+                    Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
+                };
+                var (response3, _) = Test(test3, changedAddress1);
+                var mappedAddress3 = AttributeExtensions.GetMappedAddressAttribute(response3);
+                var type = mappedAddress3 != null ? NatType.RestrictedCone : NatType.PortRestrictedCone;
+                return new ClassicStunResult(type, mappedAddress3);
+            }
+
+            return new ClassicStunResult(NatType.Unknown, null);
+        }
+
+        public IStunResult QueryAsync()
+        {
+            throw new NotImplementedException();
+        }
+
+        private (StunMessage5389, IPEndPoint) Test(StunMessage5389 sendMessage, IPEndPoint remote = null)
+        {
+            try
+            {
+                var b1 = sendMessage.Bytes.ToArray();
+
+                if (remote == null)
+                {
+                    Debug.WriteLine($@"{LocalEndPoint} => {_server}:{_port} {b1.Length} 字节");
+                    _udpClient.Send(b1, b1.Length, _server, _port);
+                }
+                else
+                {
+                    Debug.WriteLine($@"{LocalEndPoint} => {remote} {b1.Length} 字节");
+                    _udpClient.Send(b1, b1.Length, remote);
+                }
+
+                IPEndPoint ipe = null;
+
+                var receive1 = _udpClient.Receive(ref ipe);
+
+                var message = new StunMessage5389();
+                if (message.TryParse(receive1) && message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
+                {
+                    Debug.WriteLine($@"收到 {ipe} {receive1.Length} 字节");
+                    return (message, ipe);
+                }
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine(ex.Message);
+            }
+            return (null, null);
+        }
+
+        public void Dispose()
+        {
+            _udpClient?.Dispose();
+        }
+    }
+}

+ 0 - 37
STUN/Client/StunResult.cs

@@ -1,37 +0,0 @@
-using System.Net;
-
-namespace STUN.Client
-{
-	/// <summary>
-	/// This class holds STUN_Client.Query method return data.
-	/// </summary>
-	public class StunResult
-	{
-		/// <summary>
-		/// Default constructor.
-		/// </summary>
-		/// <param name="natType">Specifies UDP network type.</param>
-		/// <param name="publicEndPoint">Public IP end point.</param>
-		public StunResult(NatType natType, IPEndPoint publicEndPoint)
-		{
-			NatType = natType;
-			PublicEndPoint = publicEndPoint;
-		}
-
-
-		#region Properties Implementation
-
-		/// <summary>
-		/// Gets UDP network type.
-		/// </summary>
-		public NatType NatType { get; }
-
-		/// <summary>
-		/// Gets public IP end point. This value is null if failed to get network type.
-		/// </summary>
-		public IPEndPoint PublicEndPoint { get; }
-
-		#endregion
-
-	}
-}

+ 12 - 9
STUN/Message/Attribute.cs

@@ -25,25 +25,29 @@ namespace STUN.Message
             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          */
 
-        public AttributeType Type { get; set; }
+        public AttributeType Type { get; set; } = AttributeType.Useless;
 
         public ushort Length { get; set; }
 
+        public int RealLength => Type == AttributeType.Useless ? 0 : 4 + Length + (4 - Length % 4) % 4;
+
         public IAttribute Value { get; set; }
 
-        private byte[] _magicCookie;
-        private byte[] _transactionId;
+        private readonly byte[] _magicCookie;
+        private readonly byte[] _transactionId;
+
+        public Attribute() { }
 
-        public Attribute(byte[] magicCookie, byte[] transactionID)
+        public Attribute(byte[] magicCookie, byte[] transactionId)
         {
-            if (magicCookie.Length != 4 || transactionID.Length != 12)
+            if (magicCookie.Length != 4 || transactionId.Length != 12)
             {
-                throw new ArgumentException(@"Wrong length");
+                throw new ArgumentException(@"Wrong Transaction ID length");
             }
 
             _magicCookie = magicCookie;
 
-            _transactionId = transactionID;
+            _transactionId = transactionId;
         }
 
         public IEnumerable<byte> ToBytes()
@@ -86,10 +90,9 @@ namespace STUN.Message
                 AttributeType.OtherAddress => new OtherAddressAttribute(),
                 AttributeType.ReflectedFrom => new ReflectedFromAttribute(),
                 AttributeType.ErrorCode => new ErrorCodeAttribute(),
+                //TODO:more
                 _ => new UselessAttribute()
             };
-
-            //TODO:Parse
             Value = t.TryParse(value) ? t : null;
 
             return 4 + Length + (4 - Length % 4) % 4; // 对齐

+ 11 - 0
STUN/Message/Attributes/AddressAttribute.cs

@@ -4,6 +4,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
+using System.Net.Sockets;
 
 namespace STUN.Message.Attributes
 {
@@ -61,5 +62,15 @@ namespace STUN.Message.Attributes
 
             return true;
         }
+
+        public override string ToString()
+        {
+            return Address?.AddressFamily switch
+            {
+                AddressFamily.InterNetwork => $@"{Address}:{Port}",
+                AddressFamily.InterNetworkV6 => $@"[{Address}]:{Port}",
+                _ => base.ToString()
+            };
+        }
     }
 }

+ 3 - 11
STUN/Message/Attributes/ChangeRequestAttribute.cs

@@ -1,4 +1,5 @@
-using System.Collections;
+using System;
+using System.Collections;
 using System.Collections.Generic;
 
 namespace STUN.Message.Attributes
@@ -8,16 +9,7 @@ namespace STUN.Message.Attributes
     /// </summary>
     public class ChangeRequestAttribute : IAttribute
     {
-        public IEnumerable<byte> Bytes
-        {
-            get
-            {
-                var bits = new BitArray(32, false) { [29] = ChangeIp, [30] = ChangePort };
-                var res = new byte[4];
-                bits.CopyTo(res, 0);
-                return res;
-            }
-        }
+        public IEnumerable<byte> Bytes => new byte[] { 0, 0, 0, (byte)(Convert.ToInt32(ChangeIp) << 2 | Convert.ToInt32(ChangePort) << 1) };
 
         public bool ChangeIp { get; set; }
 

+ 2 - 2
STUN/Message/Attributes/XorMappedAddressAttribute.cs

@@ -14,10 +14,10 @@ namespace STUN.Message.Attributes
         private readonly byte[] _magicCookie;
         private readonly byte[] _transactionId;
 
-        public XorMappedAddressAttribute(byte[] magicCookie, byte[] transactionID)
+        public XorMappedAddressAttribute(byte[] magicCookie, byte[] transactionId)
         {
             _magicCookie = magicCookie;
-            _transactionId = transactionID;
+            _transactionId = transactionId;
         }
 
         public override IEnumerable<byte> Bytes

+ 1 - 0
STUN/Message/Enums/AttributeType.cs

@@ -11,6 +11,7 @@
     /// </remarks>
     public enum AttributeType : ushort
     {
+        Useless = 0x0000,
         MappedAddress = 0x0001,
         ResponseAddress = 0x0002,
         ChangeRequest = 0x0003,

+ 0 - 37
STUN/Message/StunChangeRequest.cs

@@ -1,37 +0,0 @@
-namespace STUN.Message
-{
-    /// <summary>
-    /// This class implements STUN CHANGE-REQUEST attribute. Defined in RFC 3489 11.2.4.
-    /// </summary>
-    /// <remarks> 
-    /// https://tools.ietf.org/html/rfc3489#section-11.2.4
-    /// </remarks>
-    public class StunChangeRequest
-    {
-        /// <summary>
-        /// Default constructor.
-        /// </summary>
-        /// <param name="changeIp">Specifies if STUN server must send response to different IP than request was received.</param>
-        /// <param name="changePort">Specifies if STUN server must send response to different port than request was received.</param>
-        public StunChangeRequest(bool changeIp, bool changePort)
-        {
-            ChangeIp = changeIp;
-            ChangePort = changePort;
-        }
-
-        #region Properties Implementation
-
-        /// <summary>
-        /// Gets or sets if STUN server must send response to different IP than request was received.
-        /// </summary>
-        public bool ChangeIp { get; set; }
-
-        /// <summary>
-        /// Gets or sets if STUN server must send response to different port than request was received.
-        /// </summary>
-        public bool ChangePort { get; set; }
-
-        #endregion
-
-    }
-}

+ 0 - 35
STUN/Message/StunErrorCode.cs

@@ -1,35 +0,0 @@
-namespace STUN.Message
-{
-	/// <summary>
-	/// This class implements STUN ERROR-CODE. Defined in RFC 3489 11.2.9.
-	/// </summary>
-	public class StunErrorCode
-	{
-		/// <summary>
-		/// Default constructor.
-		/// </summary>
-		/// <param name="code">Error code.</param>
-		/// <param name="reasonText">Reason text.</param>
-		public StunErrorCode(int code, string reasonText)
-		{
-			Code = code;
-			ReasonText = reasonText;
-		}
-
-
-		#region Properties Implementation
-
-		/// <summary>
-		/// Gets or sets error code.
-		/// </summary>
-		public int Code { get; set; }
-
-		/// <summary>
-		/// Gets reason text.
-		/// </summary>
-		public string ReasonText { get; set; }
-
-		#endregion
-
-	}
-}

+ 0 - 624
STUN/Message/StunMessage.cs

@@ -1,624 +0,0 @@
-using STUN.Message.Enums;
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Text;
-
-namespace STUN.Message
-{
-    /// <summary>
-    /// Implements STUN message. Defined in RFC 5389.
-    /// </summary>
-    /// <remarks>
-    /// https://tools.ietf.org/html/rfc5389#section-6
-    /// </remarks>
-    public class StunMessage
-    {
-        #region Properties Implementation
-
-        /// <summary>
-        /// Gets STUN message type.
-        /// </summary>
-        public StunMessageType Type { get; set; } = StunMessageType.BindingRequest;
-
-        /// <summary>
-        /// Gets magic cookie value. This is always 0x2112A442.
-        /// </summary>
-        public int MagicCookie { get; private set; }
-
-        /// <summary>
-        /// Gets transaction ID.
-        /// </summary>
-        public byte[] TransactionId { get; private set; }
-
-        /// <summary>
-        /// Gets or sets IP end point what was actually connected to STUN server. Returns null if not specified.
-        /// </summary>
-        public IPEndPoint MappedAddress { get; set; }
-
-        /// <summary>
-        /// Gets or sets IP end point where to STUN client likes to receive response.
-        /// Value null means not specified.
-        /// </summary>
-        public IPEndPoint ResponseAddress { get; set; }
-
-        /// <summary>
-        /// Gets or sets how and where STUN server must send response back to STUN client.
-        /// Value null means not specified.
-        /// </summary>
-        public StunChangeRequest ChangeRequest { get; set; }
-
-        /// <summary>
-        /// Gets or sets STUN server IP end point what sent response to STUN client. Value null
-        /// means not specified.
-        /// </summary>
-        public IPEndPoint SourceAddress { get; set; }
-
-        /// <summary>
-        /// Gets or sets IP end point where STUN server will send response back to STUN client 
-        /// if the "change IP" and "change port" flags had been set in the ChangeRequest.
-        /// </summary>
-        public IPEndPoint ChangedAddress { get; set; }
-
-        /// <summary>
-        /// Gets or sets user name. Value null means not specified.
-        /// </summary>          
-        public string UserName { get; set; }
-
-        /// <summary>
-        /// Gets or sets password. Value null means not specified.
-        /// </summary>
-        public string Password { get; set; }
-
-        //public MessageIntegrity
-
-        /// <summary>
-        /// Gets or sets error info. Returns null if not specified.
-        /// </summary>
-        public StunErrorCode ErrorCode { get; set; }
-
-
-        /// <summary>
-        /// Gets or sets IP endpoint from which IP end point STUN server got STUN client request.
-        /// Value null means not specified.
-        /// </summary>
-        public IPEndPoint ReflectedFrom { get; set; }
-
-        /// <summary>
-        /// Gets or sets server name.
-        /// </summary>
-        public string ServerName { get; set; }
-
-        #endregion
-
-        public StunMessage()
-        {
-            TransactionId = new byte[12];
-            new Random().NextBytes(TransactionId);
-        }
-
-        #region method Parse
-
-        /// <summary>
-        /// Parses STUN message from raw data packet.
-        /// </summary>
-        /// <param name="data">Raw STUN message.</param>
-        /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
-        public void Parse(byte[] data)
-        {
-            if (data == null)
-            {
-                throw new ArgumentNullException(nameof(data));
-            }
-
-            /* RFC 5389 6.             
-                All STUN messages MUST start with a 20-byte header followed by zero
-                or more Attributes.  The STUN header contains a STUN message type,
-                magic cookie, transaction ID, and message length.
-
-                 0                   1                   2                   3
-                 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |0 0|     STUN Message Type     |         Message Length        |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |                         Magic Cookie                          |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |                                                               |
-                 |                     Transaction ID (96 bits)                  |
-                 |                                                               |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-              
-               The message length is the count, in bytes, of the size of the
-               message, not including the 20 byte header.
-            */
-
-            if (data.Length < 20)
-            {
-                throw new ArgumentException(@"Invalid STUN message value !");
-            }
-
-            var offset = 0;
-
-            //--- message header --------------------------------------------------
-
-            // STUN Message Type
-            var messageType = data[offset++] << 8 | data[offset++];
-            if (messageType == (int)StunMessageType.BindingErrorResponse)
-            {
-                Type = StunMessageType.BindingErrorResponse;
-            }
-            else if (messageType == (int)StunMessageType.BindingRequest)
-            {
-                Type = StunMessageType.BindingRequest;
-            }
-            else if (messageType == (int)StunMessageType.BindingResponse)
-            {
-                Type = StunMessageType.BindingResponse;
-            }
-            else if (messageType == (int)StunMessageType.SharedSecretErrorResponse)
-            {
-                Type = StunMessageType.SharedSecretErrorResponse;
-            }
-            else if (messageType == (int)StunMessageType.SharedSecretRequest)
-            {
-                Type = StunMessageType.SharedSecretRequest;
-            }
-            else if (messageType == (int)StunMessageType.SharedSecretResponse)
-            {
-                Type = StunMessageType.SharedSecretResponse;
-            }
-            else
-            {
-                throw new ArgumentException(@"Invalid STUN message type value !");
-            }
-
-            // Message Length
-            var messageLength = data[offset++] << 8 | data[offset++];
-
-            // Magic Cookie
-            MagicCookie = data[offset++] << 24 | data[offset++] << 16 | data[offset++] << 8 | data[offset++];
-
-            // Transaction ID
-            TransactionId = new byte[12];
-            Array.Copy(data, offset, TransactionId, 0, 12);
-            offset += 12;
-
-            //--- Message attributes ---------------------------------------------
-            while (offset - 20 < messageLength)
-            {
-                ParseAttribute(data, ref offset);
-            }
-        }
-
-        #endregion
-
-        #region method ToByteData
-
-        /// <summary>
-        /// Converts this to raw STUN packet.
-        /// </summary>
-        /// <returns>Returns raw STUN packet.</returns>
-        public byte[] ToByteData()
-        {
-            /* RFC 5389 6.             
-                All STUN messages MUST start with a 20-byte header followed by zero
-                or more Attributes.  The STUN header contains a STUN message type,
-                magic cookie, transaction ID, and message length.
-
-                 0                   1                   2                   3
-                 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |0 0|     STUN Message Type     |         Message Length        |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |                         Magic Cookie                          |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                 |                                                               |
-                 |                     Transaction ID (96 bits)                  |
-                 |                                                               |
-                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-              
-               The message length is the count, in bytes, of the size of the
-               message, not including the 20 byte header.
-            */
-
-            // We allocate 512 for header, that should be more than enough.
-            var msg = new byte[512];
-
-            var offset = 0;
-
-            //--- message header -------------------------------------
-
-            // STUN Message Type (2 bytes)
-            msg[offset++] = (byte)(((int)Type >> 8) & 0x3F);
-            msg[offset++] = (byte)((int)Type & 0xFF);
-
-            // Message Length (2 bytes) will be assigned at last.
-            msg[offset++] = 0;
-            msg[offset++] = 0;
-
-            // Magic Cookie           
-            msg[offset++] = (byte)((MagicCookie >> 24) & 0xFF);
-            msg[offset++] = (byte)((MagicCookie >> 16) & 0xFF);
-            msg[offset++] = (byte)((MagicCookie >> 8) & 0xFF);
-            msg[offset++] = (byte)(MagicCookie & 0xFF);
-
-            // Transaction ID (16 bytes)
-            Array.Copy(TransactionId, 0, msg, offset, 12);
-            offset += 12;
-
-            //--- Message attributes ------------------------------------
-
-            /* RFC 3489 11.2.
-                After the header are 0 or more attributes.  Each attribute is TLV
-                encoded, with a 16 bit type, 16 bit length, and variable value:
-
-                0                   1                   2                   3
-                0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-               |         Type                  |            Length             |
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-               |                             Value                             ....
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-            */
-
-            if (MappedAddress != null)
-            {
-                StoreEndPoint(AttributeType.MappedAddress, MappedAddress, msg, ref offset);
-            }
-            else if (ResponseAddress != null)
-            {
-                StoreEndPoint(AttributeType.ResponseAddress, ResponseAddress, msg, ref offset);
-            }
-            else if (ChangeRequest != null)
-            {
-                /*
-                    The CHANGE-REQUEST attribute is used by the client to request that
-                    the server use a different address and/or port when sending the
-                    response.  The attribute is 32 bits long, although only two bits (A
-                    and B) are used:
-
-                     0                   1                   2                   3
-                     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
-                    The meaning of the flags is:
-
-                    A: This is the "change IP" flag.  If true, it requests the server
-                       to send the Binding Response with a different IP address than the
-                       one the Binding Request was received on.
-
-                    B: This is the "change port" flag.  If true, it requests the
-                       server to send the Binding Response with a different port than the
-                       one the Binding Request was received on.
-                */
-
-                // Attribute header
-                msg[offset++] = 0;
-                msg[offset++] = (int)AttributeType.ChangeRequest & 0xFF;
-                msg[offset++] = 0;
-                msg[offset++] = 4;
-
-                msg[offset++] = 0;
-                msg[offset++] = 0;
-                msg[offset++] = 0;
-                msg[offset++] = (byte)(Convert.ToInt32(ChangeRequest.ChangeIp) << 2 | Convert.ToInt32(ChangeRequest.ChangePort) << 1);
-            }
-            else if (SourceAddress != null)
-            {
-                StoreEndPoint(AttributeType.SourceAddress, SourceAddress, msg, ref offset);
-            }
-            else if (ChangedAddress != null)
-            {
-                StoreEndPoint(AttributeType.ChangedAddress, ChangedAddress, msg, ref offset);
-            }
-            else if (UserName != null)
-            {
-                var userBytes = Encoding.ASCII.GetBytes(UserName);
-
-                // Attribute header
-                msg[offset++] = (int)AttributeType.Username >> 8;
-                msg[offset++] = (int)AttributeType.Username & 0xFF;
-                msg[offset++] = (byte)(userBytes.Length >> 8);
-                msg[offset++] = (byte)(userBytes.Length & 0xFF);
-
-                Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
-                offset += userBytes.Length;
-            }
-            else if (Password != null)
-            {
-                var userBytes = Encoding.ASCII.GetBytes(UserName);
-
-                // Attribute header
-                msg[offset++] = (int)AttributeType.Password >> 8;
-                msg[offset++] = (int)AttributeType.Password & 0xFF;
-                msg[offset++] = (byte)(userBytes.Length >> 8);
-                msg[offset++] = (byte)(userBytes.Length & 0xFF);
-
-                Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
-                offset += userBytes.Length;
-            }
-            else if (ErrorCode != null)
-            {
-                /* 3489 11.2.9.
-                    0                   1                   2                   3
-                    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |                   0                     |Class|     Number    |
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |      Reason Phrase (variable)                                ..
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                */
-
-                var reasonBytes = Encoding.ASCII.GetBytes(ErrorCode.ReasonText);
-
-                // Header
-                msg[offset++] = 0;
-                msg[offset++] = (int)AttributeType.ErrorCode;
-                msg[offset++] = 0;
-                msg[offset++] = (byte)(4 + reasonBytes.Length);
-
-                // Empty
-                msg[offset++] = 0;
-                msg[offset++] = 0;
-                // Class
-                msg[offset++] = (byte)Math.Floor(ErrorCode.Code / 100.0);
-                // Number
-                msg[offset++] = (byte)(ErrorCode.Code & 0xFF);
-                // ReasonPhrase
-                Array.Copy(reasonBytes, msg, reasonBytes.Length);
-                offset += reasonBytes.Length;
-            }
-            else if (ReflectedFrom != null)
-            {
-                StoreEndPoint(AttributeType.ReflectedFrom, ReflectedFrom, msg, ref offset);
-            }
-
-            // Update Message Length. NOTE: 20 bytes header not included.
-            msg[2] = (byte)((offset - 20) >> 8);
-            msg[3] = (byte)((offset - 20) & 0xFF);
-
-            // Make retVal with actual size.
-            var retVal = new byte[offset];
-            Array.Copy(msg, retVal, retVal.Length);
-
-            return retVal;
-        }
-
-        #endregion
-
-
-        #region method ParseAttribute
-
-        /// <summary>
-        /// Parses attribute from data.
-        /// </summary>
-        /// <param name="data">SIP message data.</param>
-        /// <param name="offset">Offset in data.</param>
-        private void ParseAttribute(byte[] data, ref int offset)
-        {
-            /* RFC 3489 11.2.
-                Each attribute is TLV encoded, with a 16 bit type, 16 bit length, and variable value:
-
-                0                   1                   2                   3
-                0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-               |         Type                  |            Length             |
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-               |                             Value                             ....
-               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                            
-            */
-
-            // Type
-            var type = (AttributeType)(data[offset++] << 8 | data[offset++]);
-
-            // Length
-            var length = data[offset++] << 8 | data[offset++];
-
-            // MAPPED-ADDRESS
-            if (type == AttributeType.MappedAddress)
-            {
-                MappedAddress = ParseEndPoint(data, ref offset);
-            }
-            // RESPONSE-ADDRESS
-            else if (type == AttributeType.ResponseAddress)
-            {
-                ResponseAddress = ParseEndPoint(data, ref offset);
-            }
-            // CHANGE-REQUEST
-            else if (type == AttributeType.ChangeRequest)
-            {
-                /*
-                    The CHANGE-REQUEST attribute is used by the client to request that
-                    the server use a different address and/or port when sending the
-                    response.  The attribute is 32 bits long, although only two bits (A
-                    and B) are used:
-
-                     0                   1                   2                   3
-                     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
-                    The meaning of the flags is:
-
-                    A: This is the "change IP" flag.  If true, it requests the server
-                       to send the Binding Response with a different IP address than the
-                       one the Binding Request was received on.
-
-                    B: This is the "change port" flag.  If true, it requests the
-                       server to send the Binding Response with a different port than the
-                       one the Binding Request was received on.
-                */
-
-                // Skip 3 bytes
-                offset += 3;
-
-                ChangeRequest = new StunChangeRequest((data[offset] & 4) != 0, (data[offset] & 2) != 0);
-                offset++;
-            }
-            // SOURCE-ADDRESS
-            else if (type == AttributeType.SourceAddress)
-            {
-                SourceAddress = ParseEndPoint(data, ref offset);
-            }
-            // CHANGED-ADDRESS
-            else if (type == AttributeType.ChangedAddress)
-            {
-                ChangedAddress = ParseEndPoint(data, ref offset);
-            }
-            // USERNAME
-            else if (type == AttributeType.Username)
-            {
-                UserName = Encoding.Default.GetString(data, offset, length);
-                offset += length;
-            }
-            // PASSWORD
-            else if (type == AttributeType.Password)
-            {
-                Password = Encoding.Default.GetString(data, offset, length);
-                offset += length;
-            }
-            // MESSAGE-INTEGRITY
-            else if (type == AttributeType.MessageIntegrity)
-            {
-                offset += length;
-            }
-            // ERROR-CODE
-            else if (type == AttributeType.ErrorCode)
-            {
-                /* 3489 11.2.9.
-                    0                   1                   2                   3
-                    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |                   0                     |Class|     Number    |
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                    |      Reason Phrase (variable)                                ..
-                    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                */
-
-                var errorCode = (data[offset + 2] & 0x7) * 100 + (data[offset + 3] & 0xFF);
-
-                ErrorCode = new StunErrorCode(errorCode, Encoding.Default.GetString(data, offset + 4, length - 4));
-                offset += length;
-            }
-            // UNKNOWN-ATTRIBUTES
-            else if (type == AttributeType.UnknownAttribute)
-            {
-                offset += length;
-            }
-            // REFLECTED-FROM
-            else if (type == AttributeType.ReflectedFrom)
-            {
-                ReflectedFrom = ParseEndPoint(data, ref offset);
-            }
-            // XorMappedAddress
-            // XorOnly
-            // Software
-            else if (type == AttributeType.Software)
-            {
-                ServerName = Encoding.Default.GetString(data, offset, length);
-                offset += length;
-            }
-            // Unknown
-            else
-            {
-                offset += length;
-            }
-        }
-
-        #endregion
-
-        #region method ParseEndPoint
-
-        /// <summary>
-        /// Parses IP endpoint attribute.
-        /// </summary>
-        /// <param name="data">STUN message data.</param>
-        /// <param name="offset">Offset in data.</param>
-        /// <returns>Returns parsed IP end point.</returns>
-        private static IPEndPoint ParseEndPoint(IReadOnlyList<byte> data, ref int offset)
-        {
-            /*
-                It consists of an eight bit address family, and a sixteen bit
-                port, followed by a fixed length value representing the IP address.
-
-                0                   1                   2                   3
-                0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                |x x x x x x x x|    Family     |           Port                |
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                |                             Address                           |
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-            */
-
-            // Skip family
-            offset++;
-            offset++;
-
-            // Port
-            var port = data[offset++] << 8 | data[offset++];
-
-            // Address
-            var ip = new byte[4];
-            ip[0] = data[offset++];
-            ip[1] = data[offset++];
-            ip[2] = data[offset++];
-            ip[3] = data[offset++];
-
-            return new IPEndPoint(new IPAddress(ip), port);
-        }
-
-        #endregion
-
-        #region method StoreEndPoint
-
-        /// <summary>
-        /// Stores ip end point attribute to buffer.
-        /// </summary>
-        /// <param name="type">Attribute type.</param>
-        /// <param name="endPoint">IP end point.</param>
-        /// <param name="message">Buffer where to store.</param>
-        /// <param name="offset">Offset in buffer.</param>
-        private static void StoreEndPoint(AttributeType type, IPEndPoint endPoint, IList<byte> message, ref int offset)
-        {
-            /*
-                It consists of an eight bit address family, and a sixteen bit
-                port, followed by a fixed length value representing the IP address.
-
-                0                   1                   2                   3
-                0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                |x x x x x x x x|    Family     |           Port                |
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-                |                             Address                           |
-                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             
-            */
-
-            // Header
-            message[offset++] = (byte)((int)type >> 8);
-            message[offset++] = (byte)((int)type & 0xFF);
-            message[offset++] = 0;
-            message[offset++] = 8;
-
-            // Unused
-            message[offset++] = 0;
-            // Family
-            message[offset++] = (byte)IpFamily.IPv4;
-            // Port
-            message[offset++] = (byte)(endPoint.Port >> 8);
-            message[offset++] = (byte)(endPoint.Port & 0xFF);
-            // Address
-            var ipBytes = endPoint.Address.GetAddressBytes();
-            message[offset++] = ipBytes[0];
-            message[offset++] = ipBytes[1];
-            message[offset++] = ipBytes[2];
-            message[offset++] = ipBytes[3];
-        }
-
-        #endregion
-
-
-    }
-}

+ 91 - 0
STUN/Message/StunMessage5389.cs

@@ -0,0 +1,91 @@
+using STUN.Message.Enums;
+using STUN.Utils;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace STUN.Message
+{
+    /// <summary>
+    /// https://tools.ietf.org/html/rfc5389#section-6
+    /// </summary>
+    public class StunMessage5389
+    {
+        public IEnumerable<byte> Header =>
+                Convert.ToUInt16(StunMessageType).ToBe().Concat(MessageLengthBytes)
+                        .Concat(MagicCookieBytes).Concat(TransactionId);
+
+        public IEnumerable<Attribute> Attributes { get; set; }
+
+        #region Header
+
+        public StunMessageType StunMessageType { get; set; }
+
+        public ushort MessageLength => Attributes.Aggregate<Attribute, ushort>(0, (current, attribute) => (ushort)(current + Convert.ToUInt16(attribute.RealLength)));
+
+        public IEnumerable<byte> MessageLengthBytes => MessageLength.ToBe();
+
+        public int MagicCookie { get; set; }
+
+        public IEnumerable<byte> MagicCookieBytes => MagicCookie.ToBe();
+
+        public byte[] TransactionId { get; private set; }
+
+        public IEnumerable<byte> ClassicTransactionId => MagicCookieBytes.Concat(TransactionId);
+
+        #endregion
+
+        public IEnumerable<byte> Bytes =>
+                Attributes.Aggregate(Header, (current, attribute) => current.Concat(attribute.ToBytes()));
+
+        public StunMessage5389()
+        {
+            Attributes = Array.Empty<Attribute>();
+            StunMessageType = StunMessageType.BindingRequest;
+            MagicCookie = 0x2112A442;
+            TransactionId = BitUtils.GetRandomBytes(12).ToArray();
+        }
+
+        public bool TryParse(byte[] bytes)
+        {
+            if (bytes.Length < 20) return false; // Check length
+
+            StunMessageType = (StunMessageType)BitUtils.FromBe((byte)(bytes[0] & 0b0011_1111), bytes[1]);
+
+            if (!Enum.IsDefined(typeof(StunMessageType), StunMessageType)) return false;
+
+            var length = BitUtils.FromBe(bytes[2], bytes[3]);
+
+            MagicCookie = BitUtils.FromBeToInt(bytes.Skip(4).Take(4));
+
+            TransactionId = bytes.Skip(8).Take(12).ToArray();
+
+            if (bytes.Length != length + 20) return false; // Check length
+
+            var list = new List<Attribute>();
+
+            var b = bytes.Skip(20).ToArray();
+
+            while (b.Length > 0)
+            {
+                var attribute = new Attribute(MagicCookieBytes.ToArray(), TransactionId);
+                var offset = attribute.TryParse(b);
+                if (offset > 0)
+                {
+                    list.Add(attribute);
+                    b = b.Skip(offset).ToArray();
+                }
+                else
+                {
+                    Debug.WriteLine($@"[Warning] Ignore wrong attribute: {BitConverter.ToString(b)}");
+                    break;
+                }
+            }
+
+            Attributes = list;
+
+            return true;
+        }
+    }
+}

+ 41 - 0
STUN/Utils/AttributeExtensions.cs

@@ -0,0 +1,41 @@
+using STUN.Message;
+using STUN.Message.Attributes;
+using STUN.Message.Enums;
+using System.Linq;
+using System.Net;
+
+namespace STUN.Utils
+{
+    public static class AttributeExtensions
+    {
+        public static Attribute BuildChangeRequest(bool changeIp, bool changePort)
+        {
+            return new Attribute
+            {
+                Type = AttributeType.ChangeRequest,
+                Length = 4,
+                Value = new ChangeRequestAttribute { ChangeIp = changeIp, ChangePort = changePort }
+            };
+        }
+
+        public static IPEndPoint GetMappedAddressAttribute(StunMessage5389 response)
+        {
+            var mappedAddressAttribute = response?.Attributes.First(t => t.Type == AttributeType.MappedAddress);
+
+            if (mappedAddressAttribute == null) return null;
+
+            var mapped = (MappedAddressAttribute)mappedAddressAttribute.Value;
+            return new IPEndPoint(mapped.Address, mapped.Port);
+        }
+
+        public static IPEndPoint GetChangedAddressAttribute(StunMessage5389 response)
+        {
+            var changedAddressAttribute = response?.Attributes.First(t => t.Type == AttributeType.ChangedAddress);
+
+            if (changedAddressAttribute == null) return null;
+
+            var address = (ChangedAddressAttribute)changedAddressAttribute.Value;
+            return new IPEndPoint(address.Address, address.Port);
+        }
+    }
+}

+ 12 - 1
STUN/Utils/BitUtils.cs

@@ -7,6 +7,12 @@ namespace STUN.Utils
 {
     public static class BitUtils
     {
+        public static IEnumerable<byte> ToBe(this int num)
+        {
+            var res = BitConverter.GetBytes(num);
+            return BitConverter.IsLittleEndian ? res.Reverse() : res;
+        }
+
         public static IEnumerable<byte> ToBe(this ushort num)
         {
             var res = BitConverter.GetBytes(num);
@@ -23,6 +29,11 @@ namespace STUN.Utils
             return BitConverter.ToUInt16(BitConverter.IsLittleEndian ? b.Reverse().ToArray() : b.ToArray(), 0);
         }
 
+        public static int FromBeToInt(IEnumerable<byte> b)
+        {
+            return BitConverter.ToInt32(BitConverter.IsLittleEndian ? b.Reverse().ToArray() : b.ToArray(), 0);
+        }
+
         public static IEnumerable<byte> GetRandomBytes(int n)
         {
             var temp = new byte[n];
@@ -31,7 +42,7 @@ namespace STUN.Utils
             return temp;
         }
 
-        public static bool IsEqual(this byte[] a, byte[] b)
+        public static bool IsEqual(this IEnumerable<byte> a, IEnumerable<byte> b)
         {
             return a != null && b != null && a.SequenceEqual(b);
         }

+ 6 - 7
STUN/Utils/NetUtils.cs

@@ -121,7 +121,7 @@ namespace STUN.Utils
             return null;
         }
 
-        public static (string, string, string) NatTypeTestCore(string local, string server, int port)
+        public static (string, string, string) NatTypeTestCore(string local, string server, ushort port)
         {
             try
             {
@@ -131,15 +131,14 @@ namespace STUN.Utils
                     return (string.Empty, DefaultLocalEnd, string.Empty);
                 }
 
-                using var socketV4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
-                var ipe = ParseEndpoint(local) ?? new IPEndPoint(IPAddress.Any, 0);
-                socketV4.Bind(ipe);
-                var result = StunClient.Query(server, port, socketV4);
+                using var client = new StunClient3489(server, port, ParseEndpoint(local));
+
+                var result = (ClassicStunResult)client.Query();
 
                 return (
                         result.NatType.ToString(),
-                        socketV4.LocalEndPoint.ToString(),
-                        result.NatType != NatType.UdpBlocked ? result.PublicEndPoint.ToString() : string.Empty
+                        client.LocalEndPoint.ToString(),
+                        $@"{result.PublicEndPoint}"
                 );
             }
             catch (Exception ex)

+ 3 - 1
UnitTest/UnitTest.cs

@@ -1,6 +1,8 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using STUN.Client;
 using STUN.Message.Attributes;
 using STUN.Message.Enums;
+using System;
 using System.Linq;
 using System.Net;