/// <summary> /// Sends the RTCP report to the remote call party. /// </summary> /// <param name="report">RTCP report to send.</param> private void SendRtcpReport(RTCPCompoundPacket report) { var reportBytes = report.GetBytes(); var sendOnSocket = (m_isMultiplexed) ? RTPChannelSocketsEnum.RTP : RTPChannelSocketsEnum.Control; if (SrtcpProtect == null) { m_rtpChannel.SendAsync(sendOnSocket, ControlDestinationEndPoint, reportBytes); } else { byte[] sendBuffer = new byte[reportBytes.Length + SRTP_AUTH_KEY_LENGTH]; Buffer.BlockCopy(reportBytes, 0, sendBuffer, 0, reportBytes.Length); int rtperr = SrtcpProtect(sendBuffer, sendBuffer.Length - SRTP_AUTH_KEY_LENGTH); if (rtperr != 0) { logger.LogWarning("SRTP RTCP packet protection failed, result " + rtperr + "."); } else { m_rtpChannel.SendAsync(sendOnSocket, ControlDestinationEndPoint, sendBuffer); } } }
/// <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); }
/// <summary> /// Does the actual sending of an RTP packet using the specified data nad header values. /// </summary> /// <param name="rtpChannel">The RTP channel to send from.</param> /// <param name="dstRtpSocket">Destination to send to.</param> /// <param name="data">The RTP packet payload.</param> /// <param name="timestamp">The RTP header timestamp.</param> /// <param name="markerBit">The RTP header marker bit.</param> /// <param name="payloadType">The RTP header payload type.</param> private void SendRtpPacket(RTPChannel rtpChannel, IPEndPoint dstRtpSocket, byte[] data, uint timestamp, int markerBit, int payloadType) { int srtpProtectionLength = (SrtpProtect != null) ? SRTP_AUTH_KEY_LENGTH : 0; RTPPacket rtpPacket = new RTPPacket(data.Length + srtpProtectionLength); rtpPacket.Header.SyncSource = Ssrc; rtpPacket.Header.SequenceNumber = SeqNum; rtpPacket.Header.Timestamp = timestamp; rtpPacket.Header.MarkerBit = markerBit; rtpPacket.Header.PayloadType = payloadType; Buffer.BlockCopy(data, 0, rtpPacket.Payload, 0, data.Length); OnRtpPacketSent?.Invoke(rtpPacket); var rtpBuffer = rtpPacket.GetBytes(); int rtperr = SrtpProtect == null ? 0 : SrtpProtect(rtpBuffer, rtpBuffer.Length - srtpProtectionLength); if (rtperr != 0) { logger.LogError("SendDtmfEvent SRTP packet protection failed, result " + rtperr + "."); } else { rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, dstRtpSocket, rtpBuffer); } }
/// <summary> /// Does the actual sending of an RTP packet using the specified data and header values. /// </summary> /// <param name="rtpChannel">The RTP channel to send from.</param> /// <param name="dstRtpSocket">Destination to send to.</param> /// <param name="data">The RTP packet payload.</param> /// <param name="timestamp">The RTP header timestamp.</param> /// <param name="markerBit">The RTP header marker bit.</param> /// <param name="payloadType">The RTP header payload type.</param> private void SendRtpPacket(RTPChannel rtpChannel, IPEndPoint dstRtpSocket, byte[] data, uint timestamp, int markerBit, int payloadType, uint ssrc, ushort seqNum, RTCPSession rtcpSession) { if (IsSecure && !IsSecureContextReady) { logger.LogWarning("SendRtpPacket cannot be called on a secure session before calling SetSecurityContext."); } else { int srtpProtectionLength = (m_srtpProtect != null) ? SRTP_MAX_PREFIX_LENGTH : 0; RTPPacket rtpPacket = new RTPPacket(data.Length + srtpProtectionLength); rtpPacket.Header.SyncSource = ssrc; rtpPacket.Header.SequenceNumber = seqNum; rtpPacket.Header.Timestamp = timestamp; rtpPacket.Header.MarkerBit = markerBit; rtpPacket.Header.PayloadType = payloadType; Buffer.BlockCopy(data, 0, rtpPacket.Payload, 0, data.Length); var rtpBuffer = rtpPacket.GetBytes(); if (m_srtpProtect == null) { rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, dstRtpSocket, rtpBuffer); } else { int outBufLen = 0; int rtperr = m_srtpProtect(rtpBuffer, rtpBuffer.Length - srtpProtectionLength, out outBufLen); if (rtperr != 0) { logger.LogError("SendRTPPacket protection failed, result " + rtperr + "."); } else { rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, dstRtpSocket, rtpBuffer.Take(outBufLen).ToArray()); } } m_lastRtpTimestamp = timestamp; rtcpSession?.RecordRtpPacketSend(rtpPacket); } }
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> /// 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}."); } }