Преглед изворни кода

Add MAPPED-ADDRESS attribute

Bruce Wayne пре 5 година
родитељ
комит
1e841039f3

+ 1 - 1
STUN/Client/StunClient.cs

@@ -404,7 +404,7 @@ namespace STUN.Client
                         response.Parse(receiveBuffer);
 
                         // Check that transaction ID matches or not response what we want.
-                        if (NetUtils.CompareArray(request.TransactionId, response.TransactionId))
+                        if (request.TransactionId.IsEqual(response.TransactionId))
                         {
                             return response;
                         }

+ 42 - 4
STUN/Message/Attributes/Attribute.cs

@@ -1,7 +1,8 @@
-using System;
-using System.Collections.Generic;
-using STUN.Message.Enums;
+using STUN.Message.Enums;
 using STUN.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace STUN.Message.Attributes
 {
@@ -35,12 +36,49 @@ namespace STUN.Message.Attributes
 
             res.AddRange(Convert.ToUInt16(Type).ToBe());
             res.AddRange(Length.ToBe());
-            res.AddRange(Value.Value);
+            res.AddRange(Value.Bytes);
 
             var n = (4 - res.Count % 4) % 4; // 填充的字节数
             res.AddRange(BitUtils.GetRandomBytes(n));
 
             return res;
         }
+
+        /// <returns>
+        /// Parse 成功字节,0 则表示 Parse 失败
+        /// </returns>
+        public int TryParse(byte[] bytes)
+        {
+            if (bytes.Length < 4) return 0;
+
+            Type = (AttributeType)BitUtils.FromBe(bytes[0], bytes[1]);
+
+            Length = BitUtils.FromBe(bytes[2], bytes[3]);
+
+            if (bytes.Length < 4 + Length) return 0;
+
+            var value = bytes.Skip(4).Take(Length).ToArray();
+
+            Value = null;
+            switch (Type)
+            {
+                case AttributeType.MappedAddress:
+                {
+                    var t = new MappedAddressAttribute();
+                    if (t.TryParse(value))
+                    {
+                        Value = t;
+                    }
+                    break;
+                }
+                //TODO:Parse
+                default:
+                    return 0;
+            }
+
+            if (Value == null) return 0;
+
+            return 4 + Length + (4 - Length % 4) % 4; // 对齐
+        }
     }
 }

+ 3 - 1
STUN/Message/Attributes/IAttribute.cs

@@ -4,6 +4,8 @@ namespace STUN.Message.Attributes
 {
     public interface IAttribute
     {
-        public IEnumerable<byte> Value { get; set; }
+        public IEnumerable<byte> Bytes { get; }
+
+        public bool TryParse(byte[] bytes);
     }
 }

+ 60 - 0
STUN/Message/Attributes/MappedAddressAttribute.cs

@@ -0,0 +1,60 @@
+using STUN.Message.Enums;
+using STUN.Utils;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+
+namespace STUN.Message.Attributes
+{
+    /// <summary>
+    /// https://tools.ietf.org/html/rfc5389#section-15.1
+    /// </summary>
+    public class MappedAddressAttribute : IAttribute
+    {
+        public IEnumerable<byte> Bytes
+        {
+            get
+            {
+                var res = new List<byte> { 0, (byte)Family };
+                res.AddRange(Port.ToBe());
+                res.AddRange(Address.GetAddressBytes());
+                return res;
+            }
+        }
+
+        public IpFamily Family { get; set; }
+
+        public ushort Port { get; set; }
+
+        public IPAddress Address { get; set; }
+
+        public bool TryParse(byte[] bytes)
+        {
+            var length = 4;
+
+            if (bytes.Length < length) return false;
+
+            Family = (IpFamily)bytes[1];
+
+            switch (Family)
+            {
+                case IpFamily.IPv4:
+                    length += 4;
+                    break;
+                case IpFamily.IPv6:
+                    length += 16;
+                    break;
+                default:
+                    return false;
+            }
+
+            if (bytes.Length == length) return false;
+
+            Port = BitUtils.FromBe(bytes[2], bytes[3]);
+
+            Address = new IPAddress(bytes.Skip(4).ToArray());
+
+            return true;
+        }
+    }
+}

+ 80 - 81
STUN/Message/StunMessage.cs

@@ -1,21 +1,97 @@
-using System;
+using STUN.Message.Enums;
+using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Text;
-using STUN.Message.Enums;
 
 namespace STUN.Message
 {
     /// <summary>
-    /// Implements STUN message. Defined in RFC 3489.
+    /// Implements STUN message. Defined in RFC 3489, 5389.
     /// </summary>
+    /// <remarks>
+    /// https://tools.ietf.org/html/rfc3489#section-11.1
+    /// 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>
-        /// Default constructor.
+        /// 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];
@@ -545,82 +621,5 @@ namespace STUN.Message
         #endregion
 
 
-        #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
-
     }
 }

+ 10 - 0
STUN/Utils/BitUtils.cs

@@ -13,6 +13,11 @@ namespace STUN.Utils
             return BitConverter.IsLittleEndian ? res.Reverse() : res;
         }
 
+        public static ushort FromBe(byte b1, byte b2)
+        {
+            return BitConverter.ToUInt16(BitConverter.IsLittleEndian ? new[] { b2, b1 } : new[] { b1, b2 }, 0);
+        }
+
         public static IEnumerable<byte> GetRandomBytes(int n)
         {
             var temp = new byte[n];
@@ -20,5 +25,10 @@ namespace STUN.Utils
             rng.GetBytes(temp);
             return temp;
         }
+
+        public static bool IsEqual(this byte[] a, byte[] b)
+        {
+            return a != null && b != null && a.SequenceEqual(b);
+        }
     }
 }

+ 138 - 189
STUN/Utils/NetUtils.cs

@@ -1,85 +1,33 @@
-using System;
+using STUN.Client;
+using System;
 using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
-using STUN.Client;
 
 namespace STUN.Utils
 {
-	public static class NetUtils
-	{
-		#region static method CompareArray
-
-		/// <summary>
-		/// Compares if specified array items equals.
-		/// </summary>
-		/// <param name="array1">Array 1.</param>
-		/// <param name="array2">Array 2</param>
-		/// <returns>Returns true if both arrays are equal.</returns>
-		public static bool CompareArray(Array array1, Array array2)
-		{
-			return CompareArray(array1, array2, array2.Length);
-		}
-
-		/// <summary>
-		/// Compares if specified array items equals.
-		/// </summary>
-		/// <param name="array1">Array 1.</param>
-		/// <param name="array2">Array 2</param>
-		/// <param name="array2Count">Number of bytes in array 2 used for compare.</param>
-		/// <returns>Returns true if both arrays are equal.</returns>
-		public static bool CompareArray(Array array1, Array array2, int array2Count)
-		{
-			if (array1 == null && array2 == null)
-			{
-				return true;
-			}
-			if (array1 == null)
-			{
-				return false;
-			}
-			if (array2 == null)
-			{
-				return false;
-			}
-			if (array1.Length != array2Count)
-			{
-				return false;
-			}
-
-			for (var i = 0; i < array1.Length; i++)
-			{
-				if (!array1.GetValue(i).Equals(array2.GetValue(i)))
-				{
-					return false;
-				}
-			}
-
-			return true;
-		}
-
-		#endregion
-
-		#region static method IsPrivateIP
-
-		/// <summary>
-		/// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
-		/// </summary>
-		/// <param name="ip">IP address to check.</param>
-		/// <returns>Returns true if IP is private IP.</returns>
-		/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
-		public static bool IsPrivateIP(IPAddress ip)
-		{
-			if (ip == null)
-			{
-				throw new ArgumentNullException(nameof(ip));
-			}
-
-			if (ip.AddressFamily == AddressFamily.InterNetwork)
-			{
-				var ipBytes = ip.GetAddressBytes();
-
-				/* Private IPs:
+    public static class NetUtils
+    {
+        #region static method IsPrivateIP
+
+        /// <summary>
+        /// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
+        /// </summary>
+        /// <param name="ip">IP address to check.</param>
+        /// <returns>Returns true if IP is private IP.</returns>
+        /// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
+        public static bool IsPrivateIP(IPAddress ip)
+        {
+            if (ip == null)
+            {
+                throw new ArgumentNullException(nameof(ip));
+            }
+
+            if (ip.AddressFamily == AddressFamily.InterNetwork)
+            {
+                var ipBytes = ip.GetAddressBytes();
+
+                /* Private IPs:
 					First Octet = 192 AND Second Octet = 168 (Example: 192.168.X.X) 
 					First Octet = 172 AND (Second Octet >= 16 AND Second Octet <= 31) (Example: 172.16.X.X - 172.31.X.X)
 					First Octet = 10 (Example: 10.X.X.X)
@@ -87,117 +35,118 @@ namespace STUN.Utils
 
 				*/
 
-				if (ipBytes[0] == 192 && ipBytes[1] == 168)
-				{
-					return true;
-				}
-				if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
-				{
-					return true;
-				}
-				if (ipBytes[0] == 10)
-				{
-					return true;
-				}
-				if (ipBytes[0] == 169 && ipBytes[1] == 254)
-				{
-					return true;
-				}
-			}
-
-			return false;
-		}
-
-		#endregion
-
-		#region static method CreateSocket
-
-		/// <summary>
-		/// Creates new socket for the specified end point.
-		/// </summary>
-		/// <param name="localEP">Local end point.</param>
-		/// <param name="protocolType">Protocol type.</param>
-		/// <returns>Return newly created socket.</returns>
-		/// <exception cref="ArgumentNullException">Is raised when <b>localEP</b> is null reference.</exception>
-		public static Socket CreateSocket(IPEndPoint localEP, ProtocolType protocolType)
-		{
-			if (localEP == null)
-			{
-				throw new ArgumentNullException(nameof(localEP));
-			}
-
-			var socketType = SocketType.Stream;
-			if (protocolType == ProtocolType.Udp)
-			{
-				socketType = SocketType.Dgram;
-			}
-
-			if (localEP.AddressFamily == AddressFamily.InterNetwork)
-			{
-				var socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType);
-				socket.Bind(localEP);
-
-				return socket;
-			}
-
-			if (localEP.AddressFamily == AddressFamily.InterNetworkV6)
-			{
-				var socket = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType);
-				socket.Bind(localEP);
-
-				return socket;
-			}
-
-			throw new ArgumentException(@"Invalid IPEndPoint address family.");
-		}
-
-		#endregion
-
-		public const string DefaultLocalEnd = @"0.0.0.0:0";
-
-		public static IPEndPoint ParseEndpoint(string str)
-		{
-			var ipPort = str.Trim().Split(':');
-			if (ipPort.Length == 2)
-			{
-				if (IPAddress.TryParse(ipPort[0], out var ip))
-				{
-					if (ushort.TryParse(ipPort[1], out var port))
-					{
-						return new IPEndPoint(ip, port);
-					}
-				}
-			}
-
-			return null;
-		}
-
-		public static (string, string, string) NatTypeTestCore(string local, string server, int port)
-		{
-			try
-			{
-				if (string.IsNullOrWhiteSpace(server))
-				{
-					Debug.WriteLine(@"[ERROR]: Please specify STUN server !");
-					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);
-
-				return (
-						result.NatType.ToString(),
-						socketV4.LocalEndPoint.ToString(),
-						result.NatType != NatType.UdpBlocked ? result.PublicEndPoint.ToString() : string.Empty
-				);
-			}
-			catch (Exception ex)
-			{
-				Debug.WriteLine($@"[ERROR]: {ex}");
-				return (string.Empty, DefaultLocalEnd, string.Empty);
-			}
-		}
-	}
+                if (ipBytes[0] == 192 && ipBytes[1] == 168)
+                {
+                    return true;
+                }
+                if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
+                {
+                    return true;
+                }
+                if (ipBytes[0] == 10)
+                {
+                    return true;
+                }
+                if (ipBytes[0] == 169 && ipBytes[1] == 254)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        #endregion
+
+        #region static method CreateSocket
+
+        /// <summary>
+        /// Creates new socket for the specified end point.
+        /// </summary>
+        /// <param name="localEP">Local end point.</param>
+        /// <param name="protocolType">Protocol type.</param>
+        /// <returns>Return newly created socket.</returns>
+        /// <exception cref="ArgumentNullException">Is raised when <b>localEP</b> is null reference.</exception>
+        public static Socket CreateSocket(IPEndPoint localEP, ProtocolType protocolType)
+        {
+            if (localEP == null)
+            {
+                throw new ArgumentNullException(nameof(localEP));
+            }
+
+            var socketType = SocketType.Stream;
+            if (protocolType == ProtocolType.Udp)
+            {
+                socketType = SocketType.Dgram;
+            }
+
+            if (localEP.AddressFamily == AddressFamily.InterNetwork)
+            {
+                var socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType);
+                socket.Bind(localEP);
+
+                return socket;
+            }
+
+            if (localEP.AddressFamily == AddressFamily.InterNetworkV6)
+            {
+                var socket = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType);
+                socket.Bind(localEP);
+
+                return socket;
+            }
+
+            throw new ArgumentException(@"Invalid IPEndPoint address family.");
+        }
+
+        #endregion
+
+        public const string DefaultLocalEnd = @"0.0.0.0:0";
+
+        public static IPEndPoint ParseEndpoint(string str)
+        {
+            //TODO:IPv6
+            var ipPort = str.Trim().Split(':');
+            if (ipPort.Length == 2)
+            {
+                if (IPAddress.TryParse(ipPort[0], out var ip))
+                {
+                    if (ushort.TryParse(ipPort[1], out var port))
+                    {
+                        return new IPEndPoint(ip, port);
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        public static (string, string, string) NatTypeTestCore(string local, string server, int port)
+        {
+            try
+            {
+                if (string.IsNullOrWhiteSpace(server))
+                {
+                    Debug.WriteLine(@"[ERROR]: Please specify STUN server !");
+                    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);
+
+                return (
+                        result.NatType.ToString(),
+                        socketV4.LocalEndPoint.ToString(),
+                        result.NatType != NatType.UdpBlocked ? result.PublicEndPoint.ToString() : string.Empty
+                );
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($@"[ERROR]: {ex}");
+                return (string.Empty, DefaultLocalEnd, string.Empty);
+            }
+        }
+    }
 }