/// <summary> /// TODO: Documentation StunClient_OnReceivedIndication /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> private void StunClient_OnReceivedIndication(object sender, StunMessage receivedMsg) { switch (receivedMsg.MethodType) { case StunMethodType.ConnectionAttempt: if (this.OnConnectionAttemptReceived != null) this.OnConnectionAttemptReceived(this, receivedMsg); break; case StunMethodType.Data: if (this.OnDataReceived != null) this.OnDataReceived(this, receivedMsg); break; } }
/// <summary> /// TODO: Documentation StunClient_OnReceivedError /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> /// <param name="sentMsg"></param> /// <param name="transactionObject"></param> private void StunClient_OnReceivedError(object sender, StunMessage receivedMsg, StunMessage sentMsg, object transactionObject) { switch (receivedMsg.MethodType) { case StunMethodType.Allocate: if (receivedMsg.Stun.ErrorCode.ErrorType == ErrorCodeType.Unauthorized) { String[] credentials = transactionObject as String[]; if (credentials != null) this.AllocateRetry(receivedMsg, credentials[0], credentials[1]); else if (this.OnAllocateFailed != null) this.OnAllocateFailed(this, receivedMsg, sentMsg, transactionObject); } break; } }
/// <summary> /// TODO: Documentation turnManager_OnConnectionBindSucceed /// </summary> /// <param name="sender"></param> /// <param name="connectedSocket"></param> /// <param name="receivedMsg"></param> private void turnManager_OnConnectionBindSucceed(object sender, Socket connectedSocket, StunMessage receivedMsg) { if (this.OnTurnConnectionBindSucceed != null) this.OnTurnConnectionBindSucceed(this, connectedSocket, this.StartingSessionSid, this.StartingSessionRecipient); if (this.OnConnectionTryEnded != null) this.OnConnectionTryEnded(this, this.StartingSessionSid); this.CancelStartingSession(); }
/// <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; } }
/// <summary> /// TODO: Documentation SendIndication /// </summary> /// <param name="xorPeerAddress"></param> /// <param name="data"></param> /// <param name="allocation"></param> public void SendIndication(XorMappedAddress xorPeerAddress, byte[] data, TurnAllocation allocation) { StunMessage msg = new StunMessage(StunMethodType.Send, StunMethodClass.Indication, StunUtilities.NewTransactionId); msg.Turn.XorPeerAddress = xorPeerAddress; msg.Turn.Data = new StunAttribute(StunAttributeType.Data, data); 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.AddMessageIntegrity(allocation.Password, true); this.StunClient.BeginSendMessage(msg, null); }
/// <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> /// 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> /// 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_OnReceivedError /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> /// <param name="sentMsg"></param> /// <param name="transactionObject"></param> private void StunClient_OnReceivedError(object sender, StunMessage receivedMsg, StunMessage sentMsg, object transactionObject) { switch (receivedMsg.MethodType) { case StunMethodType.Allocate: if (receivedMsg.Stun.ErrorCode.ErrorType == ErrorCodeType.Unauthorized) { String[] credentials = transactionObject as String[]; if (credentials != null) { this.AllocateRetry(receivedMsg, credentials[0], credentials[1]); } else if (this.OnAllocateFailed != null) { this.OnAllocateFailed(this, receivedMsg, sentMsg, transactionObject); } } break; } }
/// <summary> /// TODO: Documentation Allocate /// </summary> /// <param name="username"></param> /// <param name="password"></param> public void Allocate(String username, String password) { StunMessage msg = new StunMessage(StunMethodType.Allocate, StunMethodClass.Request, StunUtilities.NewTransactionId); this.StunClient.BeginSendMessage(msg, new String[] { username, password }); }
/// <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; } }
/// <summary> /// Sends a StunMessage to the connected STUN Server. /// </summary> /// <param name="msgToSend">The StunMessage to send to the connected STUN Server</param> /// <param name="transactionObject"></param> public void BeginSendMessage(StunMessage msgToSend, Object transactionObject) { if(msgToSend.MethodClass == StunMethodClass.Request) this.PendingTransactions.Add(msgToSend.TransactionID, new KeyValuePair<StunMessage, Object>(msgToSend, transactionObject)); if (this.UseSsl) this.SslStream.BeginWrite(msgToSend, 0, msgToSend.Bytes.Length, new AsyncCallback(this.SendCallback), null); else this.Socket.BeginSend(msgToSend, 0, msgToSend.Bytes.Length, SocketFlags.None, new AsyncCallback(this.SendCallback), null); }
/// <summary> /// Sends a StunMessage to the connected STUN Server. /// </summary> /// <param name="msgToSend">The StunMessage to send to the connected STUN Server</param> /// <returns>The STUN Server response or null if msgToSend parameter is not a Request</returns> public StunMessage SendMessage(StunMessage msgToSend) { Boolean waitForResponse = msgToSend.MethodClass == StunMethodClass.Request; byte[] msgReceived = new byte[StunClient.BUFFER_SIZE]; if (this.UseSsl) { this.SslStream.Write(msgToSend, 0, msgToSend.Bytes.Length); if (waitForResponse) this.SslStream.Read(msgReceived, 0, StunClient.BUFFER_SIZE); } else { this.Socket.Send(msgToSend, 0, msgToSend.Bytes.Length, SocketFlags.None); if (waitForResponse) this.Socket.Receive(msgReceived, 0, StunClient.BUFFER_SIZE, SocketFlags.None); } if (waitForResponse && new ByteArrayComparer().Equals(((StunMessage)msgReceived).TransactionID, msgToSend.TransactionID)) { return msgReceived; } return null; }
/// <summary> /// TODO: Documentation turnManager_OnAllocateSucceed /// </summary> /// <param name="sender"></param> /// <param name="allocation"></param> /// <param name="sentMsg"></param> /// <param name="receivedMsg"></param> private void turnManager_OnAllocateSucceed(object sender, TurnAllocation allocation, StunMessage sentMsg, StunMessage receivedMsg) { if (this.StartingSessionRecipient != null) { this.turnSessions[this.StartingSessionSid].TurnAllocation = allocation; XmlDocument doc = new XmlDocument(); // Jingle Transport JingleIce jingleIce = new JingleIce(doc) { Pwd = JingleUtilities.GenerateIcePwd, Ufrag = JingleUtilities.GenerateIceUfrag }; if (this.OnIceCandidatesGathering != null) this.OnIceCandidatesGathering(this, jingleIce, (sender as TurnManager).HostEP, this.turnSessions[this.StartingSessionSid].UseTurnOnly, allocation); this.localCandidates.Add(this.StartingSessionSid, jingleIce.GetCandidates()); JingleIQ jingleIq = null; // Jingle Description Element jingleDescription = null; String contentName = null; if (this.OnDescriptionGathering != null) this.OnDescriptionGathering(this, doc, this.StartingSessionSid, ref jingleDescription, ref contentName); jingleIq = this.JingleManager.SessionRequest(this.StartingSessionRecipient, this.StartingSessionAction, this.StartingSessionSid, contentName, jingleDescription, jingleIce); //this.jingleManager.FindSession(this.StartingSessionSid).Local = jingleIq.Instruction; this.Stream.Write(jingleIq); } }
/// <summary> /// TODO: Documentation AllocateRetry /// </summary> /// <param name="receivedMsg"></param> /// <param name="username"></param> /// <param name="password"></param> private void AllocateRetry(StunMessage receivedMsg, String username, String password) { StunMessage msg = new StunMessage(StunMethodType.Allocate, StunMethodClass.Request, StunUtilities.NewTransactionId); msg.Turn.RequestedTransport = new StunAttribute(StunAttributeType.RequestedTransport, BitConverter.GetBytes(StunMessage.CODE_POINT_TCP)); msg.Stun.Username = new UTF8Attribute(StunAttributeType.Username, username); msg.Stun.Realm = receivedMsg.Stun.Realm; msg.Stun.Nonce = receivedMsg.Stun.Nonce; msg.AddMessageIntegrity(password, true); this.StunClient.BeginSendMessage(msg, password); }
/// <summary> /// TODO: Documentation turnManager_OnAllocateFailed /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> /// <param name="sentMsg"></param> /// <param name="transactionObject"></param> private void turnManager_OnAllocateFailed(object sender, StunMessage receivedMsg, StunMessage sentMsg, object transactionObject) { if (this.StartingSessionRecipient != null) { if (this.OnConnectionTryEnded != null) this.OnConnectionTryEnded(this, this.StartingSessionSid); this.DestroyTurnSession(this.StartingSessionSid); this.CancelStartingSession(); } }
/// <summary> /// TODO: Documentation BindChannel /// </summary> /// <param name="channelNumber"></param> /// <param name="xorPeerAddress"></param> /// <param name="allocation"></param> public void BindChannel(byte[] channelNumber, XorMappedAddress xorPeerAddress, TurnAllocation allocation) { StunMessage msg = new StunMessage(StunMethodType.ChannelBind, StunMethodClass.Request, StunUtilities.NewTransactionId); msg.Turn.ChannelNumber = new StunAttribute(StunAttributeType.ChannelNumber, channelNumber); msg.Turn.XorPeerAddress = xorPeerAddress; 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.AddMessageIntegrity(allocation.Password, true); this.StunClient.BeginSendMessage(msg, allocation); }
/// <summary> /// TODO: Documentation turnManager_OnConnectionAttemptReceived /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> private void turnManager_OnConnectionAttemptReceived(object sender, StunMessage receivedMsg) { (sender as TurnManager).ConnectionBind(receivedMsg.Turn.ConnectionId, this.TurnUsername, this.TurnPassword); }
/// <summary> /// TODO: Documentation ConnectionBind /// </summary> /// <param name="connectionId"></param> /// <param name="username"></param> /// <param name="password"></param> public void ConnectionBind(StunAttribute connectionId, String username, String password) { this.TurnTcpManager = new TurnManager(this.StunClient.ServerEP, this.StunClient.ProtocolType, this.StunClient.ClientCertificate, this.StunClient.RemoteCertificateValidation); this.TurnTcpManager.OnAllocateSucceed += (object sender, TurnAllocation allocation, StunMessage sentMsg, StunMessage receivedMsg) => { StunMessage msg = new StunMessage(StunMethodType.ConnectionBind, StunMethodClass.Request, StunUtilities.NewTransactionId); msg.Turn.ConnectionId = connectionId; 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.AddMessageIntegrity(allocation.Password, true); this.TurnTcpManager.StunClient.BeginSendMessage(msg, this.TurnTcpManager.StunClient.Socket); }; this.TurnTcpManager.OnConnectionBindSucceed += (object sender, Socket connectedSocket, StunMessage receivedMsg) => { this.TurnTcpManager.Allocations.Clear(); this.TurnTcpManager.StunClient.Cancel = true; if (this.OnConnectionBindSucceed != null) this.OnConnectionBindSucceed(sender, connectedSocket, receivedMsg); }; this.TurnTcpManager.Connect(); this.TurnTcpManager.Allocate(username, password); }
/// <summary> /// TODO: Documentation ReceiveCallback /// </summary> /// <param name="ar"></param> private void ReceiveCallback(IAsyncResult ar) { byte[] state = (byte[])ar.AsyncState; try { Int32 bytesReceived; if (this.UseSsl) { bytesReceived = this.SslStream.EndRead(ar); } else { bytesReceived = this.Socket.EndReceive(ar); } if (bytesReceived > 0) { StunMessage receivedMsg = state; KeyValuePair <StunMessage, Object> transactionObject = new KeyValuePair <StunMessage, Object>(); switch (receivedMsg.MethodClass) { case StunMethodClass.SuccessResponse: if (!this.IsPendingTransaction(receivedMsg.TransactionID, out transactionObject)) { return; } if (this.OnReceivedSuccessResponse != null) { this.OnReceivedSuccessResponse(this, receivedMsg, transactionObject.Key, transactionObject.Value); } break; case StunMethodClass.Indication: if (this.OnReceivedIndication != null) { this.OnReceivedIndication(this, receivedMsg); } break; case StunMethodClass.Error: if (!this.IsPendingTransaction(receivedMsg.TransactionID, out transactionObject)) { return; } if (this.OnReceivedError != null) { this.OnReceivedError(this, receivedMsg, transactionObject.Key, transactionObject.Value); } break; } byte[] result = new byte[StunClient.BUFFER_SIZE]; if (this.Socket != null && !this.Cancel) { if (this.UseSsl) { this.SslStream.BeginRead(result, 0, result.Length, new AsyncCallback(this.ReceiveCallback), result); } else { this.Socket.BeginReceive(result, 0, result.Length, SocketFlags.None, new AsyncCallback(this.ReceiveCallback), result); } } } else { if (!this.isDisconnecting) { this.Disconnect(); } } } catch (Exception ex) { if (!this.isDisconnecting) { this.Disconnect(); } } }