From f35cc1eff873f3991dc18d9c94150c50f36ca8d3 Mon Sep 17 00:00:00 2001
From: Haves Irfan <20160532+ha-ves@users.noreply.github.com>
Date: Mon, 8 Jul 2024 04:56:17 +0900
Subject: [PATCH] WebRTC IPv6 support for TURN (along with
REQUESTED-ADDRESS-FAMILY) (#1133)
* requested-address-family RFC6156, address family constant values RFC5389
* ipv6 support for xor-addresses
* create base class for address attributes
* obsolescence
* transactionid usage for ipv6
* more transactionid usage
* requested-address-family for refresh turn
---
src/net/ICE/RtpIceChannel.cs | 12 +-
.../STUNAttributes/STUNAddressAttribute.cs | 38 +++++--
.../STUNAddressAttributeBase.cs | 41 +++++++
src/net/STUN/STUNAttributes/STUNAttribute.cs | 18 ++-
.../STUNAttributes/STUNXORAddressAttribute.cs | 105 ++++++++++++++----
src/net/STUN/STUNMessage.cs | 6 +-
6 files changed, 179 insertions(+), 41 deletions(-)
create mode 100644 src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs
index 9ce685ffc..e939ba155 100755
--- a/src/net/ICE/RtpIceChannel.cs
+++ b/src/net/ICE/RtpIceChannel.cs
@@ -2316,6 +2316,11 @@ private SocketError SendTurnAllocateRequest(IceServer iceServer)
STUNAttributeConstants.TcpTransportType :
STUNAttributeConstants.UdpTransportType));*/
+ allocateRequest.Attributes.Add(
+ new STUNAttribute(STUNAttributeTypesEnum.RequestedAddressFamily,
+ iceServer.ServerEndPoint.AddressFamily == AddressFamily.InterNetwork ?
+ STUNAttributeConstants.IPv4AddressFamily : STUNAttributeConstants.IPv6AddressFamily));
+
byte[] allocateReqBytes = null;
if (iceServer.Nonce != null && iceServer.Realm != null && iceServer._username != null && iceServer._password != null)
@@ -2359,6 +2364,11 @@ private SocketError SendTurnRefreshRequest(IceServer iceServer)
//allocateRequest.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Lifetime, 3600));
allocateRequest.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Lifetime, ALLOCATION_TIME_TO_EXPIRY_VALUE));
+ allocateRequest.Attributes.Add(
+ new STUNAttribute(STUNAttributeTypesEnum.RequestedAddressFamily,
+ iceServer.ServerEndPoint.AddressFamily == AddressFamily.InterNetwork ?
+ STUNAttributeConstants.IPv4AddressFamily : STUNAttributeConstants.IPv6AddressFamily));
+
byte[] allocateReqBytes = null;
if (iceServer.Nonce != null && iceServer.Realm != null && iceServer._username != null && iceServer._password != null)
@@ -2399,7 +2409,7 @@ private SocketError SendTurnCreatePermissionsRequest(string transactionID, IceSe
{
STUNMessage permissionsRequest = new STUNMessage(STUNMessageTypesEnum.CreatePermission);
permissionsRequest.Header.TransactionId = Encoding.ASCII.GetBytes(transactionID);
- permissionsRequest.Attributes.Add(new STUNXORAddressAttribute(STUNAttributeTypesEnum.XORPeerAddress, peerEndPoint.Port, peerEndPoint.Address));
+ permissionsRequest.Attributes.Add(new STUNXORAddressAttribute(STUNAttributeTypesEnum.XORPeerAddress, peerEndPoint.Port, peerEndPoint.Address, permissionsRequest.Header.TransactionId));
byte[] createPermissionReqBytes = null;
diff --git a/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs b/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
index 207ed1dd9..8e839caa4 100644
--- a/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
@@ -19,19 +19,17 @@
namespace SIPSorcery.Net
{
- public class STUNAddressAttribute : STUNAttribute
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
+ public class STUNAddressAttribute : STUNAddressAttributeBase
{
- public const UInt16 ADDRESS_ATTRIBUTE_LENGTH = 8;
-
- public int Family = 1; // Ipv4 = 1, IPv6 = 2.
- public int Port;
- public IPAddress Address;
-
- public override UInt16 PaddedLength
- {
- get { return ADDRESS_ATTRIBUTE_LENGTH; }
- }
-
+ ///
+ /// Obsolete.
+ ///
For IPv6 support, please parse using
+ ///
+ ///
+ /// Parses an IPv4 Address attribute.
+ ///
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
public STUNAddressAttribute(byte[] attributeValue)
: base(STUNAttributeTypesEnum.MappedAddress, attributeValue)
{
@@ -47,6 +45,14 @@ public STUNAddressAttribute(byte[] attributeValue)
Address = new IPAddress(new byte[] { attributeValue[4], attributeValue[5], attributeValue[6], attributeValue[7] });
}
+ ///
+ /// Obsolete.
+ ///
For IPv6 support, please parse using
+ ///
+ ///
+ /// Parses an IPv4 Address attribute.
+ ///
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
public STUNAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attributeValue)
: base(attributeType, attributeValue)
{
@@ -62,6 +68,14 @@ public STUNAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attribu
Address = new IPAddress(new byte[] { attributeValue[4], attributeValue[5], attributeValue[6], attributeValue[7] });
}
+ ///
+ /// Obsolete.
+ ///
For IPv6 support, please parse using
+ ///
+ ///
+ /// Parses an IPv4 Address attribute.
+ ///
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
public STUNAddressAttribute(STUNAttributeTypesEnum attributeType, int port, IPAddress address)
: base(attributeType, null)
{
diff --git a/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs b/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
new file mode 100644
index 000000000..6fea34308
--- /dev/null
+++ b/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+
+namespace SIPSorcery.Net
+{
+ public abstract class STUNAddressAttributeBase : STUNAttribute
+ {
+ ///
+ /// Obsolete.
+ ///
Please use or instead.
+ ///
+ ///
+ [Obsolete("Default attribute length for IPv4 only.")]
+ public const UInt16 ADDRESS_ATTRIBUTE_LENGTH = 8;
+
+ public const UInt16 ADDRESS_ATTRIBUTE_IPV4_LENGTH = 8;
+ public const UInt16 ADDRESS_ATTRIBUTE_IPV6_LENGTH = 20;
+
+ protected UInt16 AddressAttributeLength = ADDRESS_ATTRIBUTE_IPV4_LENGTH;
+ protected byte[] TransactionId;
+
+ ///
+ /// Defaults to IPv4 (0x01 // 1)
+ ///
+ public int Family = 1; // Ipv4 = 1, IPv6 = 2.
+ public int Port;
+ public IPAddress Address;
+
+ public override UInt16 PaddedLength
+ {
+ get => AddressAttributeLength;
+ }
+
+ public STUNAddressAttributeBase(STUNAttributeTypesEnum attributeType, byte[] value)
+ : base(attributeType, value)
+ {
+ }
+ }
+}
diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs
index fc23979c9..6fc997a65 100644
--- a/src/net/STUN/STUNAttributes/STUNAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: STUNAttribute.cs
//
// Description: Implements STUN message attributes as defined in RFC5389.
@@ -59,6 +59,7 @@ public enum STUNAttributeTypesEnum : ushort
ReflectedFrom = 0x000B, // Not used in RFC5389.
Realm = 0x0014,
Nonce = 0x0015,
+ RequestedAddressFamily = 0x0017,// Added in RFC6156.
XORMappedAddress = 0x0020,
Software = 0x8022, // Added in RFC5389.
@@ -97,6 +98,15 @@ public class STUNAttributeConstants
{
public static readonly byte[] UdpTransportType = new byte[] { 0x11, 0x00, 0x00, 0x00 }; // The payload type for UDP in a RequestedTransport type attribute.
public static readonly byte[] TcpTransportType = new byte[] { 0x06, 0x00, 0x00, 0x00 }; // The payload type for TCP in a RequestedTransport type attribute.
+
+ ///
+ /// The requested TURN relay ip address is IPv4 (RFC5389, Section 15.1)
+ ///
+ public static readonly byte[] IPv4AddressFamily = new byte[] { 0x01, 0x00, 0x00, 0x00 };
+ ///
+ /// The requested TURN relay ip address is IPv6 (RFC5389, Section 15.1)
+ ///
+ public static readonly byte[] IPv6AddressFamily = new byte[] { 0x02, 0x00, 0x00, 0x00 };
}
public class STUNAttribute
@@ -147,7 +157,9 @@ public STUNAttribute(STUNAttributeTypesEnum attributeType, ulong value)
Value = NetConvert.GetBytes(value);
}
- public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex)
+ public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex) => ParseMessageAttributes(buffer, startIndex, endIndex, null);
+
+ public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex, STUNHeader header)
{
if (buffer != null && buffer.Length > startIndex && buffer.Length >= endIndex)
{
@@ -194,7 +206,7 @@ public static List ParseMessageAttributes(byte[] buffer, int star
}
else if (attributeType == STUNAttributeTypesEnum.XORMappedAddress || attributeType == STUNAttributeTypesEnum.XORPeerAddress || attributeType == STUNAttributeTypesEnum.XORRelayedAddress)
{
- attribute = new STUNXORAddressAttribute(attributeType, stunAttributeValue);
+ attribute = new STUNXORAddressAttribute(attributeType, stunAttributeValue, header.TransactionId);
}
else if(attributeType == STUNAttributeTypesEnum.ConnectionId)
{
diff --git a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
index 6f90c7c6b..c6cc025c0 100644
--- a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: STUNXORAddressAttribute.cs
//
// Description: Implements STUN XOR mapped address attribute as defined in RFC5389.
@@ -14,6 +14,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Linq;
using System.Net;
using SIPSorcery.Sys;
@@ -23,41 +24,89 @@ namespace SIPSorcery.Net
/// This attribute is the same as the mapped address attribute except the address details are XOR'ed with the STUN magic cookie.
/// THe reason for this is to stop NAT application layer gateways from doing string replacements of private IP addresses and ports.
///
- public class STUNXORAddressAttribute : STUNAttribute
+ public class STUNXORAddressAttribute : STUNAddressAttributeBase
{
- public const UInt16 ADDRESS_ATTRIBUTE_LENGTH = 8;
-
- public int Family = 1; // Ipv4 = 1, IPv6 = 2.
- public int Port;
- public IPAddress Address;
-
- public override UInt16 PaddedLength
+ ///
+ /// Obsolete.
+ ///
For IPv6 support, please parse using
+ ///
+ ///
+ /// Parses an XOR-d (encoded) IPv4 Address attribute.
+ ///
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
+ public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attributeValue)
+ : this(attributeType, attributeValue, null)
{
- get { return ADDRESS_ATTRIBUTE_LENGTH; }
}
- public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attributeValue)
+ ///
+ /// Parses an XOR-d (encoded) Address attribute with IPv4/IPv6 support.
+ ///
+ /// of
+ /// or
+ /// or
+ /// the raw bytes
+ /// the
+ public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attributeValue, byte[] transactionId)
: base(attributeType, attributeValue)
{
+ Family = attributeValue[1];
+ AddressAttributeLength = Family == 1 ? ADDRESS_ATTRIBUTE_IPV4_LENGTH : ADDRESS_ATTRIBUTE_IPV6_LENGTH;
+ TransactionId = transactionId;
+
+ byte[] address;
+
if (BitConverter.IsLittleEndian)
{
Port = NetConvert.DoReverseEndian(BitConverter.ToUInt16(attributeValue, 2)) ^ (UInt16)(STUNHeader.MAGIC_COOKIE >> 16);
- UInt32 address = NetConvert.DoReverseEndian(BitConverter.ToUInt32(attributeValue, 4)) ^ STUNHeader.MAGIC_COOKIE;
- Address = new IPAddress(NetConvert.DoReverseEndian(address));
+ address = BitConverter.GetBytes(NetConvert.DoReverseEndian(BitConverter.ToUInt32(attributeValue, 4)) ^ STUNHeader.MAGIC_COOKIE).Reverse().ToArray();
}
else
{
Port = BitConverter.ToUInt16(attributeValue, 2) ^ (UInt16)(STUNHeader.MAGIC_COOKIE >> 16);
- UInt32 address = BitConverter.ToUInt32(attributeValue, 4) ^ STUNHeader.MAGIC_COOKIE;
- Address = new IPAddress(address);
+ address = BitConverter.GetBytes(BitConverter.ToUInt32(attributeValue, 4) ^ STUNHeader.MAGIC_COOKIE);
+ }
+
+ if (Family == STUNAttributeConstants.IPv6AddressFamily[0] && TransactionId != null)
+ {
+ address = address.Concat(BitConverter.GetBytes(BitConverter.ToUInt32(attributeValue, 08) ^ BitConverter.ToUInt32(TransactionId, 0)))
+ .Concat(BitConverter.GetBytes(BitConverter.ToUInt32(attributeValue, 12) ^ BitConverter.ToUInt32(TransactionId, 4)))
+ .Concat(BitConverter.GetBytes(BitConverter.ToUInt32(attributeValue, 16) ^ BitConverter.ToUInt32(TransactionId, 8)))
+ .ToArray();
}
+
+ Address = new IPAddress(address);
}
+ ///
+ /// Obsolete.
+ ///
For IPv6 support, please create using
+ ///
+ /// Creates an XOR-d (encoded) IPv4 Address attribute.
+ ///
+ [Obsolete("Provided for backward compatibility with RFC3489 clients.")]
public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, int port, IPAddress address)
+ : this(attributeType, port, address, null)
+ {
+ }
+
+ ///
+ /// Creates an XOR-d (encoded) Address attribute with IPv4/IPv6 support.
+ ///
+ /// of
+ /// or
+ /// or
+ /// Allocated Port
+ /// Allocated IPAddress
+ /// the
+ public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, int port, IPAddress address, byte[] transactionId)
: base(attributeType, null)
{
Port = port;
Address = address;
+ Family = address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 1 : 2;
+ AddressAttributeLength = Family == 1 ? ADDRESS_ATTRIBUTE_IPV4_LENGTH : ADDRESS_ATTRIBUTE_IPV6_LENGTH;
+ TransactionId = transactionId;
}
public override int ToByteBuffer(byte[] buffer, int startIndex)
@@ -65,33 +114,45 @@ public override int ToByteBuffer(byte[] buffer, int startIndex)
if (BitConverter.IsLittleEndian)
{
Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian((UInt16)base.AttributeType)), 0, buffer, startIndex, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(ADDRESS_ATTRIBUTE_LENGTH)), 0, buffer, startIndex + 2, 2);
+ Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(AddressAttributeLength)), 0, buffer, startIndex + 2, 2);
}
else
{
Buffer.BlockCopy(BitConverter.GetBytes((UInt16)base.AttributeType), 0, buffer, startIndex, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(ADDRESS_ATTRIBUTE_LENGTH), 0, buffer, startIndex + 2, 2);
+ Buffer.BlockCopy(BitConverter.GetBytes(AddressAttributeLength), 0, buffer, startIndex + 2, 2);
}
buffer[startIndex + 4] = 0x00;
buffer[startIndex + 5] = (byte)Family;
+ var address = Address.GetAddressBytes();
+
if (BitConverter.IsLittleEndian)
{
- UInt16 xorPort = Convert.ToUInt16(Convert.ToUInt16(Port) ^ (STUNHeader.MAGIC_COOKIE >> 16));
- UInt32 xorAddress = NetConvert.DoReverseEndian(BitConverter.ToUInt32(Address.GetAddressBytes(), 0)) ^ STUNHeader.MAGIC_COOKIE;
+ UInt16 xorPort = Convert.ToUInt16(Convert.ToUInt16(Port) ^ (UInt16)(STUNHeader.MAGIC_COOKIE >> 16));
+ UInt32 xorAddress = NetConvert.DoReverseEndian(BitConverter.ToUInt32(address, 0)) ^ STUNHeader.MAGIC_COOKIE;
Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(xorPort)), 0, buffer, startIndex + 6, 2);
Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(xorAddress)), 0, buffer, startIndex + 8, 4);
}
else
{
- UInt16 xorPort = Convert.ToUInt16(Convert.ToUInt16(Port) ^ (STUNHeader.MAGIC_COOKIE >> 16));
- UInt32 xorAddress = BitConverter.ToUInt32(Address.GetAddressBytes(), 0) ^ STUNHeader.MAGIC_COOKIE;
+ UInt16 xorPort = Convert.ToUInt16(Convert.ToUInt16(Port) ^ (UInt16)(STUNHeader.MAGIC_COOKIE >> 16));
+ UInt32 xorAddress = BitConverter.ToUInt32(address, 0) ^ STUNHeader.MAGIC_COOKIE;
Buffer.BlockCopy(BitConverter.GetBytes(xorPort), 0, buffer, startIndex + 6, 2);
Buffer.BlockCopy(BitConverter.GetBytes(xorAddress), 0, buffer, startIndex + 8, 4);
}
- return STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH + ADDRESS_ATTRIBUTE_LENGTH;
+ if (Family == STUNAttributeConstants.IPv6AddressFamily[0] && TransactionId != null)
+ {
+ Buffer.BlockCopy(
+ BitConverter.GetBytes(BitConverter.ToUInt32(address, 04) ^ BitConverter.ToUInt32(TransactionId, 0))
+ .Concat(BitConverter.GetBytes(BitConverter.ToUInt32(address, 08) ^ BitConverter.ToUInt32(TransactionId, 4)))
+ .Concat(BitConverter.GetBytes(BitConverter.ToUInt32(address, 12) ^ BitConverter.ToUInt32(TransactionId, 8)))
+ .ToArray(),
+ 0, buffer, startIndex + 12, 12);
+ }
+
+ return STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH + PaddedLength;
}
public override string ToString()
diff --git a/src/net/STUN/STUNMessage.cs b/src/net/STUN/STUNMessage.cs
index 28e6aa840..e6ed1a02f 100644
--- a/src/net/STUN/STUNMessage.cs
+++ b/src/net/STUN/STUNMessage.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: STUNMessage.cs
//
// Description: Implements STUN Message as defined in RFC5389.
@@ -86,7 +86,7 @@ public void AddXORPeerAddressAttribute(IPAddress remoteAddress, int remotePort)
public void AddXORAddressAttribute(STUNAttributeTypesEnum addressType, IPAddress remoteAddress, int remotePort)
{
- STUNXORAddressAttribute xorAddressAttribute = new STUNXORAddressAttribute(addressType, remotePort, remoteAddress);
+ STUNXORAddressAttribute xorAddressAttribute = new STUNXORAddressAttribute(addressType, remotePort, remoteAddress, Header.TransactionId);
Attributes.Add(xorAddressAttribute);
}
@@ -100,7 +100,7 @@ public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength)
if (stunMessage.Header.MessageLength > 0)
{
- stunMessage.Attributes = STUNAttribute.ParseMessageAttributes(buffer, STUNHeader.STUN_HEADER_LENGTH, bufferLength);
+ stunMessage.Attributes = STUNAttribute.ParseMessageAttributes(buffer, STUNHeader.STUN_HEADER_LENGTH, bufferLength, stunMessage.Header);
}
if (stunMessage.Attributes.Count > 0 && stunMessage.Attributes.Last().AttributeType == STUNAttributeTypesEnum.FingerPrint)