public void Close(string reason) { IsClosed = true; IceConnectionState = IceConnectionStatesEnum.Closed; m_stunChecksTimer.Dispose(); _rtpSession.CloseSession(reason); OnClose?.Invoke(reason); }
/// <summary> /// Close the session including the underlying RTP session and channels. /// </summary> /// <param name="reason">An optional descriptive reason for the closure.</param> public void Close(string reason) { if (!IsClosed) { IceConnectionState = IceConnectionStatesEnum.Closed; m_stunChecksTimer.Dispose(); CloseSession(reason); OnClose?.Invoke(reason); } }
private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState) { try { IceConnectionState = iceConnectionState; OnIceStateChange?.Invoke(iceConnectionState); } catch (Exception excp) { logger.Error("Exception SetIceConnectionState. " + excp); } }
private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState) { try { IceConnectionState = iceConnectionState; if (OnIceStateChange != null) { OnIceStateChange(iceConnectionState); } } catch (Exception excp) { logger.LogError("Exception SetIceConnectionState. " + 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}."); } }
/// <summary> /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session /// is ready for to exchange encrypted RTP and RTCP packets. /// </summary> /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN /// server in this session's ICE candidate gathering.</param> public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint) { try { _doDtlsHandshake = doDtlsHandshake; _turnServerEndPoint = turnServerEndPoint; DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync(); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { logger.LogWarning("No local socket candidates were found for WebRTC call closing."); Close("No local ICE candidates available."); } else { string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } LocalIceUser = LocalIceUser ?? Crypto.GetRandomString(20); LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20); //var localIceCandidate = GetIceCandidatesForMediaType(RtpMediaTypesEnum.None).First(); var offerHeader = String.Format(_sdpOfferTemplate, Crypto.GetRandomInt(10).ToString()); string dtlsAttribute = String.Format(_dtlsFingerprint, _dtlsCertificateFingerprint); string rtpSecurityDescriptor = RTP_MEDIA_SECURE_DESCRIPTOR; var audioOffer = String.Format(_sdpAudioPcmOfferTemplate, _rtpChannel.RTPPort, rtpSecurityDescriptor, IPAddress.Loopback, localIceCandidateString.TrimEnd(), LocalIceUser, LocalIcePassword, dtlsAttribute); var videoOffer = String.Format(_sdpVideoOfferTemplate, rtpSecurityDescriptor, IPAddress.Loopback, LocalIceUser, LocalIcePassword, dtlsAttribute); string offer = offerHeader + audioOffer + videoOffer; //logger.LogDebug("WebRTC Offer SDP: " + offer); SDP = offer; OnSdpOfferReady?.Invoke(offer); } // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check. // If there are no remote candidates this call will end up being a NOP. SendStunConnectivityChecks(null); if (_doDtlsHandshake != null) { _ = Task.Run(() => { int result = _doDtlsHandshake(this, _rtpChannel.m_rtpSocket, out _rtpSession.SrtpProtect, out _rtpSession.RtcpSession.SrtcpProtect); IsDtlsNegotiationComplete = (result == 0); }); } } catch (Exception excp) { logger.LogError("Exception WebRtcPeer.Initialise. " + excp); Close(excp.Message); } }
/// <summary> /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending /// on whether it's being used in an offer or an answer. /// </summary> private async Task <SDP> createBaseSdp() { DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync().ConfigureAwait(false); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { //logger.LogWarning("No local socket candidates were found for WebRTC call closing."); //Close("No local ICE candidates available."); throw new ApplicationException("No local ICE candidates available."); } else { SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = LocalSdpSessionID; bool haveIceCandidatesBeenAdded = false; string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (AudioLocalTrack != null && VideoLocalTrack != null) { offerSdp.Group = MEDIA_GROUPING; } // The media is being multiplexed so the audio and video RTP channel is the same. var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio); // --- Audio announcement --- if (AudioLocalTrack != null) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, rtpChannel.RTPPort, AudioLocalTrack.Capabilties); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.MediaStreamStatus = AudioLocalTrack.Transceiver.Direction; audioAnnouncement.MediaID = AudioLocalTrack.Transceiver.MID; offerSdp.Media.Add(audioAnnouncement); } // --- Video announcement --- if (VideoLocalTrack != null) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, rtpChannel.RTPPort, VideoLocalTrack.Capabilties); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.MediaStreamStatus = VideoLocalTrack.Transceiver.Direction; videoAnnouncement.MediaID = VideoLocalTrack.Transceiver.MID; offerSdp.Media.Add(videoAnnouncement); } return(offerSdp); } }
/// <summary> /// Generates the SDP for an offer or answer. /// </summary> private async Task <SDP> getSdp(string setupAttribute) { try { DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync(); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { //logger.LogWarning("No local socket candidates were found for WebRTC call closing."); //Close("No local ICE candidates available."); throw new ApplicationException("No local ICE candidates available."); } else { bool haveIceCandidatesBeenAdded = false; string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } LocalIceUser = LocalIceUser ?? Crypto.GetRandomString(20); LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20); SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = Crypto.GetRandomInt(5).ToString(); // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (RtpSession.AudioTrack != null && RtpSession.VideoTrack != null) { offerSdp.Group = MEDIA_GROUPING; } // --- Audio announcement --- if (RtpSession.AudioTrack != null) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, _rtpChannel.RTPPort, RtpSession.AudioTrack.Capabilties); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.AddExtra(setupAttribute); audioAnnouncement.MediaStreamStatus = RtpSession.AudioTrack.Transceiver.Direction; audioAnnouncement.MediaID = RtpSession.AudioTrack.Transceiver.MID; offerSdp.Media.Add(audioAnnouncement); } // --- Video announcement --- if (RtpSession.VideoTrack != null) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, _rtpChannel.RTPPort, RtpSession.VideoTrack.Capabilties); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.AddExtra(setupAttribute); videoAnnouncement.MediaStreamStatus = RtpSession.VideoTrack.Transceiver.Direction; videoAnnouncement.MediaID = RtpSession.VideoTrack.Transceiver.MID;; offerSdp.Media.Add(videoAnnouncement); } OnSdpOfferReady?.Invoke(offerSdp); return(offerSdp); } // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check. // If there are no remote candidates this call will end up being a NOP. //SendStunConnectivityChecks(null); //if (_doDtlsHandshake != null) //{ // _ = Task.Run(() => // { // int result = _doDtlsHandshake(this); // IsDtlsNegotiationComplete = (result == 0); // }); //} } catch (Exception excp) { logger.LogError("Exception getSdp. " + excp); Close(excp.Message); throw; } }
private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState) { try { IceConnectionState = iceConnectionState; if (OnIceStateChange != null) { OnIceStateChange(iceConnectionState); } } catch (Exception excp) { logger.Error("Exception SetIceConnectionState. " + excp); } }
/// <summary> /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session /// is ready for to exchange encrypted RTP and RTCP packets. /// </summary> /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN /// server in this session's ICE candidate gathering.</param> public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint) { try { _doDtlsHandshake = doDtlsHandshake; _turnServerEndPoint = turnServerEndPoint; DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync(); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { logger.LogWarning("No local socket candidates were found for WebRTC call closing."); Close("No local ICE candidates available."); } else { bool includeAudioOffer = _supportedAudioFormats?.Count() > 0; bool includeVideoOffer = _supportedVideoFormats?.Count() > 0; bool haveIceCandidatesBeenAdded = false; bool isMediaBundle = includeAudioOffer && includeVideoOffer; // Is this SDP offer bundling audio and video on the same RTP connection. string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } LocalIceUser = LocalIceUser ?? Crypto.GetRandomString(20); LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20); SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = Crypto.GetRandomInt(5).ToString(); // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (isMediaBundle) { offerSdp.Group = MEDIA_GROUPING; } if (includeAudioOffer) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, _rtpChannel.RTPPort, _supportedAudioFormats); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.AddExtra(SETUP_ATTRIBUTE); audioAnnouncement.MediaStreamStatus = AudioStreamStatus; if (isMediaBundle) { audioAnnouncement.MediaID = AUDIO_MEDIA_ID; } offerSdp.Media.Add(audioAnnouncement); } if (includeVideoOffer) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, _rtpChannel.RTPPort, _supportedVideoFormats); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.AddExtra(SETUP_ATTRIBUTE); videoAnnouncement.MediaStreamStatus = VideoStreamStatus; if (isMediaBundle) { videoAnnouncement.MediaID = VIDEO_MEDIA_ID; } offerSdp.Media.Add(videoAnnouncement); } SDP = offerSdp; OnSdpOfferReady?.Invoke(SDP); } // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check. // If there are no remote candidates this call will end up being a NOP. SendStunConnectivityChecks(null); if (_doDtlsHandshake != null) { _ = Task.Run(() => { int result = _doDtlsHandshake(this); IsDtlsNegotiationComplete = (result == 0); }); } } catch (Exception excp) { logger.LogError("Exception WebRtcPeer.Initialise. " + excp); Close(excp.Message); } }