/// <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.LocalIPAddresses; IceNegotiationStartedAt = DateTime.Now; LocalIceCandidates = new List <IceCandidate>(); foreach (var address in localIPAddresses.Where(x => x.AddressFamily == rtpChannel.RTPLocalEndPoint.AddressFamily)) { var iceCandidate = new IceCandidate(address, 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); } }
private async Task GetIceCandidatesAsync() { 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, _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)); }
private void SendInitialStunBindingRequest(IceCandidate iceCandidate, ManualResetEvent iceGatheringCompleteMRE) { int attempt = 1; while (attempt < INITIAL_STUN_BINDING_ATTEMPTS_LIMIT && !IsClosed && !iceCandidate.IsGatheringComplete) { logger.Debug("Sending STUN binding request " + attempt + " from " + iceCandidate.LocalRtpSocket.LocalEndPoint + " to " + iceCandidate.TurnServer.ServerEndPoint + "."); STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); byte[] stunReqBytes = stunRequest.ToByteBuffer(null, false); iceCandidate.LocalRtpSocket.SendTo(stunReqBytes, iceCandidate.TurnServer.ServerEndPoint); Thread.Sleep(INITIAL_STUN_BINDING_PERIOD_MILLISECONDS); attempt++; } iceCandidate.IsGatheringComplete = true; // Potentially save a few seconds if all the ICE candidates are now ready. if (LocalIceCandidates.All(x => x.IsGatheringComplete)) { iceGatheringCompleteMRE.Set(); } }
public void AppendRemoteIceCandidate(IceCandidate remoteIceCandidate) { IPAddress candidateIPAddress = null; //foreach (var iceCandidate in remoteIceCandidates) //{ // logger.LogDebug("Appending remote ICE candidate " + iceCandidate.NetworkAddress + ":" + iceCandidate.Port + "."); //} if (remoteIceCandidate.Transport.ToLower() != "udp") { logger.LogDebug("Omitting remote non-UDP ICE candidate. " + remoteIceCandidate.RawString + "."); } else if (!IPAddress.TryParse(remoteIceCandidate.NetworkAddress, out candidateIPAddress)) { logger.LogDebug("Omitting ICE candidate with unrecognised IP Address. " + remoteIceCandidate.RawString + "."); } else if (candidateIPAddress.AddressFamily == AddressFamily.InterNetworkV6) { logger.LogDebug("Omitting IPv6 ICE candidate. " + remoteIceCandidate.RawString + "."); } else { // ToDo: Add srflx and relay endpoints as hosts as well. if (!_remoteIceCandidates.Any(x => x.NetworkAddress == remoteIceCandidate.NetworkAddress && x.Port == remoteIceCandidate.Port)) { logger.LogDebug("Adding remote ICE candidate: " + remoteIceCandidate.CandidateType + " " + remoteIceCandidate.NetworkAddress + ":" + remoteIceCandidate.Port + " (" + remoteIceCandidate.RawString + ")."); _remoteIceCandidates.Add(remoteIceCandidate); } } // We now should have a remote ICE candidate to start the STUN dance with. SendStunConnectivityChecks(null); }
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 GetIceCandidates(ManualResetEvent iceGatheringCompleteMRE) { IceNegotiationStartedAt = DateTime.Now; LocalIceCandidates = new List <IceCandidate>(); var addresses = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetUnicastAddresses() .Where(x => x.Address.AddressFamily == AddressFamily.InterNetwork && // Exclude IPv6 at this stage. IPAddress.IsLoopback(x.Address) == false && (x.Address != null && x.Address.ToString().StartsWith(AUTOMATIC_PRIVATE_ADRRESS_PREFIX) == false)); foreach (var address in addresses) { logger.Debug("Attempting to create RTP socket with IP address " + address.Address + "."); Socket rtpSocket = null; Socket controlSocket = null; NetServices.CreateRtpSocket(address.Address, WEBRTC_START_PORT, WEBRTC_END_PORT, false, out rtpSocket, out controlSocket); if (rtpSocket != null) { logger.Debug("RTP socket successfully created on " + rtpSocket.LocalEndPoint + "."); var iceCandidate = new IceCandidate() { LocalAddress = address.Address, Port = ((IPEndPoint)rtpSocket.LocalEndPoint).Port, LocalRtpSocket = rtpSocket, LocalControlSocket = controlSocket, TurnServer = (_turnServerEndPoint != null) ? new TurnServer() { ServerEndPoint = _turnServerEndPoint } : null }; LocalIceCandidates.Add(iceCandidate); var listenerTask = Task.Run(() => { StartWebRtcRtpListener(iceCandidate); }); iceCandidate.RtpListenerTask = listenerTask; if (_turnServerEndPoint != null) { var stunBindingTask = Task.Run(() => { SendInitialStunBindingRequest(iceCandidate, iceGatheringCompleteMRE); }); } else { 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 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; }
public static IceCandidate Parse(string candidateLine) { IceCandidate candidate = new IceCandidate(); candidate.RawString = candidateLine; string[] candidateFields = candidateLine.Trim().Split(' '); candidate.Transport = candidateFields[2]; candidate.NetworkAddress = candidateFields[4]; candidate.Port = Convert.ToInt32(candidateFields[5]); Enum.TryParse <IceCandidateTypesEnum>(candidateFields[7], out candidate.CandidateType); if (candidateFields.Length > 8 && candidateFields[8] == REMOTE_ADDRESS_KEY) { candidate.RemoteAddress = candidateFields[9]; } if (candidateFields.Length > 10 && candidateFields[10] == REMOTE_PORT_KEY) { candidate.RemotePort = Convert.ToInt32(candidateFields[11]); } return(candidate); }
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); } }
public void AppendRemoteIceCandidate(IceCandidate remoteIceCandidate) { IPAddress candidateIPAddress = null; //foreach (var iceCandidate in remoteIceCandidates) //{ // logger.Debug("Appending remote ICE candidate " + iceCandidate.NetworkAddress + ":" + iceCandidate.Port + "."); //} if (remoteIceCandidate.Transport.ToLower() != "udp") { logger.Debug("Omitting remote non-UDP ICE candidate. " + remoteIceCandidate.RawString + "."); } else if (!IPAddress.TryParse(remoteIceCandidate.NetworkAddress, out candidateIPAddress)) { logger.Debug("Omitting ICE candidate with unrecognised IP Address. " + remoteIceCandidate.RawString + "."); } else if (candidateIPAddress.AddressFamily == AddressFamily.InterNetworkV6) { logger.Debug("Omitting IPv6 ICE candidate. " + remoteIceCandidate.RawString + "."); } else { // ToDo: Add srflx and relay endpoints as hosts as well. if (!_remoteIceCandidates.Any(x => x.NetworkAddress == remoteIceCandidate.NetworkAddress && x.Port == remoteIceCandidate.Port)) { logger.Debug("Adding remote ICE candidate: " + remoteIceCandidate.CandidateType + " " + remoteIceCandidate.NetworkAddress + ":" + remoteIceCandidate.Port + " (" + remoteIceCandidate.RawString + ")."); _remoteIceCandidates.Add(remoteIceCandidate); } } }
public void MediaPacketReceived(IceCandidate iceCandidate, byte[] buffer, IPEndPoint remoteEndPoint) { if ((buffer[0] >= 128) && (buffer[0] <= 191)) { //logger.Debug("A non-STUN packet was received Receiver Client."); 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 { logger.Debug("An unrecognised packet was received on the WebRTC media socket."); } }
public void DtlsPacketReceived(IceCandidate iceCandidate, byte[] buffer, IPEndPoint remoteEndPoint) { logger.Debug("DTLS packet received " + buffer.Length + " bytes from " + remoteEndPoint.ToString() + "."); if (DtlsContext == null) { DtlsContext = new DtlsManaged(); int res = DtlsContext.Init(); logger.Debug("DtlsContext initialisation result=" + res); } int bytesWritten = 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 = DtlsContext.Read(dtlsOutBytes, dtlsOutBytes.Length); if (bytesRead == 0) { logger.Debug("No bytes read from DTLS context :(."); } else { logger.Debug(bytesRead + " bytes read from DTLS context sending to " + remoteEndPoint.ToString() + "."); iceCandidate.LocalRtpSocket.SendTo(dtlsOutBytes, 0, bytesRead, SocketFlags.None, remoteEndPoint); //if (client.DtlsContext.IsHandshakeComplete()) if (DtlsContext.GetState() == 3) { logger.Debug("DTLS negotiation complete for " + remoteEndPoint.ToString() + "."); SrtpContext = new SRTPManaged(DtlsContext, false); SrtpReceiveContext = new SRTPManaged(DtlsContext, true); Peer.IsDtlsNegotiationComplete = true; iceCandidate.RemoteRtpEndPoint = 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 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 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 GetIceCandidates(ManualResetEvent iceGatheringCompleteMRE) { IceNegotiationStartedAt = DateTime.Now; LocalIceCandidates = new List<IceCandidate>(); List<UnicastIPAddressInformation> addresses = new List<UnicastIPAddressInformation>(); // CAUTION: GetUnicastAddresses canj take up to 60 seconds to return if the machine has IP addresses in the IpDadStateTentative state, // such as DHCP addresses still checking for their lease. More info at: https://msdn.microsoft.com/en-us/library/windows/desktop/aa814507(v=vs.85).aspx //var addresses = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetUnicastAddresses() // .Where(x => // x.Address.AddressFamily == AddressFamily.InterNetwork && // Exclude IPv6 at this stage. // IPAddress.IsLoopback(x.Address) == false && // (x.Address != null && x.Address.ToString().StartsWith(AUTOMATIC_PRIVATE_ADRRESS_PREFIX) == false)); foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus == OperationalStatus.Up) { foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && IPAddress.IsLoopback(ip.Address) == false && ip.IsTransient == false) { //Console.WriteLine(ip.Address.ToString()); addresses.Add(ip); } } } } foreach (var address in addresses) { logger.Debug("Attempting to create RTP socket with IP address " + address.Address + "."); Socket rtpSocket = null; Socket controlSocket = null; NetServices.CreateRtpSocket(address.Address, WEBRTC_START_PORT, WEBRTC_END_PORT, false, out rtpSocket, out controlSocket); if (rtpSocket != null) { logger.Debug("RTP socket successfully created on " + rtpSocket.LocalEndPoint + "."); var iceCandidate = new IceCandidate() { LocalAddress = address.Address, Port = ((IPEndPoint)rtpSocket.LocalEndPoint).Port, LocalRtpSocket = rtpSocket, LocalControlSocket = controlSocket, TurnServer = (_turnServerEndPoint != null) ? new TurnServer() { ServerEndPoint = _turnServerEndPoint } : null }; LocalIceCandidates.Add(iceCandidate); var listenerTask = Task.Run(() => { StartWebRtcRtpListener(iceCandidate); }); iceCandidate.RtpListenerTask = listenerTask; if (_turnServerEndPoint != null) { var stunBindingTask = Task.Run(() => { SendInitialStunBindingRequest(iceCandidate, iceGatheringCompleteMRE); }); } else { 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 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 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}."); } }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); sdp.m_rawSdp = sdpDescription; SDPMediaAnnouncement activeAnnouncement = null; string[] sdpLines = Regex.Split(sdpDescription, CRLF); foreach (string sdpLine in sdpLines) { string sdpLineTrimmed = sdpLine.Trim(); if (sdpLineTrimmed.StartsWith("v=")) { if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version)) { logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + "."); } } else if (sdpLineTrimmed.StartsWith("o=")) { string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' '); sdp.Username = ownerFields[0]; sdp.SessionId = ownerFields[1]; Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion); sdp.NetworkType = ownerFields[3]; sdp.AddressType = ownerFields[4]; sdp.AddressOrHost = ownerFields[5]; } else if (sdpLineTrimmed.StartsWith("s=")) { sdp.SessionName = sdpLineTrimmed.Substring(2); } else if (sdpLineTrimmed.StartsWith("c=")) { if (sdp.Connection == null) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } if (activeAnnouncement != null) { activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } } else if (sdpLineTrimmed.StartsWith("b=")) { if (activeAnnouncement != null) { activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } else { sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } } else if (sdpLineTrimmed.StartsWith("t=")) { sdp.Timing = sdpLineTrimmed.Substring(2); } else if (sdpLineTrimmed.StartsWith("m=")) { Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$"); if (mediaMatch.Success) { SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port); announcement.Transport = mediaMatch.Result("${transport}"); announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); sdp.Media.Add(announcement); activeAnnouncement = announcement; } else { logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + "."); } } else if (sdpLineTrimmed.StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX)) { sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else if (sdpLineTrimmed.StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX)) { sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); } } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatParameterAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); } } else if (sdpLineTrimmed.StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX)) { if (sdp.IceCandidates == null) { sdp.IceCandidates = new List <IceCandidate>(); } sdp.IceCandidates.Add(IceCandidate.Parse(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1))); } //2018-12-21 rj2: add a=crypto else if (sdpLineTrimmed.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { try { activeAnnouncement.AddCryptoLine(sdpLineTrimmed); } catch (FormatException fex) { logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex); } } } else if (MediaStreamStatusType.IsMediaStreamStatusAttribute(sdpLine.Trim(), out var mediaStreamStatus)) { if (activeAnnouncement != null) { activeAnnouncement.MediaStreamStatus = mediaStreamStatus; } else { sdp.SessionMediaStreamStatus = mediaStreamStatus; } } else { if (activeAnnouncement != null) { activeAnnouncement.AddExtra(sdpLineTrimmed); } else { sdp.AddExtra(sdpLineTrimmed); } } } return(sdp); } else { return(null); } } catch (Exception excp) { logger.LogError("Exception ParseSDPDescription. " + excp.Message); throw excp; } }
public static IceCandidate Parse(string candidateLine) { IceCandidate candidate = new IceCandidate(); candidate.RawString = candidateLine; string[] candidateFields = candidateLine.Trim().Split(' '); candidate.Transport = candidateFields[2]; candidate.NetworkAddress = candidateFields[4]; candidate.Port = Convert.ToInt32(candidateFields[5]); Enum.TryParse<IceCandidateTypesEnum>(candidateFields[7], out candidate.CandidateType); if (candidateFields.Length > 8 && candidateFields[8] == REMOTE_ADDRESS_KEY) { candidate.RemoteAddress = candidateFields[9]; } if (candidateFields.Length > 10 && candidateFields[10] == REMOTE_PORT_KEY) { candidate.RemotePort = Convert.ToInt32(candidateFields[11]); } return candidate; }
private void GetIceCandidates(ManualResetEvent iceGatheringCompleteMRE) { IceNegotiationStartedAt = DateTime.Now; LocalIceCandidates = new List <IceCandidate>(); List <UnicastIPAddressInformation> addresses = new List <UnicastIPAddressInformation>(); // CAUTION: GetUnicastAddresses canj take up to 60 seconds to return if the machine has IP addresses in the IpDadStateTentative state, // such as DHCP addresses still checking for their lease. More info at: https://msdn.microsoft.com/en-us/library/windows/desktop/aa814507(v=vs.85).aspx //var addresses = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetUnicastAddresses() // .Where(x => // x.Address.AddressFamily == AddressFamily.InterNetwork && // Exclude IPv6 at this stage. // IPAddress.IsLoopback(x.Address) == false && // (x.Address != null && x.Address.ToString().StartsWith(AUTOMATIC_PRIVATE_ADRRESS_PREFIX) == false)); foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus == OperationalStatus.Up) { foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && IPAddress.IsLoopback(ip.Address) == false && ip.IsTransient == false) { //Console.WriteLine(ip.Address.ToString()); addresses.Add(ip); } } } } foreach (var address in addresses) { logger.Debug("Attempting to create RTP socket with IP address " + address.Address + "."); Socket rtpSocket = null; Socket controlSocket = null; NetServices.CreateRtpSocket(address.Address, WEBRTC_START_PORT, WEBRTC_END_PORT, false, out rtpSocket, out controlSocket); if (rtpSocket != null) { logger.Debug("RTP socket successfully created on " + rtpSocket.LocalEndPoint + "."); var iceCandidate = new IceCandidate() { LocalAddress = address.Address, Port = ((IPEndPoint)rtpSocket.LocalEndPoint).Port, LocalRtpSocket = rtpSocket, LocalControlSocket = controlSocket, TurnServer = (_turnServerEndPoint != null) ? new TurnServer() { ServerEndPoint = _turnServerEndPoint } : null }; LocalIceCandidates.Add(iceCandidate); var listenerTask = Task.Run(() => { StartWebRtcRtpListener(iceCandidate); }); iceCandidate.RtpListenerTask = listenerTask; if (_turnServerEndPoint != null) { var stunBindingTask = Task.Run(() => { SendInitialStunBindingRequest(iceCandidate, iceGatheringCompleteMRE); }); } else { iceCandidate.IsGatheringComplete = true; // Potentially save a few seconds if all the ICE candidates are now ready. if (LocalIceCandidates.All(x => x.IsGatheringComplete)) { iceGatheringCompleteMRE.Set(); } } } } }