Пример #1
0
        /// <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);
        }
Пример #2
0
 /// <summary>
 /// Sets the nominated checklist entry. This action completes the checklist processing and
 /// indicates the connection checks were successful.
 /// </summary>
 /// <param name="entry">The checklist entry that was nominated.</param>
 private void SetNominatedEntry(ChecklistEntry entry)
 {
     entry.Nominated    = true;
     _checklistState    = ChecklistState.Completed;
     NominatedCandidate = entry.RemoteCandidate;
     ConnectionState    = RTCIceConnectionState.connected;
     OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected);
 }
Пример #3
0
        /// <summary>
        /// Updates the checklist with new candidate pairs.
        /// </summary>
        /// <remarks>
        /// From https://tools.ietf.org/html/rfc8445#section-6.1.2.2:
        /// IPv6 link-local addresses MUST NOT be paired with other than link-local addresses.
        /// </remarks>
        private void UpdateChecklist(RTCIceCandidate remoteCandidate)
        {
            lock (_checklist)
            {
                // Local server reflexive candidates don't get added to the checklist since they are just local
                // "host" candidates with an extra NAT address mapping. The NAT address mapping is needed for the
                // remote ICE peer but locally a server reflexive candidate is always going to be represented by
                // a "host" candidate.

                foreach (var localCandidate in Candidates.Where(x => x.type != RTCIceCandidateType.srflx))
                {
                    if (localCandidate.CandidateAddress != null && remoteCandidate.CandidateAddress != null &&
                        localCandidate.CandidateAddress.AddressFamily == remoteCandidate.CandidateAddress.AddressFamily)
                    {
                        if (remoteCandidate.CandidateAddress.IsIPv6LinkLocal)
                        {
                            if (localCandidate.CandidateAddress.IsIPv6LinkLocal)
                            {
                                // Only pair IPv6 link local candidates if both are link local.
                                ChecklistEntry entry = new ChecklistEntry(localCandidate, remoteCandidate, IsController);

                                // Because only ONE checklist is currently supported each candidate pair can be set to
                                // a "waiting" state. If an additional checklist is ever added then only one candidate
                                // pair with the same foundation should be set to waiting across all checklists.
                                // See https://tools.ietf.org/html/rfc8445#section-6.1.2.6 for a somewhat convoluted
                                // explanation and example.
                                entry.State = ChecklistEntryState.Waiting;

                                _checklist.Add(entry);
                            }
                        }
                        else
                        {
                            ChecklistEntry entry = new ChecklistEntry(localCandidate, remoteCandidate, IsController);
                            // See comment above about why the candidate state is adjusted.
                            entry.State = ChecklistEntryState.Waiting;
                            _checklist.Add(entry);
                        }
                    }
                }

                // Finally sort the checklist to put it in priority order and if necessary remove lower
                // priority pairs.
                _checklist.Sort();

                while (_checklist.Count > MAX_CHECKLIST_ENTRIES)
                {
                    _checklist.RemoveAt(_checklist.Count - 1);
                }
            }
        }
Пример #4
0
        /// <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;
        }
Пример #5
0
        /// <summary>
        /// Updates the checklist with new candidate pairs.
        /// </summary>
        /// <remarks>
        /// From https://tools.ietf.org/html/rfc8445#section-6.1.2.2:
        /// IPv6 link-local addresses MUST NOT be paired with other than link-local addresses.
        /// </remarks>
        private void UpdateChecklist(RTCIceCandidate remoteCandidate)
        {
            lock (_checklist)
            {
                // TODO: Check for duplicate entries and adjust reflexive local candidates to use the base address
                // as per https://tools.ietf.org/html/rfc8445#section-6.1.2.4.

                foreach (var localCandidate in Candidates)
                {
                    if (localCandidate.CandidateAddress != null && remoteCandidate.CandidateAddress != null &&
                        localCandidate.CandidateAddress.AddressFamily == remoteCandidate.CandidateAddress.AddressFamily)
                    {
                        if (remoteCandidate.CandidateAddress.IsIPv6LinkLocal)
                        {
                            if (localCandidate.CandidateAddress.IsIPv6LinkLocal)
                            {
                                // Only pair IPv6 link local candidates if both are link local.
                                ChecklistEntry entry = new ChecklistEntry(localCandidate, remoteCandidate, IsController);
                                _checklist.Add(entry);
                                _checklist.Sort();
                            }
                        }
                        else
                        {
                            ChecklistEntry entry = new ChecklistEntry(localCandidate, remoteCandidate, IsController);
                            _checklist.Add(entry);
                            _checklist.Sort();
                        }
                    }
                }

                while (_checklist.Count > MAX_CHECKLIST_ENTRIES)
                {
                    _checklist.RemoveAt(_checklist.Count - 1);
                }
            }
        }
Пример #6
0
        /// <summary>
        /// Updates the checklist with new candidate pairs.
        /// </summary>
        /// <remarks>
        /// From https://tools.ietf.org/html/rfc8445#section-6.1.2.2:
        /// IPv6 link-local addresses MUST NOT be paired with other than link-local addresses.
        /// </remarks>
        private void UpdateChecklist(RTCIceCandidate remoteCandidate)
        {
            lock (_checklist)
            {
                // Local server reflexive candidates don't get added to the checklist since they are just local
                // "host" candidates with an extra NAT address mapping. The NAT address mapping is needed for the
                // remote ICE peer but locally a server reflexive candidate is always going to be represented by
                // a "host" candidate.

                bool supportsIPv4 = _rtpChannel.RtpSocket.AddressFamily == AddressFamily.InterNetwork || _rtpChannel.IsDualMode;
                bool supportsIPv6 = _rtpChannel.RtpSocket.AddressFamily == AddressFamily.InterNetworkV6 || _rtpChannel.IsDualMode;

                if (remoteCandidate.addressFamily == AddressFamily.InterNetwork && supportsIPv4 ||
                    remoteCandidate.addressFamily == AddressFamily.InterNetworkV6 && supportsIPv6)
                {
                    ChecklistEntry entry = new ChecklistEntry(_localChecklistCandidate, remoteCandidate, IsController);

                    // Because only ONE checklist is currently supported each candidate pair can be set to
                    // a "waiting" state. If an additional checklist is ever added then only one candidate
                    // pair with the same foundation should be set to waiting across all checklists.
                    // See https://tools.ietf.org/html/rfc8445#section-6.1.2.6 for a somewhat convoluted
                    // explanation and example.
                    entry.State = ChecklistEntryState.Waiting;

                    AddChecklistEntry(entry);
                }

                // Finally sort the checklist to put it in priority order and if necessary remove lower
                // priority pairs.
                _checklist.Sort();

                while (_checklist.Count > MAX_CHECKLIST_ENTRIES)
                {
                    _checklist.RemoveAt(_checklist.Count - 1);
                }
            }
        }
Пример #7
0
        /// <summary>
        /// Attempts to add a checklist entry. If there is already an equivalent entry in the checklist
        /// the entry may not be added or may replace an existing entry.
        /// </summary>
        /// <param name="entry">The new entry to attempt to add to the checklist.</param>
        private void AddChecklistEntry(ChecklistEntry entry)
        {
            // Check if there is already an entry that matches the remote candidate.
            // Note: The implementation in this class relies binding the socket used for all
            // local candidates on a SINGLE address (typically 0.0.0.0 or [::]). Consequently
            // there is no need to check the local candidate when determining duplicates. As long
            // as there is one checklist entry with each remote candidate the connectivity check will
            // work. To put it another way the local candidate information is not used on the
            // "Nominated" pair.

            var entryRemoteEP = entry.RemoteCandidate.GetEndPoint();

            var existingEntry = _checklist.Where(x => x.RemoteCandidate.GetEndPoint().Address.Equals(entryRemoteEP.Address) &&
                                                 x.RemoteCandidate.GetEndPoint().Port == entryRemoteEP.Port &&
                                                 x.RemoteCandidate.protocol == entry.RemoteCandidate.protocol).SingleOrDefault();

            if (existingEntry != null)
            {
                if (entry.Priority > existingEntry.Priority)
                {
                    logger.LogDebug($"Removing lower priority entry and adding candidate pair to checklist for: {entry.RemoteCandidate}");
                    _checklist.Remove(existingEntry);
                    _checklist.Add(entry);
                }
                else
                {
                    logger.LogDebug($"Existing checklist entry has higher priority, NOT adding entry for: {entry.RemoteCandidate}");
                }
            }
            else
            {
                // No existing entry.
                logger.LogDebug($"Adding new candidate pair to checklist for: {entry.RemoteCandidate}");
                _checklist.Add(entry);
            }
        }