/// <summary> /// /// </summary> /// <param name="dtlsCertificateFingerprint">The SHA256 fingerprint that gets placed in the SDP offer for this WebRTC peer. It must match the certificate being used /// in the DTLS negotiation.</param> /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN server in this peer's ICE candidate gathering.</param> /// <param name="localAddress">Optional parameter to specify the local IP address to use for STUN/TRP sockets. If null all available interfaces will be used.</param> public void Initialise(string dtlsCertificateFingerprint, IPEndPoint turnServerEndPoint, List <RtpMediaTypesEnum> mediaTypes, IPAddress localAddress, bool isEncryptionDisabled) { MediaTypes = mediaTypes; _localIPAddresses = new List <IPAddress>(); _isEncryptionDisabled = isEncryptionDisabled; if (localAddress != null) { _localIPAddresses.Add(localAddress); } if (dtlsCertificateFingerprint.IsNullOrBlank() && isEncryptionDisabled == false) { throw new ArgumentNullException("dtlsCertificateFingerprint", "A DTLS certificate fingerprint must be supplied when initialising a new WebRTC peer (to get the fingerprint use: openssl x509 -fingerprint -sha256 -in server-cert.pem)."); } try { _dtlsCertificateFingerprint = dtlsCertificateFingerprint; _turnServerEndPoint = turnServerEndPoint; _iceGatheringMRE = new ManualResetEvent(false); DateTime startGatheringTime = DateTime.Now; SetIceConnectionState(IceConnectionStatesEnum.Gathering); GetIceCandidates(_iceGatheringMRE); _iceGatheringMRE.WaitOne(ICE_GATHERING_TIMEOUT_MILLISECONDS, true); logger.LogDebug("ICE gathering completed for call " + CallID + " in " + DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds + "ms, number of local sockets " + LocalIceCandidates.Count + "."); SetIceConnectionState(IceConnectionStatesEnum.GatheringComplete); if (LocalIceCandidates.Count == 0) { logger.LogWarning("No local socket candidates were found for WebRTC call " + CallID + ", closing peer."); Close(); } else { string localIceCandidateString = null; logger.LogDebug("ICE Candidates for " + CallID + ": "); foreach (var iceCandidate in GetIceCandidatesForMediaType(RtpMediaTypesEnum.None)) { localIceCandidateString += iceCandidate.ToString(); } var localIceUser = Crypto.GetRandomString(20); var localIcePassword = Crypto.GetRandomString(20) + Crypto.GetRandomString(20); var localIceCandidate = GetIceCandidatesForMediaType(RtpMediaTypesEnum.None).First(); var offerHeader = String.Format(_sdpOfferTemplate, Crypto.GetRandomInt(10).ToString()); string dtlsAttribute = (_isEncryptionDisabled == false) ? String.Format(_dtlsFingerprint, _dtlsCertificateFingerprint) : null; string rtpSecurityDescriptor = (_isEncryptionDisabled == false) ? RTP_MEDIA_SECURE_DESCRIPTOR : RTP_MEDIA_UNSECURE_DESCRIPTOR; var audioOffer = mediaTypes.Contains(RtpMediaTypesEnum.Audio) ? String.Format(_sdpAudioPcmOfferTemplate, localIceCandidate.Port, rtpSecurityDescriptor, localIceCandidate.LocalAddress, localIceCandidateString.TrimEnd(), localIceUser, localIcePassword, dtlsAttribute) : null; var videoOffer = String.Format(_sdpVideoOfferTemplate, rtpSecurityDescriptor, localIceCandidate.LocalAddress, localIceUser, localIcePassword, dtlsAttribute); string offer = offerHeader + audioOffer + videoOffer; //logger.LogDebug("WebRTC Offer SDP: " + offer); SDP = offer; LocalIceUser = localIceUser; LocalIcePassword = localIcePassword; Task.Run(() => { SendStunConnectivityChecks(); }); logger.LogDebug("Sending SDP offer for WebRTC call " + CallID + "."); OnSdpOfferReady?.Invoke(offer); } } catch (Exception excp) { logger.LogError("Exception WebRtcPeer.Initialise. " + excp); Close(); } }
/// <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> /// /// </summary> /// <param name="dtlsCertificateFingerprint">The SHA256 fingerprint that gets placed in the SDP offer for this WebRTC peer. It must match the certificate being used /// in the DTLS negotiation.</param> /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN server in this peer's ICE candidate gathering.</param> public void Initialise(string dtlsCertificateFingerprint, IPEndPoint turnServerEndPoint) { if (dtlsCertificateFingerprint.IsNullOrBlank()) { throw new ArgumentNullException("dtlsCertificateFingerprint", "A DTLS certificate fingerprint must be supplied when initialising a new WebRTC peer (to get the fingerprint use: openssl x509 -fingerprint -sha256 -in server-cert.pem)."); } try { _dtlsCertificateFingerprint = dtlsCertificateFingerprint; _turnServerEndPoint = turnServerEndPoint; _iceGatheringMRE = new ManualResetEvent(false); DateTime startGatheringTime = DateTime.Now; SetIceConnectionState(IceConnectionStatesEnum.Gathering); GetIceCandidates(_iceGatheringMRE); _iceGatheringMRE.WaitOne(ICE_GATHERING_TIMEOUT_MILLISECONDS, true); logger.Debug("ICE gathering completed for call " + CallID + " in " + DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds + "ms, number of local sockets " + LocalIceCandidates.Count + "."); SetIceConnectionState(IceConnectionStatesEnum.GatheringComplete); if (LocalIceCandidates.Count == 0) { logger.Warn("No local socket candidates were found for WebRTC call " + CallID + ", closing peer."); Close(); } else { string iceCandidateString = null; logger.Debug("ICE Candidates for " + CallID + ": "); foreach (var iceCandidate in LocalIceCandidates) { iceCandidateString += iceCandidate.ToString(); } var localIceUser = Crypto.GetRandomString(20); var localIcePassword = Crypto.GetRandomString(20) + Crypto.GetRandomString(20); var offer = String.Format(_sdpOfferTemplate, Crypto.GetRandomInt(10).ToString(), (LocalIceCandidates.First().LocalRtpSocket.LocalEndPoint as IPEndPoint).Port, LocalIceCandidates.First().LocalAddress, iceCandidateString.TrimEnd(), localIceUser, localIcePassword, _dtlsCertificateFingerprint); //logger.Debug("WebRTC Offer SDP: " + offer); SDP = offer; LocalIceUser = localIceUser; LocalIcePassword = localIcePassword; SSRC = Convert.ToUInt32(Crypto.GetRandomInt(8)); SequenceNumber = 1; Task.Run(() => { SendStunConnectivityChecks(); }); logger.Debug("Sending SDP offer for WebRTC call " + CallID + "."); OnSdpOfferReady?.Invoke(offer); } } catch (Exception excp) { logger.Error("Exception WebRtcPeer.Initialise. " + excp); Close(); } }
/// <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; } }
/// <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); } }