/// <summary> /// Parses an array of bytes containing every attributes of a message and /// add them to managed and unmanaged attributes lists /// </summary> /// <param name="attributes">The array of byte which contains every attributes of a message</param> private void ImportAttributes(byte[] attributes) { Int32 offset = 0; Int32 attributesLength = attributes.Length; while (offset < attributesLength) { // We retrieve length and add it attribute header length (4 bytes) UInt16 valueLength = BitConverter.ToUInt16(StunUtilities.SubArray(attributes, offset + 2, 2), 0); UInt16 attributeLength = (UInt16)(StunUtilities.ReverseBytes(valueLength) + 4); // Adjust original length to a padded 32bit length if (attributeLength % 4 != 0) { attributeLength = (UInt16)(attributeLength + (4 - attributeLength % 4)); } StunAttribute attr = StunUtilities.SubArray(attributes, offset, attributeLength); // When doing auto conversion of a byte array, according to STUN RFC 5389 // only the first attribute of a given type must be taken into account this.SetAttribute(attr, false); offset += attributeLength; } }
/// <summary> /// Computes a HMAC SHA1 based on this StunMessage attributes /// </summary> /// <param name="hmacSha1Key">The key of HMAC SHA1 computation algorithm</param> /// <returns>The HMAC computed value of this StunMessage</returns> private byte[] ComputeHMAC(byte[] hmacSha1Key) { byte[] hashed; using (HMACSHA1 hmacSha1 = new HMACSHA1(hmacSha1Key)) { StunMessage thisCopy = new StunMessage(this.MethodType, this.MethodClass, this.TransactionID); foreach (var item in this.attributesList) { if (item.Key == StunAttributeType.MessageIntegrity) { break; } thisCopy.SetAttribute(item.Value); } if (this.Stun.FingerPrint != null) { thisCopy.SetAttribute(this.Stun.FingerPrint); } byte[] thisCopyBytes = thisCopy; // Insert a fake message length for HMAC computation as described in [RFC5489#15.4] UInt16 dummyLength = StunUtilities.ReverseBytes((UInt16)(thisCopy.MessageLength + 24)); // 20 hmac + 4 header BitConverter.GetBytes(dummyLength).CopyTo(thisCopyBytes, 2); hashed = hmacSha1.ComputeHash(thisCopyBytes); } return(hashed); }
/// <summary> /// Convert a network-byte ordered array of bytes to a StunAttributeType /// </summary> /// <param name="bytes">An array of 2 bytes (16bits) representing an attribute type</param> /// <returns> /// The StunAttributeType matching the array of bytes /// Returns StunAttributeType.Unmanaged if the byte array doesn't match any StunAttributeType StunValue's /// </returns> public static StunAttributeType BytesToAttributeType(byte[] bytes) { UInt16 type = StunUtilities.ReverseBytes(BitConverter.ToUInt16(bytes, 0)); foreach (FieldInfo field in typeof(StunAttributeType).GetFields()) { Object[] fieldAttributes = field.GetCustomAttributes(typeof(StunValueAttribute), false); if (fieldAttributes.Length == 1) { StunValueAttribute stunValueAttribute = fieldAttributes.GetValue(0) as StunValueAttribute; if (stunValueAttribute != null && stunValueAttribute.Value == type) { return((StunAttributeType)Enum.Parse(typeof(StunAttributeType), field.Name)); } } } return(StunAttributeType.Unmanaged); }
/// <summary> /// Sample usage of this StunClient /// </summary> /// <param name="args">Unused</param> private static void Main(string[] args) { KeyValuePair <IPEndPoint, IPEndPoint> stunKeyValue = StunUtilities.GetMappedAddressFrom(null, new IPEndPoint(IPAddress.Parse("66.228.45.110"), StunClient.DEFAULT_STUN_PORT), ProtocolType.Udp); StunMessage msg = new StunMessage(StunMethodType.Binding, StunMethodClass.Request, StunUtilities.NewTransactionId); msg.Stun.Realm = new UTF8Attribute(StunAttributeType.Realm, "Hello World !"); msg.Stun.Username = new UTF8Attribute(StunAttributeType.Username, "Bob"); byte[] octets = msg; StunMessage msgCopy = octets; // Reuse of an existing local IPEndPoint makes the three requests returning // the same MappedAddress IPEndPoint if this client is behind a Cone NAT StunClient cli1 = new StunClient(stunKeyValue.Key, new IPEndPoint(IPAddress.Parse("66.228.45.110"), StunClient.DEFAULT_STUNS_PORT), ProtocolType.Tcp, null, (sender, certificate, chain, sslPolicyErrors) => true); // Sample TLS over TCP working with ejabberd but may not work with the sample server IP given here cli1.Connect(); StunMessage resp1 = cli1.SendMessage(msgCopy); cli1.Disconnect(); msgCopy.ClearAttributes(); StunClient cli2 = new StunClient(stunKeyValue.Key, new IPEndPoint(IPAddress.Parse("132.177.123.13"), StunClient.DEFAULT_STUN_PORT), ProtocolType.Udp, null, null); cli2.Connect(); StunMessage resp2 = cli2.SendMessage(msgCopy); cli2.Disconnect(); }
/// <summary> /// TODO: Documentation RefreshAllocation /// </summary> /// <param name="allocation"></param> /// <param name="lifeTime"></param> public void RefreshAllocation(TurnAllocation allocation, UInt32 lifeTime) { StunMessage msg = new StunMessage(StunMethodType.Refresh, StunMethodClass.Request, StunUtilities.NewTransactionId); msg.Stun.Username = new UTF8Attribute(StunAttributeType.Username, allocation.Username); msg.Stun.Realm = new UTF8Attribute(StunAttributeType.Realm, allocation.Realm); msg.Stun.Nonce = new UTF8Attribute(StunAttributeType.Nonce, allocation.Nonce); msg.Turn.LifeTime = new StunAttribute(StunAttributeType.LifeTime, BitConverter.GetBytes(StunUtilities.ReverseBytes(lifeTime))); msg.AddMessageIntegrity(allocation.Password, true); this.StunClient.BeginSendMessage(msg, null); }
/// <summary> /// TODO: Documentation StunClient_OnReceivedSuccessResponse /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> /// <param name="sentMsg"></param> /// <param name="transactionObject"></param> private void StunClient_OnReceivedSuccessResponse(object sender, StunMessage receivedMsg, StunMessage sentMsg, object transactionObject) { switch (receivedMsg.MethodType) { case StunMethodType.Allocate: TurnAllocation allocation = new TurnAllocation() { Username = sentMsg.Stun.Username.ValueString, Password = transactionObject as String, Realm = sentMsg.Stun.Realm.ValueString, Nonce = sentMsg.Stun.Nonce.ValueString, RelayedMappedAddress = receivedMsg.Turn.XorRelayedAddress, MappedAddress = receivedMsg.Stun.XorMappedAddress, StartTime = DateTime.Now, LifeTime = StunUtilities.ReverseBytes(BitConverter.ToUInt32(receivedMsg.Turn.LifeTime.Value, 0)) }; if (this.Allocations.ContainsKey(receivedMsg.Turn.XorRelayedAddress)) { this.Allocations[receivedMsg.Turn.XorRelayedAddress] = allocation; } else { this.Allocations.Add(receivedMsg.Turn.XorRelayedAddress, allocation); if (this.OnAllocateSucceed != null) { this.OnAllocateSucceed(this, allocation, sentMsg, receivedMsg); } } break; case StunMethodType.CreatePermission: TurnPermission permission = new TurnPermission() { PeerAddress = sentMsg.Turn.XorPeerAddress, StartTime = DateTime.Now, LifeTime = 300 }; TurnAllocation permAllocation = transactionObject as TurnAllocation; if (permAllocation.Permissions.ContainsKey(sentMsg.Turn.XorPeerAddress)) { permAllocation.Permissions[sentMsg.Turn.XorPeerAddress] = permission; } else { permAllocation.Permissions.Add(sentMsg.Turn.XorPeerAddress, permission); if (this.OnCreatePermissionSucceed != null) { this.OnCreatePermissionSucceed(this, permAllocation, permission, sentMsg, receivedMsg); } } break; case StunMethodType.ChannelBind: TurnChannel channel = new TurnChannel() { Channel = sentMsg.Turn.ChannelNumber, PeerAddress = sentMsg.Turn.XorPeerAddress, StartTime = DateTime.Now, LifeTime = 600 }; TurnAllocation channelAllocation = transactionObject as TurnAllocation; if (channelAllocation.Channels.ContainsKey(sentMsg.Turn.ChannelNumber)) { channelAllocation.Channels[sentMsg.Turn.ChannelNumber] = channel; } else { channelAllocation.Channels.Add(sentMsg.Turn.ChannelNumber, channel); if (this.OnChannelBindSucceed != null) { this.OnChannelBindSucceed(this, transactionObject as TurnAllocation, channel, sentMsg, receivedMsg); } } break; case StunMethodType.ConnectionBind: if (this.OnConnectionBindSucceed != null) { this.OnConnectionBindSucceed(this, transactionObject as Socket, receivedMsg); } break; } }