private void CreateTurnPermissions() { try { var localTurnIceCandidate = (from cand in LocalIceCandidates where cand.TurnRelayIPEndPoint != null select cand).First(); var remoteTurnCandidate = (from cand in RemoteIceCandidates where cand.CandidateType == IceCandidateTypesEnum.relay select cand).First(); // Send create permission request STUNv2Message turnPermissionRequest = new STUNv2Message(STUNv2MessageTypesEnum.CreatePermission); turnPermissionRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); //turnBindRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.ChannelNumber, (ushort)3000)); turnPermissionRequest.Attributes.Add(new STUNv2XORAddressAttribute(STUNv2AttributeTypesEnum.XORPeerAddress, remoteTurnCandidate.Port, IPAddress.Parse(remoteTurnCandidate.NetworkAddress))); turnPermissionRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Username, Encoding.UTF8.GetBytes(localTurnIceCandidate.TurnServer.Username))); turnPermissionRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Nonce, Encoding.UTF8.GetBytes(localTurnIceCandidate.TurnServer.Nonce))); turnPermissionRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Realm, Encoding.UTF8.GetBytes(localTurnIceCandidate.TurnServer.Realm))); MD5 md5 = new MD5CryptoServiceProvider(); byte[] hmacKey = md5.ComputeHash(Encoding.UTF8.GetBytes(localTurnIceCandidate.TurnServer.Username + ":" + localTurnIceCandidate.TurnServer.Realm + ":" + localTurnIceCandidate.TurnServer.Password)); byte[] turnPermissionReqBytes = turnPermissionRequest.ToByteBuffer(hmacKey, false); localTurnIceCandidate.LocalRtpSocket.SendTo(turnPermissionReqBytes, localTurnIceCandidate.TurnServer.ServerEndPoint); } catch (Exception excp) { logger.Error("Exception CreateTurnPermissions. " + excp); } }
private void AllocateTurn(IceCandidate iceCandidate) { try { if (iceCandidate.TurnAllocateAttempts >= MAXIMUM_TURN_ALLOCATE_ATTEMPTS) { logger.Debug("TURN allocation for local socket " + iceCandidate.LocalAddress + " failed after " + iceCandidate.TurnAllocateAttempts + " attempts."); iceCandidate.IsGatheringComplete = true; } else { iceCandidate.TurnAllocateAttempts++; //logger.Debug("Sending STUN connectivity check to client " + client.SocketAddress + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.Allocate); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Lifetime, 3600)); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.RequestedTransport, STUNv2AttributeConstants.UdpTransportType)); // UDP byte[] stunReqBytes = stunRequest.ToByteBuffer(null, false); iceCandidate.LocalRtpSocket.SendTo(stunReqBytes, iceCandidate.TurnServer.ServerEndPoint); } } catch (Exception excp) { logger.Error("Exception AllocateTurn. " + excp); } }
private void SendInitialStunBindingRequest(IceCandidate iceCandidate, ManualResetEvent iceGatheringCompleteMRE) { int attempt = 1; while (attempt < INITIAL_STUN_BINDING_ATTEMPTS_LIMIT && !IsClosed && !iceCandidate.IsGatheringComplete) { logger.Debug("Sending STUN binding request " + attempt + " from " + iceCandidate.LocalRtpSocket.LocalEndPoint + " to " + iceCandidate.TurnServer.ServerEndPoint + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); byte[] stunReqBytes = stunRequest.ToByteBuffer(null, false); iceCandidate.LocalRtpSocket.SendTo(stunReqBytes, iceCandidate.TurnServer.ServerEndPoint); Thread.Sleep(INITIAL_STUN_BINDING_PERIOD_MILLISECONDS); attempt++; } iceCandidate.IsGatheringComplete = true; // Potentially save a few seconds if all the ICE candidates are now ready. if (LocalIceCandidates.All(x => x.IsGatheringComplete)) { iceGatheringCompleteMRE.Set(); } }
public static STUNv2Message ParseSTUNMessage(byte[] buffer, int bufferLength) { if (buffer != null && buffer.Length > 0 && buffer.Length >= bufferLength) { STUNv2Message stunMessage = new STUNv2Message(); stunMessage.Header = STUNv2Header.ParseSTUNHeader(buffer); if (stunMessage.Header.MessageLength > 0) { stunMessage.Attributes = STUNv2Attribute.ParseMessageAttributes(buffer, STUNv2Header.STUN_HEADER_LENGTH, bufferLength); } return(stunMessage); } return(null); }
private void ProcessStunMessage(IceCandidate iceCandidate, STUNv2Message stunMessage, IPEndPoint remoteEndPoint) { //logger.Debug("STUN message received from remote " + remoteEndPoint + " " + stunMessage.Header.MessageType + "."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); // ToDo: Check authentication. byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(LocalIcePassword, true); iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint); iceCandidate.LastStunRequestReceivedAt = DateTime.Now; iceCandidate.IsStunRemoteExchangeComplete = true; if (_remoteIceCandidates != null && !_remoteIceCandidates.Any(x => (x.NetworkAddress == remoteEndPoint.Address.ToString() || x.RemoteAddress == remoteEndPoint.Address.ToString()) && (x.Port == remoteEndPoint.Port || x.RemotePort == remoteEndPoint.Port))) { // This STUN request has come from a socket not in the remote ICE candidates list. Add it so we can send our STUN binding request to it. IceCandidate remoteIceCandidate = new IceCandidate() { Transport = "udp", NetworkAddress = remoteEndPoint.Address.ToString(), Port = remoteEndPoint.Port, CandidateType = IceCandidateTypesEnum.host }; logger.Debug("Adding missing remote ICE candidate for " + remoteEndPoint + "."); _remoteIceCandidates.Add(remoteIceCandidate); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (_turnServerEndPoint != null && remoteEndPoint.ToString() == _turnServerEndPoint.ToString()) { if (iceCandidate.IsGatheringComplete == false) { var reflexAddressAttribute = stunMessage.Attributes.FirstOrDefault(y => y.AttributeType == STUNv2AttributeTypesEnum.XORMappedAddress) as STUNv2XORAddressAttribute; if (reflexAddressAttribute != null) { iceCandidate.StunRflxIPEndPoint = new IPEndPoint(reflexAddressAttribute.Address, reflexAddressAttribute.Port); iceCandidate.IsGatheringComplete = true; logger.Debug("ICE gathering complete for local socket " + iceCandidate.LocalRtpSocket.LocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + "."); } else { iceCandidate.IsGatheringComplete = true; logger.Debug("The STUN binding response received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + " did not have an XORMappedAddress attribute, rlfx address can not be determined."); } } } else { iceCandidate.LastStunResponseReceivedAt = DateTime.Now; if (iceCandidate.IsStunLocalExchangeComplete == false) { iceCandidate.IsStunLocalExchangeComplete = true; logger.Debug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.LocalRtpSocket.LocalEndPoint + ", remote socket " + remoteEndPoint + "."); SetIceConnectionState(IceConnectionStatesEnum.Connected); } } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.Warn("A STUN binding error response was received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + "."); } else { logger.Warn("An unrecognised STUN request was received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + "."); } }
private void StartWebRtcRtpListener(IceCandidate iceCandidate) { string localEndPoint = "?"; try { localEndPoint = iceCandidate.LocalRtpSocket.LocalEndPoint.ToString(); logger.Debug("Starting WebRTC RTP listener for call " + CallID + " on socket " + localEndPoint + "."); IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); UdpClient localSocket = new UdpClient(); localSocket.Client = iceCandidate.LocalRtpSocket; while (!IsClosed) { try { //logger.Debug("ListenToReceiverWebRTCClient Receive."); byte[] buffer = localSocket.Receive(ref remoteEndPoint); iceCandidate.LastCommunicationAt = DateTime.Now; //logger.Debug(buffer.Length + " bytes read on Receiver Client media socket from " + remoteEndPoint.ToString() + "."); //if (buffer.Length > 3 && buffer[0] == 0x16 && buffer[1] == 0xfe) if (buffer[0] >= 20 && buffer[0] <= 64) { //OnMediaPacket(iceCandidate, buffer, remoteEndPoint); if (OnDtlsPacket != null) { OnDtlsPacket(iceCandidate, buffer, remoteEndPoint); } } //else if ((buffer[0] & 0x80) == 0) else if (buffer[0] == 0 || buffer[0] == 1) { STUNv2Message stunMessage = STUNv2Message.ParseSTUNMessage(buffer, buffer.Length); ProcessStunMessage(iceCandidate, stunMessage, remoteEndPoint); } else { if (OnMediaPacket != null) { OnMediaPacket(iceCandidate, buffer, remoteEndPoint); } } } catch (Exception sockExcp) { _communicationFailureCount++; logger.Warn("Exception ListenToReceiverWebRTCClient Receive (" + localEndPoint + " and " + remoteEndPoint + ", failure count " + _communicationFailureCount + "). " + sockExcp.Message); // Need to be careful about deciding when the connection has failed. Sometimes the STUN requests we send will arrive before the remote peer is ready and cause a socket exception. // Only shutdown the peer if we are sure all ICE intialisation is complete and the socket exception occurred after the RTP had stated flowing. if (iceCandidate.IsStunLocalExchangeComplete && iceCandidate.IsStunRemoteExchangeComplete && iceCandidate.RemoteRtpEndPoint != null && remoteEndPoint != null && iceCandidate.RemoteRtpEndPoint.ToString() == remoteEndPoint.ToString() && DateTime.Now.Subtract(IceNegotiationStartedAt).TotalSeconds > 10) { logger.Warn("WebRtc peer communication failure on call " + CallID + " for local RTP socket " + localEndPoint + " and remote RTP socket " + remoteEndPoint + " ."); iceCandidate.DisconnectionMessage = sockExcp.Message; break; } else if (_communicationFailureCount > COMMUNICATION_FAILURE_COUNT_FOR_CLOSE) { logger.Warn("WebRtc peer communication failures on call " + CallID + " exceeded limit of " + COMMUNICATION_FAILURE_COUNT_FOR_CLOSE + " closing peer."); break; } //else if (DateTime.Now.Subtract(peer.IceNegotiationStartedAt).TotalSeconds > ICE_CONNECTION_LIMIT_SECONDS) //{ // logger.Warn("WebRTC peer ICE connection establishment timed out on call " + peer.CallID + " for " + iceCandidate.LocalRtpSocket.LocalEndPoint + "."); // break; //} } } Close(); } catch (Exception excp) { logger.Error("Exception ListenForWebRTCClient (" + localEndPoint + "). " + excp); } }
private void SendStunConnectivityChecks() { try { while (!IsClosed) { try { // If one of the ICE candidates has the remote RTP socket set then the negotiation is complete and the STUN checks are to keep the connection alive. if (LocalIceCandidates.Any(x => x.IsConnected == true)) { var iceCandidate = LocalIceCandidates.First(x => x.IsConnected == true); // Remote RTP endpoint gets set when the DTLS negotiation is finished. if (iceCandidate.RemoteRtpEndPoint != null) { //logger.Debug("Sending STUN connectivity check to client " + iceCandidate.RemoteRtpEndPoint + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + LocalIceUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); // Must send this to get DTLS started. byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); iceCandidate.LocalRtpSocket.SendTo(stunReqBytes, iceCandidate.RemoteRtpEndPoint); iceCandidate.LastSTUNSendAt = DateTime.Now; } var secondsSinceLastResponse = DateTime.Now.Subtract(iceCandidate.LastCommunicationAt).TotalSeconds; if (secondsSinceLastResponse > ICE_TIMEOUT_SECONDS) { logger.Warn("No STUN response was received on a connected ICE connection for " + secondsSinceLastResponse + "s, closing connection."); iceCandidate.IsDisconnected = true; if (LocalIceCandidates.Any(x => x.IsConnected == true) == false) { // If there are no connected local candidates left close the peer. Close(); break; } } } else { if (_remoteIceCandidates.Count() > 0) { foreach (var localIceCandidate in LocalIceCandidates.Where(x => x.IsStunLocalExchangeComplete == false && x.StunConnectionRequestAttempts < MAXIMUM_STUN_CONNECTION_ATTEMPTS)) { localIceCandidate.StunConnectionRequestAttempts++; // ToDo: Include srflx and relay addresses. foreach (var remoteIceCandidate in RemoteIceCandidates.Where(x => x.Transport != "tcp" && x.NetworkAddress.NotNullOrBlank())) // Only supporting UDP candidates at this stage. { IPAddress remoteAddress = IPAddress.Parse(remoteIceCandidate.NetworkAddress); logger.Debug("Sending authenticated STUN binding request " + localIceCandidate.StunConnectionRequestAttempts + " from " + localIceCandidate.LocalRtpSocket.LocalEndPoint + " to WebRTC peer at " + remoteIceCandidate.NetworkAddress + ":" + remoteIceCandidate.Port + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + LocalIceUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); // Must send this to get DTLS started. byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); localIceCandidate.LocalRtpSocket.SendTo(stunReqBytes, new IPEndPoint(IPAddress.Parse(remoteIceCandidate.NetworkAddress), remoteIceCandidate.Port)); localIceCandidate.LastSTUNSendAt = DateTime.Now; } } } } } catch (Exception excp) { logger.Error("Exception SendStunConnectivityCheck ConnectivityCheck. " + excp); } if (!IsClosed) { Thread.Sleep(ESTABLISHED_STUN_BINDING_PERIOD_MILLISECONDS); } } } catch (Exception excp) { logger.Error("Exception SendStunConnectivityCheck. " + excp); } }