private void AllocateTurn(IceCandidate iceCandidate) { try { if (iceCandidate.TurnAllocateAttempts >= MAXIMUM_TURN_ALLOCATE_ATTEMPTS) { logger.LogDebug("TURN allocation for local socket " + iceCandidate.LocalAddress + " failed after " + iceCandidate.TurnAllocateAttempts + " attempts."); iceCandidate.IsGatheringComplete = true; } else { iceCandidate.TurnAllocateAttempts++; //logger.LogDebug("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); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, iceCandidate.TurnServer.ServerEndPoint, stunReqBytes); } } catch (Exception excp) { logger.LogError("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(); } }
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); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, localTurnIceCandidate.TurnServer.ServerEndPoint, turnPermissionReqBytes); } catch (Exception excp) { logger.LogError("Exception CreateTurnPermissions. " + excp); } }
/// <summary> /// Performs a connectivity check for a single candidate pair entry. /// </summary> /// <param name="candidatePair">The candidate pair to perform a connectivity check for.</param> /// <param name="setUseCandidate">If true indicates we are acting as the "controlling" ICE agent /// and are nominating this candidate as the chosen one.</param> /// <remarks>As specified in https://tools.ietf.org/html/rfc8445#section-7.2.4.</remarks> private void SendConnectivityCheck(ChecklistEntry candidatePair, bool setUseCandidate) { candidatePair.State = ChecklistEntryState.InProgress; candidatePair.LastCheckSentAt = DateTime.Now; candidatePair.ChecksSent++; candidatePair.RequestTransactionID = Crypto.GetRandomString(STUNv2Header.TRANSACTION_ID_LENGTH); IPEndPoint remoteEndPoint = candidatePair.RemoteCandidate.GetEndPoint(); logger.LogDebug($"Sending ICE connectivity check from {_rtpChannel.RTPLocalEndPoint} to {remoteEndPoint} (use candidate {setUseCandidate})."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Encoding.ASCII.GetBytes(candidatePair.RequestTransactionID); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + LocalIceUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, BitConverter.GetBytes(candidatePair.Priority))); if (setUseCandidate) { stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); } byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunReqBytes); }
public void BindingRequestWithUsernameToBytesUnitTest() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); STUNv2Message initMessage = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); initMessage.AddUsernameAttribute("someusernamex"); byte[] stunMessageBytes = initMessage.ToByteBuffer(null, false); Console.WriteLine(BitConverter.ToString(stunMessageBytes)); Assert.IsTrue(stunMessageBytes.Length % 4 == 0); }
/// <summary> /// From RFC5764: /// +----------------+ /// | 127 < B< 192 -+--> forward to RTP /// | | /// packet --> | 19 < B< 64 -+--> forward to DTLS /// | | /// | B< 2 -+--> forward to STUN /// +----------------+ /// </summary> /// <param name="remoteEP"></param> /// <param name="buffer"></param> private void OnRTPDataReceived(IPEndPoint remoteEP, byte[] buffer) { //logger.LogDebug($"RTP channel received a packet from {remoteEP}, {buffer?.Length} bytes."); if (buffer?.Length > 0) { _lastCommunicationAt = DateTime.Now; try { if (buffer[0] == 0x00 || buffer[0] == 0x01) { // STUN packet. _lastStunMessageReceivedAt = DateTime.Now; var stunMessage = STUNv2Message.ParseSTUNMessage(buffer, buffer.Length); ProcessStunMessage(stunMessage, remoteEP); } else if (buffer[0] >= 128 && buffer[0] <= 191) { // RTP/RTCP packet need to decrypt. //if (buffer[1] == 0xC8 /* RTCP SR */ || buffer[1] == 0xC9 /* RTCP RR */) //{ // // RTCP packet. // //webRtcClient.LastSTUNReceiveAt = DateTime.Now; //} //else //{ // // RTP packet. // //int res = peer.SrtpReceiveContext.UnprotectRTP(buffer, buffer.Length); // //if (res != 0) // //{ // // logger.Warn("SRTP unprotect failed, result " + res + "."); // //} //} } else if (buffer[0] >= 20 && buffer[0] <= 63) { // DTLS packet. // Do nothing. The DTLSContext already has the socket handle and is monitoring // for DTLS packets. } else { logger.LogWarning("Unknown packet type received on RTP channel."); } } catch (Exception excp) { logger.LogError($"Exception WebRtcSession.OnRTPDataReceived {excp.Message}."); } } }
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; }
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 async Task SendTurnServerBindingRequest(IceCandidate iceCandidate) { int attempt = 1; while (attempt < INITIAL_STUN_BINDING_ATTEMPTS_LIMIT && !IsConnected && !IsClosed && !iceCandidate.IsGatheringComplete) { logger.LogDebug($"Sending STUN binding request {attempt} from {_rtpChannel.RTPLocalEndPoint} 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); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, iceCandidate.TurnServer.ServerEndPoint, stunReqBytes); await Task.Delay(INITIAL_STUN_BINDING_PERIOD_MILLISECONDS); attempt++; } iceCandidate.IsGatheringComplete = true; }
/// <summary> /// From RFC5764: /// +----------------+ /// | 127 < B< 192 -+--> forward to RTP /// | | /// packet --> | 19 < B< 64 -+--> forward to DTLS /// | | /// | B< 2 -+--> forward to STUN /// +----------------+ /// </summary> /// <param name="remoteEP"></param> /// <param name="buffer"></param> private void OnRTPDataReceived(IPEndPoint localEndPoint, IPEndPoint remoteEP, byte[] buffer) { //logger.LogDebug($"RTP channel received a packet from {remoteEP}, {buffer?.Length} bytes."); if (buffer?.Length > 0) { _lastCommunicationAt = DateTime.Now; try { if (buffer[0] == 0x00 || buffer[0] == 0x01) { // STUN packet. _lastStunMessageReceivedAt = DateTime.Now; var stunMessage = STUNv2Message.ParseSTUNMessage(buffer, buffer.Length); ProcessStunMessage(stunMessage, remoteEP); } else if (buffer[0] >= 128 && buffer[0] <= 191) { // RTP/RTCP packet. // Do nothing. The RTPSession takes care of these. } else if (buffer[0] >= 20 && buffer[0] <= 63) { // DTLS packet. // Do nothing. The DTLSContext already has the socket handle and is monitoring // for DTLS packets. } else { logger.LogWarning("Unknown packet type received on RTP channel."); } } catch (Exception excp) { logger.LogError($"Exception WebRtcSession.OnRTPDataReceived {excp.Message}."); } } }
/// <summary> /// Performs a connectivity check for a single candidate pair entry. /// </summary> /// <param name="candidatePair">The candidate pair to perform a connectivity check for.</param> /// <remarks>As specified in https://tools.ietf.org/html/rfc8445#section-7.2.4.</remarks> private void DoConnectivityCheck(ChecklistEntry candidatePair) { candidatePair.State = ChecklistEntryState.InProgress; IPAddress remoteAddress = IPAddress.Parse(candidatePair.RemoteCandidate.address); IPEndPoint remoteEndPoint = new IPEndPoint(remoteAddress, candidatePair.RemoteCandidate.port); logger.LogDebug($"Sending ICE connectivity check from {_rtpChannel.RTPLocalEndPoint} to {remoteEndPoint}."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunReqBytes); //localIceCandidate.LastSTUNSendAt = DateTime.Now; }
private void RTPReceive() { try { Thread.CurrentThread.Name = "rtspsess-rtprecv"; byte[] buffer = new byte[2048]; while (!_closed) { try { EndPoint remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); //int bytesRead = _rtpSocket.Receive(buffer); int bytesRead = _rtpSocket.ReceiveFrom(buffer, ref remoteEP); IPEndPoint remoteIPEndPoint = remoteEP as IPEndPoint; //logger.Debug("RTPReceive from " + remoteEP + "."); //if (((IPEndPoint)remoteEP).Address.ToString() != _remoteEndPoint.Address.ToString()) //{ // var oldEndPoint = _remoteEndPoint; // _remoteEndPoint = remoteEP as IPEndPoint; // logger.Warn("RtspSession " + _sessionID + " switched to new remote endpoint at " + _remoteEndPoint + " (old end point " + oldEndPoint + ")."); //} if (bytesRead > 0) { _rtpLastActivityAt = DateTime.Now; if (bytesRead > RTPHeader.MIN_HEADER_LEN) { if ((buffer[0] >= 20) && (buffer[0] <= 64)) { // DTLS. if(OnDtlsReceive != null) { try { OnDtlsReceive(buffer, bytesRead, SendRTPRaw); } catch(Exception dtlsExcp) { logger.Error("Exception RTSPSession.RTPReceive DTLS. " + dtlsExcp); } } else { logger.Warn("RTSPSession.RTPReceive received a DTLS packet from " + _remoteEndPoint + "but bo DTLS handler has been set."); } } else if ((buffer[0] == 0) || (buffer[0] == 1)) { // STUN. if (_iceState != null) { try { STUNv2Message stunMessage = STUNv2Message.ParseSTUNMessage(buffer, bytesRead); //logger.Debug("STUN message received from Receiver Client @ " + stunMessage.Header.MessageType + "."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { //logger.Debug("Sending STUN response to Receiver Client @ " + remoteEndPoint + "."); STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteIPEndPoint.Address, remoteIPEndPoint.Port); byte[] stunRespBytes = stunResponse.ToByteBuffer(_iceState.SenderPassword, true); _rtpSocket.SendTo(stunRespBytes, remoteIPEndPoint); //logger.Debug("Sending Binding request to Receiver Client @ " + remoteEndPoint + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(_iceState.ReceiverUser + ":" + _iceState.SenderUser); 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.ToByteBuffer(_iceState.ReceiverPassword, true); _rtpSocket.SendTo(stunReqBytes, remoteIPEndPoint); _iceState.LastSTUNMessageReceivedAt = DateTime.Now; } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (!_iceState.IsSTUNExchangeComplete) { _iceState.IsSTUNExchangeComplete = true; logger.Debug("WebRTC client STUN exchange complete for " + remoteIPEndPoint.ToString() + " and ICE ufrag " + _iceState.ReceiverUser + "."); _remoteEndPoint = remoteIPEndPoint; } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { //logger.Warn("A STUN binding error response was received from " + remoteIPEndPoint + "."); } else { //logger.Warn("An unrecognised STUN request was received from " + remoteIPEndPoint + "."); } } catch (SocketException sockExcp) { logger.Debug("RTPSession.RTPReceive STUN processing (" + remoteIPEndPoint + "). " + sockExcp.Message); continue; } catch (Exception stunExcp) { logger.Warn("Exception RTPSession.RTPReceive STUN processing (" + remoteIPEndPoint + "). " + stunExcp); continue; } } else { //logger.Warn("A STUN reponse was received on RTP socket from " + remoteIPEndPoint + " but no ICE state was set."); } } else if ((buffer[0] >= 128) && (buffer[0] <= 191)) { if (buffer[1] == 0xC8 /* RTCP SR */ || buffer[1] == 0xC9 /* RTCP RR */) { // RTCP packet. } else { // RTP Packet. RTPPacket rtpPacket = new RTPPacket(buffer.Take(bytesRead).ToArray()); //System.Diagnostics.Debug.WriteLine("RTPReceive ssrc " + rtpPacket.Header.SyncSource + ", seq num " + rtpPacket.Header.SequenceNumber + ", timestamp " + rtpPacket.Header.Timestamp + ", marker " + rtpPacket.Header.MarkerBit + "."); //logger.Debug("RTPReceive remote " + remoteIPEndPoint + ", ssrc " + rtpPacket.Header.SyncSource + ", seq num " + rtpPacket.Header.SequenceNumber + ", timestamp " + rtpPacket.Header.Timestamp + ", bytes " + bytesRead + ", marker " + rtpPacket.Header.MarkerBit + "."); lock (_packets) { if (_packets.Count > RTP_PACKETS_MAX_QUEUE_LENGTH) { System.Diagnostics.Debug.WriteLine("RTSPSession.RTPReceive packets queue full, clearing."); logger.Warn("RTSPSession.RTPReceive packets queue full, clearing."); _packets.Clear(); if (OnRTPQueueFull != null) { OnRTPQueueFull(); } } else { _packets.Enqueue(rtpPacket); } } } } } else { logger.Warn("RTSPSession.RTPReceive an unrecognised packet was received for session ID " + SessionID + " and " + remoteIPEndPoint + "."); } } else { logger.Warn("Zero bytes read from RTSPSession RTP socket for session ID " + SessionID + " and " + remoteIPEndPoint + "."); break; } } catch (SocketException sockExcp) { if (!_closed) { _rtpSocketError = sockExcp.SocketErrorCode; if (_rtpSocketError == SocketError.Interrupted) { // If the receive has been interrupted it means the socket has been closed most likely as a result of an RTSP TEARDOWN request. if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(_sessionID); } break; } else { throw; } } } catch (Exception excp) { if (!_closed) { logger.Error("Exception RTSPSession.RTPReceive receiving. " + excp); } } } } catch (Exception excp) { if (!_closed) { logger.Error("Exception RTSPSession.RTPReceive. " + excp); if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(_sessionID); } } } }
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); } }
private void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint remoteEndPoint) { //logger.LogDebug("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. string localIcePassword = LocalIcePassword; byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(localIcePassword, true); //iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes); //iceCandidate.LastStunRequestReceivedAt = DateTime.Now; //iceCandidate.IsStunRemoteExchangeComplete = true; if (RemoteEndPoint == null) { RemoteEndPoint = remoteEndPoint; _rtpSession.DestinationEndPoint = RemoteEndPoint; _rtpSession.RtcpSession.ControlDestinationEndPoint = RemoteEndPoint; //OnIceConnected?.Invoke(iceCandidate, remoteEndPoint); IceConnectionState = IceConnectionStatesEnum.Connected; } 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("udp", remoteEndPoint.Address, remoteEndPoint.Port, IceCandidateTypesEnum.host); logger.LogDebug("Adding missing remote ICE candidate for " + remoteEndPoint + "."); _remoteIceCandidates.Add(remoteIceCandidate); // Some browsers require a STUN binding request from our end before the DTLS handshake will be initiated. // The STUN connectivity checks are already scheduled but we can speed things up by sending a binding // request immediately. SendStunConnectivityChecks(null); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { // TODO: What needs to be done here? //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.LogDebug("ICE gathering complete for local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + "."); // } // else // { // iceCandidate.IsGatheringComplete = true; // logger.LogDebug("The STUN binding response received on " + iceCandidate.RtpChannel.RTPLocalEndPoint + " 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.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", remote socket " + remoteEndPoint + "."); // SetIceConnectionState(IceConnectionStatesEnum.Connected); // } //} } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}."); } else { logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}."); } }
private void RTPReceive() { try { Thread.CurrentThread.Name = "rtpchanrecv-" + _rtpPort; byte[] buffer = new byte[2048]; while (!_isClosed) { try { int bytesRead = _rtpSocket.Receive(buffer); if (bytesRead > 0) { _rtpSocket.SendTo(buffer, bytesRead, SocketFlags.None, _wiresharkEP); _rtpLastActivityAt = DateTime.Now; if (bytesRead > RTPHeader.MIN_HEADER_LEN) { if ((buffer[0] & 0x80) == 0) { #region STUN Packet. if (_iceState != null) { try { STUNv2Message stunMessage = STUNv2Message.ParseSTUNMessage(buffer, bytesRead); //logger.Debug("STUN message received from Receiver Client @ " + stunMessage.Header.MessageType + "."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { //logger.Debug("Sending STUN response to Receiver Client @ " + remoteEndPoint + "."); STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(_remoteEndPoint.Address, _remoteEndPoint.Port); byte[] stunRespBytes = stunResponse.ToByteBuffer(_iceState.SenderPassword, true); _rtpSocket.SendTo(stunRespBytes, _remoteEndPoint); //logger.Debug("Sending Binding request to Receiver Client @ " + remoteEndPoint + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(_iceState.ReceiverUser + ":" + _iceState.SenderUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); byte[] stunReqBytes = stunRequest.ToByteBuffer(_iceState.ReceiverPassword, true); _rtpSocket.SendTo(stunReqBytes, _remoteEndPoint); _iceState.LastSTUNMessageReceivedAt = DateTime.Now; } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (!_iceState.IsSTUNExchangeComplete) { _iceState.IsSTUNExchangeComplete = true; logger.Debug("WebRTC client STUN exchange complete for " + _remoteEndPoint.ToString() + "."); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.Warn("A STUN binding error response was received from " + _remoteEndPoint + "."); } else { logger.Warn("An unrecognised STUN request was received from " + _remoteEndPoint + "."); } } catch (SocketException sockExcp) { logger.Debug("RTPChannel.RTPReceive STUN processing (" + _remoteEndPoint + "). " + sockExcp.Message); continue; } catch (Exception stunExcp) { logger.Warn("Exception RTPChannel.RTPReceive STUN processing (" + _remoteEndPoint + "). " + stunExcp); continue; } } else { logger.Warn("A STUN reponse was received on RTP socket from " + _remoteEndPoint + " but no ICE state was set."); } #endregion } else { RTPPacket rtpPacket = new RTPPacket(buffer.Take(bytesRead).ToArray()); //System.Diagnostics.Debug.WriteLine("RTPReceive ssrc " + rtpPacket.Header.SyncSource + ", seq num " + rtpPacket.Header.SequenceNumber + ", timestamp " + rtpPacket.Header.Timestamp + ", marker " + rtpPacket.Header.MarkerBit + "."); lock (_packets) { if (_packets.Count > RTP_PACKETS_MAX_QUEUE_LENGTH) { System.Diagnostics.Debug.WriteLine("RTPChannel.RTPReceive packets queue full, clearing."); logger.Warn("RTPChannel.RTPReceive packets queue full, clearing."); _packets.Clear(); if (OnRTPQueueFull != null) { OnRTPQueueFull(); } } else { _packets.Enqueue(rtpPacket); } } } } } else { logger.Warn("Zero bytes read from RTPChannel RTP socket connected to " + _remoteEndPoint + "."); //break; } } catch (SocketException sockExcp) { if (!_isClosed) { _rtpSocketError = sockExcp.SocketErrorCode; if (_rtpSocketError == SocketError.Interrupted) { // If the receive has been interrupted it means the socket has been closed. if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } break; } else { throw; } } } catch (Exception excp) { if (!_isClosed) { logger.Error("Exception RTPChannel.RTPReceive receiving. " + excp); } } } } catch (Exception excp) { if (!_isClosed) { logger.Error("Exception RTPChannel.RTPReceive. " + excp); if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } } } }
/// <summary> /// Processes a received STUN request or response. /// </summary> /// <param name="stunMessage">The STUN message received.</param> /// <param name="remoteEndPoint">The remote end point the STUN packet was received from.</param> public void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint remoteEndPoint) { remoteEndPoint = (!remoteEndPoint.Address.IsIPv4MappedToIPv6) ? remoteEndPoint : new IPEndPoint(remoteEndPoint.Address.MapToIPv4(), remoteEndPoint.Port); //logger.LogDebug($"STUN message received from remote {remoteEndPoint} {stunMessage.Header.MessageType}."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { // TODO: The integrity check method needs to be implemented (currently just returns true). bool result = stunMessage.CheckIntegrity(System.Text.Encoding.UTF8.GetBytes(LocalIcePassword), LocalIceUser, RemoteIceUser); if (!result) { // Send STUN error response. STUNv2Message stunErrResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingErrorResponse); stunErrResponse.Header.TransactionId = stunMessage.Header.TransactionId; _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunErrResponse.ToByteBuffer(null, false)); } else { var matchingCandidate = (_remoteCandidates != null) ? _remoteCandidates.Where(x => x.IsEquivalentEndPoint(RTCIceProtocol.udp, remoteEndPoint)).FirstOrDefault() : null; if (matchingCandidate == null) { // This STUN request has come from a socket not in the remote ICE candidates list. // Add a new remote peer reflexive candidate. RTCIceCandidate peerRflxCandidate = new RTCIceCandidate(new RTCIceCandidateInit()); peerRflxCandidate.SetAddressProperties(RTCIceProtocol.udp, remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.prflx, null, 0); logger.LogDebug($"Adding peer reflex ICE candidate for {remoteEndPoint}."); _remoteCandidates.Add(peerRflxCandidate); UpdateChecklist(peerRflxCandidate); matchingCandidate = peerRflxCandidate; } // Find the checklist entry for this remote candidate and update its status. var matchingChecklistEntry = _checklist.Where(x => x.RemoteCandidate.foundation == matchingCandidate.foundation).FirstOrDefault(); if (matchingChecklistEntry == null) { logger.LogWarning("ICE session STUN request matched a remote candidate but NOT a checklist entry."); } //else //{ // if (!IsController) // { // matchingChecklistEntry.State = ChecklistEntryState.Succeeded; // } //} // The UseCandidate attribute is only meant to be set by the "Controller" peer. This implementation // will accept it irrespective of the peer roles. If the remote peer wants us to use a certain remote // end point then so be it. if (stunMessage.Attributes.Any(x => x.AttributeType == STUNv2AttributeTypesEnum.UseCandidate)) { if (ConnectionState != RTCIceConnectionState.connected) { // If we are the "controlled" agent and get a "use candidate" attribute that sets the matching candidate as nominated // as per https://tools.ietf.org/html/rfc8445#section-7.3.1.5. if (matchingChecklistEntry == null) { logger.LogWarning("ICE session STUN request had UseCandidate set but no matching checklist entry was found."); } else { logger.LogDebug($"ICE session remote peer nominated entry from binding request: {matchingChecklistEntry.RemoteCandidate}"); SetNominatedEntry(matchingChecklistEntry); } } } STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); string localIcePassword = LocalIcePassword; byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(localIcePassword, true); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { // Correlate with request using transaction ID as per https://tools.ietf.org/html/rfc8445#section-7.2.5. // Actions to take on a successful STUN response https://tools.ietf.org/html/rfc8445#section-7.2.5.3 // - Discover peer reflexive remote candidates // (TODO: According to https://tools.ietf.org/html/rfc8445#section-7.2.5.3.1 peer reflexive get added to the local candidates list?) // - Construct a valid pair which means match a candidate pair in the check list and mark it as valid (since a successful STUN exchange // has now taken place on it). A new entry may need to be created for this pair since peer reflexive candidates are not added to the connectivity // check checklist. // - Update state of candidate pair that generated the check to Succeeded. // - If the controlling candidate set the USE_CANDIDATE attribute then the ICE agent that receives the successful response sets the nominated // flag of the pair to true. Once the nominated flag is set it concludes the ICE processing for that component. if (_checklistState == ChecklistState.Running) { string txID = Encoding.ASCII.GetString(stunMessage.Header.TransactionId); // Attempt to find the checklist entry for this transaction ID. var matchingChecklistEntry = _checklist.Where(x => x.RequestTransactionID == txID).FirstOrDefault(); if (matchingChecklistEntry == null) { logger.LogWarning("ICE session STUN response transaction ID did not match a checklist entry."); } else { matchingChecklistEntry.State = ChecklistEntryState.Succeeded; if (matchingChecklistEntry.Nominated) { logger.LogDebug($"ICE session remote peer nominated entry from binding response: {matchingChecklistEntry.RemoteCandidate}"); // This is the response to a connectivity check that had the "UseCandidate" attribute set. SetNominatedEntry(matchingChecklistEntry); } else if (this.IsController && !_checklist.Any(x => x.Nominated)) { // If we are the controlling ICE agent it's up to us to decide when to nominate a candidate pair to use for the connection. // To start with we'll just use whichever pair gets the first successful STUN exchange. If needs be the selection algorithm can // improve over time. matchingChecklistEntry.ChecksSent = 0; matchingChecklistEntry.LastCheckSentAt = DateTime.MinValue; matchingChecklistEntry.Nominated = true; SendConnectivityCheck(matchingChecklistEntry, true); } } } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}."); // Attempt to find the checklist entry for this transaction ID. string txID = Encoding.ASCII.GetString(stunMessage.Header.TransactionId); var matchingChecklistEntry = _checklist.Where(x => x.RequestTransactionID == txID).FirstOrDefault(); if (matchingChecklistEntry == null) { logger.LogWarning("ICE session STUN error response transaction ID did not match a checklist entry."); } else { logger.LogWarning($"ICE session check list entry set to failed: {matchingChecklistEntry.RemoteCandidate}"); matchingChecklistEntry.State = ChecklistEntryState.Failed; } } else { logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}."); } }
public void PutResponseToBufferTestMethod() { STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); //stunResponse.AddFingerPrintAttribute(); stunResponse.AddXORMappedAddressAttribute(IPAddress.Parse("127.0.0.1"), 1234); byte[] buffer = stunResponse.ToByteBuffer(null, true); }
private void SendStunConnectivityChecks(Object stateInfo) { try { lock (_stunChecksTimer) { //logger.LogDebug($"Send STUN connectivity checks, local candidates {_candidates?.Count()}, remote candidates {_remoteCandidates?.Count()}."); // 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 (RemoteIceUser != null && RemoteIcePassword != null) { if (ConnectionState == RTCIceConnectionState.connected) { // Remote RTP endpoint gets set when the DTLS negotiation is finished. if (_connectedRemoteEndPoint != null) { //logger.LogDebug("Sending STUN connectivity check to client " + iceCandidate.RemoteRtpEndPoint + "."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); 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); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, _connectedRemoteEndPoint, stunReqBytes); //_lastStunSentAt = DateTime.Now; } } else { if (_remoteCandidates.Count() > 0 && _candidates != null) { foreach (var localIceCandidate in _candidates.Where(x => x.IsStunLocalExchangeComplete == false && x.StunConnectionRequestAttempts < MAXIMUM_STUN_CONNECTION_ATTEMPTS)) { localIceCandidate.StunConnectionRequestAttempts++; // ToDo: Include srflx and relay addresses. // Only supporting UDP candidates at this stage. foreach (var remoteIceCandidate in _remoteCandidates.Where(x => x.protocol == RTCIceProtocol.udp && x.address.NotNullOrBlank() && x.HasConnectionError == false)) { try { IPAddress remoteAddress = IPAddress.Parse(remoteIceCandidate.address); logger.LogDebug($"Sending authenticated STUN binding request {localIceCandidate.StunConnectionRequestAttempts} from {_rtpChannel.RTPLocalEndPoint} to WebRTC peer at {remoteIceCandidate.address}:{remoteIceCandidate.port}."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, new IPEndPoint(IPAddress.Parse(remoteIceCandidate.address), remoteIceCandidate.port), stunReqBytes); localIceCandidate.LastSTUNSendAt = DateTime.Now; } catch (System.Net.Sockets.SocketException sockExcp) { logger.LogWarning($"SocketException sending STUN request to {remoteIceCandidate.address}:{remoteIceCandidate.port}, removing candidate. {sockExcp.Message}"); remoteIceCandidate.HasConnectionError = true; } } } } } } //if (!_closed) //{ // var interval = GetNextStunCheckInterval(STUN_CHECK_BASE_PERIOD_MILLISECONDS); // if (m_stunChecksTimer == null) // { // m_stunChecksTimer = new Timer(SendStunConnectivityChecks, null, interval, interval); // } // else // { // m_stunChecksTimer.Change(interval, interval); // } //} } } catch (Exception excp) { logger.LogError("Exception SendStunConnectivityCheck. " + excp); //m_stunChecksTimer?.Dispose(); } }
/// <summary> /// Attempts to get a list of local ICE candidates. /// </summary> //private async Task GetIceCandidatesAsync() //{ // // The media is being multiplexed so the audio and video RTP channel is the same. // var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio); // if (rtpChannel == null) // { // throw new ApplicationException("Cannot start gathering ICE candidates without an RTP channel."); // } // else // { // var localIPAddresses = _offerAddresses ?? NetServices.GetAllLocalIPAddresses(); // IceNegotiationStartedAt = DateTime.Now; // LocalIceCandidates = new List<IceCandidate>(); // foreach (var address in localIPAddresses.Where(x => x.AddressFamily == rtpChannel.RTPLocalEndPoint.AddressFamily)) // { // var iceCandidate = new IceCandidate(address, (ushort)rtpChannel.RTPPort); // if (_turnServerEndPoint != null) // { // iceCandidate.TurnServer = new TurnServer() { ServerEndPoint = _turnServerEndPoint }; // iceCandidate.InitialStunBindingCheck = SendTurnServerBindingRequest(iceCandidate); // } // LocalIceCandidates.Add(iceCandidate); // } // await Task.WhenAll(LocalIceCandidates.Where(x => x.InitialStunBindingCheck != null).Select(x => x.InitialStunBindingCheck)).ConfigureAwait(false); // } //} public void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint receivedOn) { IPEndPoint remoteEndPoint = (!receivedOn.Address.IsIPv4MappedToIPv6) ? receivedOn : new IPEndPoint(receivedOn.Address.MapToIPv4(), receivedOn.Port); //logger.LogDebug($"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. string localIcePassword = LocalIcePassword; byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(localIcePassword, true); //iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes); //iceCandidate.LastStunRequestReceivedAt = DateTime.Now; //iceCandidate.IsStunRemoteExchangeComplete = true; //if (remoteEndPoint == null) //{ //RemoteEndPoint = remoteEndPoint; //SetDestination(SDPMediaTypesEnum.audio, RemoteEndPoint, RemoteEndPoint); //OnIceConnected?.Invoke(iceCandidate, remoteEndPoint); //IceConnectionState = RTCIceConnectionState.connected; //} if (_remoteCandidates != null && !_remoteCandidates.Any(x => (x.address == remoteEndPoint.Address.ToString() || x.relatedAddress == remoteEndPoint.Address.ToString()) && (x.port == remoteEndPoint.Port || x.relatedPort == 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. // RTCIceCandidate remoteIceCandidate = new IceCandidate("udp", remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.host); RTCIceCandidate peerRflxCandidate = new RTCIceCandidate(new RTCIceCandidateInit()); peerRflxCandidate.SetAddressProperties(RTCIceProtocol.udp, remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.prflx, null, 0); logger.LogDebug($"Adding peer reflex ICE candidate for {remoteEndPoint}."); _remoteCandidates.Add(peerRflxCandidate); // Some browsers require a STUN binding request from our end before the DTLS handshake will be initiated. // The STUN connectivity checks are already scheduled but we can speed things up by sending a binding // request immediately. SendStunConnectivityChecks(null); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (ConnectionState != RTCIceConnectionState.connected) { logger.LogDebug($"ICE session setting connected remote end point to {remoteEndPoint}."); _connectedRemoteEndPoint = remoteEndPoint; ConnectionState = RTCIceConnectionState.connected; OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected); } // TODO: What needs to be done here? //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.LogDebug("ICE gathering complete for local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + "."); // } // else // { // iceCandidate.IsGatheringComplete = true; // logger.LogDebug("The STUN binding response received on " + iceCandidate.RtpChannel.RTPLocalEndPoint + " 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.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", remote socket " + remoteEndPoint + "."); // SetIceConnectionState(IceConnectionStatesEnum.Connected); // } //} } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}."); } else { logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}."); } }
private static void SendStunConnectivityChecks(UdpClient localSocket) { try { while (!m_exit) { try { foreach (var client in _webRTCClients.Where(x => x.SocketAddress != null)) { //logger.Debug("Sending STUN connectivity check to client " + client.SocketAddress + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(client.ICEUser + ":" + client.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.ToByteBuffer(client.ICEPassword, true); localSocket.Send(stunReqBytes, stunReqBytes.Length, client.SocketAddress); client.LastSTUNSendAt = DateTime.Now; //if(client.IsDtlsNegotiationComplete) //{ // // Send RTCP report. // RTCPPacket rtcp = new RTCPPacket(client.SSRC, 0, 0, 0, 0); // RTCPReport rtcpResport = new RTCPReport(Guid.NewGuid(), 0, client.SocketAddress); // var rtcpBuffer = rtcp.GetBytes(rtcpResport.GetBytes()); // var rtcpProtectedBuffer = new byte[rtcpBuffer.Length + SRTP_AUTH_KEY_LENGTH]; // Buffer.BlockCopy(rtcpBuffer, 0, rtcpProtectedBuffer, 0, rtcpBuffer.Length); // int rtperr = client.SrtpContext.ProtectRTP(rtcpBuffer, rtcpBuffer.Length - SRTP_AUTH_KEY_LENGTH); // if (rtperr != 0) // { // logger.Debug("RTCP packet protect result " + rtperr + "."); // } // else // { // localSocket.Send(rtcpProtectedBuffer, rtcpProtectedBuffer.Length, client.SocketAddress); // } //} } } catch (Exception excp) { logger.Error("Exception SendStunConnectivityCheck ConnectivityCheck. " + excp); } Thread.Sleep(2000); lock (_webRTCClients) { var expiredClients = (from cli in _webRTCClients where cli.STUNExchangeComplete && cli.IsDtlsNegotiationComplete && DateTime.Now.Subtract(cli.LastSTUNReceiveAt).TotalSeconds > EXPIRE_CLIENT_SECONDS select cli).ToList(); for (int index = 0; index < expiredClients.Count(); index++) { var expiredClient = expiredClients[index]; logger.Debug("Removed expired client " + expiredClient.SocketAddress + "."); _webRTCClients.TryTake(out expiredClient); } } } } catch (Exception excp) { logger.Error("Exception SendStunConnectivityCheck. " + excp); } }
/// <summary> /// Periodically send a STUN binding request to check connectivity. /// </summary> /// <param name="stateInfo">Not used.</param> private void SendStunConnectivityChecks(Object stateInfo) { try { //logger.LogDebug($"Send STUN connectivity checks, local candidates {LocalIceCandidates.Count()}, remote candidates {_remoteIceCandidates.Count()}."); // 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 (RemoteIceUser != null && RemoteIcePassword != null) { if (IsConnected) { // Remote RTP endpoint gets set when the DTLS negotiation is finished. if (RemoteEndPoint != null) { //logger.LogDebug("Sending STUN connectivity check to client " + iceCandidate.RemoteRtpEndPoint + "."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); 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); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, RemoteEndPoint, stunReqBytes); _lastStunSentAt = DateTime.Now; } if (_lastCommunicationAt != DateTime.MinValue) { var secondsSinceLastResponse = DateTime.Now.Subtract(_lastCommunicationAt).TotalSeconds; if (secondsSinceLastResponse > ICE_CONNECTED_NO_COMMUNICATIONS_TIMEOUT_SECONDS) { logger.LogWarning($"No packets have been received from {RemoteEndPoint} within the last {secondsSinceLastResponse:#} seconds, closing session."); Close("Inactivity timeout."); } } } 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. // Only supporting UDP candidates at this stage. foreach (var remoteIceCandidate in RemoteIceCandidates.Where(x => x.Transport.ToLower() == "udp" && x.NetworkAddress.NotNullOrBlank() && x.HasConnectionError == false)) { try { IPAddress remoteAddress = IPAddress.Parse(remoteIceCandidate.NetworkAddress); logger.LogDebug($"Sending authenticated STUN binding request {localIceCandidate.StunConnectionRequestAttempts} from {_rtpChannel.RTPLocalEndPoint} to WebRTC peer at {remoteIceCandidate.NetworkAddress}:{remoteIceCandidate.Port}."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, new IPEndPoint(IPAddress.Parse(remoteIceCandidate.NetworkAddress), remoteIceCandidate.Port), stunReqBytes); localIceCandidate.LastSTUNSendAt = DateTime.Now; } catch (System.Net.Sockets.SocketException sockExcp) { logger.LogWarning($"SocketException sending STUN request to {remoteIceCandidate.NetworkAddress}:{remoteIceCandidate.Port}, removing candidate. {sockExcp.Message}"); remoteIceCandidate.HasConnectionError = true; } } } } } } if (!IsClosed) { var interval = GetNextStunCheckInterval(STUN_CHECK_BASE_PERIOD_MILLISECONDS); if (m_stunChecksTimer == null) { m_stunChecksTimer = new Timer(SendStunConnectivityChecks, null, interval, interval); } else { m_stunChecksTimer.Change(interval, interval); } } } catch (Exception excp) { logger.LogError("Exception SendStunConnectivityCheck. " + excp); //m_stunChecksTimer?.Dispose(); } }
private static void ListenToReceiverWebRTCClient(UdpClient localSocket) { try { while (!m_exit) { try { //logger.Debug("ListenToReceiverWebRTCClient Receive."); IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] buffer = localSocket.Receive(ref remoteEndPoint); //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)) { logger.Debug("DTLS packet received " + buffer.Length + " bytes from " + remoteEndPoint.ToString() + "."); var client = _webRTCClients.Where(x => x.SocketAddress != null && x.SocketAddress.ToString() == remoteEndPoint.ToString()).SingleOrDefault(); if (client != null) { if (client.DtlsContext == null) { client.DtlsContext = new DtlsManaged(); int res = client.DtlsContext.Init(); Console.WriteLine("DtlsContext initialisation result=" + res); } int bytesWritten = client.DtlsContext.Write(buffer, buffer.Length); if (bytesWritten != buffer.Length) { logger.Warn("The required number of bytes were not successfully written to the DTLS context."); } else { byte[] dtlsOutBytes = new byte[2048]; int bytesRead = client.DtlsContext.Read(dtlsOutBytes, dtlsOutBytes.Length); if (bytesRead == 0) { Console.WriteLine("No bytes read from DTLS context :(."); } else { Console.WriteLine(bytesRead + " bytes read from DTLS context sending to " + remoteEndPoint.ToString() + "."); localSocket.Send(dtlsOutBytes, bytesRead, remoteEndPoint); //if (client.DtlsContext.IsHandshakeComplete()) if (client.DtlsContext.GetState() == 3) { Console.WriteLine("DTLS negotiation complete for " + remoteEndPoint.ToString() + "."); client.SrtpContext = new SRTPManaged(client.DtlsContext, false); client.SrtpReceiveContext = new SRTPManaged(client.DtlsContext, true); client.IsDtlsNegotiationComplete = true; } } } } } //else if ((buffer[0] & 0x80) == 0) else if ((buffer[0] == 0) || (buffer[0] == 1)) { STUNv2Message stunMessage = STUNv2Message.ParseSTUNMessage(buffer, buffer.Length); string localICEUser = null; logger.Debug("STUN message received from Receiver Client @ " + stunMessage.Header.MessageType + "."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { string stunUserAttribute = Encoding.UTF8.GetString(stunMessage.Attributes.Where(y => y.AttributeType == STUNv2AttributeTypesEnum.Username).Single().Value); if (stunUserAttribute != null && stunUserAttribute.Contains(':')) { localICEUser = stunUserAttribute.Split(':')[0]; logger.Debug("STUN binding request username " + localICEUser + "."); } var client = _webRTCClients.Where(x => (x.SocketAddress != null && x.SocketAddress.ToString() == remoteEndPoint.ToString()) || (localICEUser != null && localICEUser == x.LocalICEUser)).SingleOrDefault(); if (client != null) { if (client.SocketAddress == null) { client.SocketAddress = remoteEndPoint; logger.Debug("Set socket endpoint of WebRTC client with SDP session ID " + client.SdpSessionID + " to " + remoteEndPoint + "."); } client.LastSTUNReceiveAt = DateTime.Now; if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { //logger.Debug("Sending STUN response to Receiver Client @ " + remoteEndPoint + "."); STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); byte[] stunRespBytes = stunResponse.ToByteBuffer(client.LocalICEPassword, true); localSocket.Send(stunRespBytes, stunRespBytes.Length, remoteEndPoint); //logger.Debug("Sending Binding request to Receiver Client @ " + remoteEndPoint + "."); //if (client != null && !client.STUNExchangeComplete) if (client != null) { //client.SrtpContext = new SRTPManaged(Convert.FromBase64String(_sourceSRTPKey)); //client.IsDtlsNegotiationComplete = true; //STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); //stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); //stunRequest.AddUsernameAttribute(client.ICEUser + ":" + _senderICEUser); //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.ToByteBuffer(client.ICEPassword, true); //localSocket.Send(stunReqBytes, stunReqBytes.Length, remoteEndPoint); //client.LastSTUNMessageAt = DateTime.Now; } } } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { var client = _webRTCClients.Where(x => x.SocketAddress != null && x.SocketAddress.ToString() == remoteEndPoint.ToString()).SingleOrDefault(); if (client != null) { client.LastSTUNReceiveAt = DateTime.Now; if (client.STUNExchangeComplete == false) { client.STUNExchangeComplete = true; logger.Debug("WebRTC client STUN exchange complete for " + remoteEndPoint.ToString() + "."); } //client.IsDtlsNegotiationComplete = true; // Not using DTLS in this case. //client.SrtpContext = new SRTPManaged(Convert.FromBase64String(_sourceSRTPKey), true); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.Debug("A STUN binding error response was received from Receiver Client."); } else { logger.Debug("An unrecognised STUN request was received from Receiver Client."); } } else if ((buffer[0] >= 128) && (buffer[0] <= 191)) { //logger.Debug("A non-STUN packet was received Receiver Client."); var client = _webRTCClients.Where(x => x.SocketAddress != null && x.SocketAddress.ToString() == remoteEndPoint.ToString()).SingleOrDefault(); if (client != null) { if (buffer[1] == 0xC8 /* RTCP SR */ || buffer[1] == 0xC9 /* RTCP RR */) { // RTCP packet. client.LastSTUNReceiveAt = DateTime.Now; } else { // RTP packet. int res = client.SrtpReceiveContext.UnprotectRTP(buffer, buffer.Length); if (res != 0) { logger.Warn("SRTP unprotect failed, result " + res + "."); } } } } else { logger.Debug("An unrecognised packet was received on the WebRTC media socket."); } } catch (Exception sockExcp) { logger.Debug("ListenToReceiverWebRTCClient Receive. " + sockExcp.Message); continue; } } } catch (Exception excp) { logger.Error("Exception ListenForWebRTCClient. " + 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 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); } }