All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lastbamboo.common.ice.IceCheckListImpl Maven / Gradle / Ivy

package org.lastbamboo.common.ice;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.lastbamboo.common.ice.candidate.IceCandidate;
import org.lastbamboo.common.ice.candidate.IceCandidatePair;
import org.lastbamboo.common.ice.candidate.IceCandidatePairFactory;
import org.lastbamboo.common.ice.candidate.IceCandidatePairPriorityCalculator;
import org.lastbamboo.common.ice.candidate.IceCandidatePairState;
import org.lastbamboo.common.ice.candidate.IceCandidateVisitor;
import org.lastbamboo.common.ice.candidate.IceTcpActiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpHostPassiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpPeerReflexiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpRelayPassiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpServerReflexiveSoCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpHostCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpPeerReflexiveCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpRelayCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpServerReflexiveCandidate;
import org.lastbamboo.common.offer.answer.IceConfig;
import org.littleshoot.util.Closure;
import org.littleshoot.util.CollectionUtils;
import org.littleshoot.util.CollectionUtilsImpl;
import org.littleshoot.util.Pair;
import org.littleshoot.util.PairImpl;
import org.littleshoot.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class containing data and state for an ICE check list.

* * See: http://tools.ietf.org/html/draft-ietf-mmusic-ice-17#section-5.7 */ public class IceCheckListImpl implements IceCheckList { private final Logger m_log = LoggerFactory.getLogger(getClass()); /** * The triggered check queue. This is a FIFO queue of checks that the * course of the connectivity check process "triggers", typically through * the discovery of new peer reflexive candidates. */ private final Queue m_triggeredQueue = new ConcurrentLinkedQueue(); private final List m_pairs = new ArrayList(); private volatile IceCheckListState m_state = IceCheckListState.RUNNING; private final Collection m_localCandidates; private final IceCandidatePairFactory m_iceCandidatePairFactory; private final Collection allPairs = new HashSet(); /** * Creates a new check list, starting with only local candidates. * * @param candidatePairFactory Factory for creating candidate pairs. * @param localCandidates The local candidates to use in the check list. */ public IceCheckListImpl(final IceCandidatePairFactory candidatePairFactory, final Collection localCandidates) { this.m_iceCandidatePairFactory = candidatePairFactory; this.m_localCandidates = localCandidates; m_log.debug("Using local candidates: {}", localCandidates); } public IceCandidatePair removeTopTriggeredPair() { while (!this.m_triggeredQueue.isEmpty()) { final IceCandidatePair pair = this.m_triggeredQueue.poll(); // Don't re-check nominated pairs. if (!pair.isNominated()) { return pair; } } return null; } public void setState(final IceCheckListState state) { if (this.m_state != IceCheckListState.COMPLETED) { this.m_state = state; synchronized (this) { m_log.debug("State changed to: {}", state); this.notifyAll(); } } } public IceCheckListState getState() { return this.m_state; } public void check() { synchronized (this) { while (this.m_state == IceCheckListState.RUNNING) { try { // This doesn't wait forever simply as a fail safe to // avoid blocking the thread indefinitely if something // goes wrong. wait(60000); } catch (final InterruptedException e) { m_log.error("Interrupted??", e); } } } m_log.debug("Returning from check"); } public boolean isActive() { // TODO: I believe this should depend on the state of the check list. // The active state is used in determining the value of N in timer // computations. return false; } public void addTriggeredPair(final IceCandidatePair pair) { synchronized (this) { if (!this.m_triggeredQueue.contains(pair)) { m_log.debug("Adding triggered pair:{}", pair); this.m_triggeredQueue.add(pair); this.allPairs.add(pair); } else { m_log.debug("Triggered queue already has pair:{}", pair); } } } public void addPair(final IceCandidatePair pair) { if (pair == null) { m_log.error("Null pair"); throw new NullPointerException("Null pair"); } synchronized (this) { this.m_pairs.add(pair); this.allPairs.add(pair); Collections.sort(this.m_pairs); } } public void recomputePairPriorities(final boolean controlling) { synchronized (this) { recompute(this.m_triggeredQueue, controlling); recompute(this.m_pairs, controlling); sortPairs(this.m_pairs); } } private void recompute(final Collection pairs, final boolean controlling) { final Closure closure = new Closure() { public void execute(final IceCandidatePair pair) { final IceCandidate local = pair.getLocalCandidate(); final IceCandidate remote = pair.getRemoteCandidate(); local.setControlling(controlling); // Note we also set the controlling status of the remote // candidate because there's nothing in the SDP specifying the // controlling status -- it's just an externally configured // property based on starting roles and any role conflicts that // may emerge over the course of establishing a media session. remote.setControlling(!controlling); pair.recomputePriority(); } }; executeOnPairs(pairs, closure); } public void formCheckList(final Collection remoteCandidates) { final Collection> pairs = new ArrayList>(10); for (final IceCandidate localCandidate : m_localCandidates) { for (final IceCandidate remoteCandidate : remoteCandidates) { final InetSocketAddress isa = remoteCandidate .getSocketAddress(); final InetAddress remote = isa.getAddress(); if (remote.isSiteLocalAddress() && IceConfig.isDisableUdpOnLocalNetwork()) { // We ignore site local addresses for UDP because those // should be accessible with TCP instead. m_log.info("Ignoring site local address: {}", remote); continue; } if (shouldPair(localCandidate, remoteCandidate)) { final Pair pair = new PairImpl( localCandidate, remoteCandidate); pairs.add(pair); } } } m_log.debug("Pairs before conversion: {}", pairs.size()); // Convert server reflexive local candidates to their base and remove // pairs with TCP passive local candidates. final List> convertedPairs = convertPairs(pairs); m_log.debug("Pairs after conversion: {}", convertedPairs.size()); final Comparator> comparator = new Comparator>() { public int compare(final Pair pair1, final Pair pair2) { final long pair1Priority = calculatePriority(pair1); final long pair2Priority = calculatePriority(pair2); if (pair1Priority > pair2Priority) return -1; if (pair1Priority < pair2Priority) return 1; return 0; } private long calculatePriority( final Pair pair) { return IceCandidatePairPriorityCalculator.calculatePriority( pair.getFirst(), pair.getSecond()); } }; Collections.sort(convertedPairs, comparator); m_log.debug(convertedPairs.size() + " converted"); final List pruned = prunePairs(convertedPairs); m_log.debug(pruned.size() + " after pruned"); final List sorted = sortPairs(pruned); synchronized (this) { this.m_pairs.addAll(sorted); this.allPairs.addAll(sorted); m_log.debug("Created pairs:\n" + this.m_pairs); } /* * final Closure tcpTurnClosure = new * Closure() { private final Set * addedAddresses = new HashSet(); public void * execute(final IceCandidatePair pair) { final IceCandidate remote = * pair.getRemoteCandidate(); final InetAddress remoteAddress = * remote.getSocketAddress().getAddress(); if * (addedAddresses.contains(remoteAddress)) { return; } if * (!remoteAddress.isSiteLocalAddress() && * !remoteAddress.isLinkLocalAddress() && * !remoteAddress.isAnyLocalAddress()) { * addedAddresses.add(remoteAddress); } } }; * executeOnPairs(tcpTurnClosure); */ } private List sortPairs(final List pairs) { synchronized (this) { Collections.sort(pairs); } return pairs; } /** * Removes any TCP passive local pairs and converts pairs with a local * UDP server reflexive candidate to the associated base candidate. * * @param pairs The pairs to convert. * @return The {@link List} of pairs with TCP passive pairs removed and * server reflexive local candidates converted to their bases. See * ICE section 5.7.3. */ private static List> convertPairs( final Collection> pairs) { final List> convertedPairs = new ArrayList>(pairs.size()); for (final Pair pair : pairs) { final Pair converted = convertPair(pair); if (converted != null) { convertedPairs.add(converted); } } return convertedPairs; } private static Pair convertPair( final Pair pair) { final IceCandidate localCandidate = pair.getFirst(); final IceCandidate remoteCandidate = pair.getSecond(); // We have to convert all local UDP server reflexive candidates to // their base and we have to ignore all TCP passive candidates. final IceCandidateVisitor> visitor = new IceCandidateVisitor>() { public void visitCandidates(Collection candidates) { // Not used here. } public Pair visitUdpServerReflexiveCandidate( final IceUdpServerReflexiveCandidate candidate) { // Convert server reflexive candidates to their base. final IceCandidate base = candidate.getBaseCandidate(); final InetAddress localAddress = base.getSocketAddress() .getAddress(); final InetAddress remoteAddress = remoteCandidate .getSocketAddress().getAddress(); // If we're trying to connect an address to itself, ignore it. // This can happen when we're on a public IP for some reason. if (localAddress.equals(remoteAddress)) { return null; } return new PairImpl(base, remoteCandidate); } public Pair visitTcpActiveCandidate( final IceTcpActiveCandidate candidate) { return pair; } public Pair visitTcpHostPassiveCandidate( final IceTcpHostPassiveCandidate candidate) { // Ignore all TCP passive candidates. return null; } public Pair visitTcpRelayPassiveCandidate( final IceTcpRelayPassiveCandidate candidate) { // Ignore all TCP passive candidates. return null; } public Pair visitTcpServerReflexiveSoCandidate( final IceTcpServerReflexiveSoCandidate candidate) { // TODO: We don't currently support TCP SO. return null; } public Pair visitTcpPeerReflexiveCandidate( final IceTcpPeerReflexiveCandidate candidate) { // Should not visit peer reflexive in check lists. return null; } public Pair visitUdpHostCandidate( final IceUdpHostCandidate candidate) { return pair; } public Pair visitUdpPeerReflexiveCandidate( final IceUdpPeerReflexiveCandidate candidate) { // This can happen when the answerer starts checks before the // offerer has received the answer. The offerer might get // a peer reflexive candidate before it's had a chance to form // its checklist. return pair; } public Pair visitUdpRelayCandidate( final IceUdpRelayCandidate candidate) { return pair; } }; return localCandidate.accept(visitor); } /** * Prunes pairs by converting any non-host local candidates to host * candidates and removing any duplicates created. The pairs should already * be ordered by priority when this method is called. * * @param pairs The pairs to prune. This {@link List} MUST already be * sorted by pair priority prior to this call. */ private List prunePairs( final List> pairs) { // Note the pairs override hashCode using the local and the remote // candidates. We just use the map here to identify pairs with the // same address for the local and the remote candidates. This is // possible because we just converted local server reflexive // candidates to their associated bases, according to the algorithm. // // If we find a duplicate pair, we always take the one with the // higher priority. final List prunedPairs = new ArrayList(6); final Set> seenPairs = new HashSet>(); for (final Pair pair : pairs) { if (!seenPairs.contains(pair)) { seenPairs.add(pair); prunedPairs.add(createPair(pair)); } } // Limit attacks based on the number of pairs. if (prunedPairs.size() > 100) { return prunedPairs.subList(0, 40); } return prunedPairs; } private IceCandidatePair createPair( final Pair pair) { final IceCandidate localCandidate = pair.getFirst(); final IceCandidate remoteCandidate = pair.getSecond(); return m_iceCandidatePairFactory.newPair(localCandidate, remoteCandidate); } private static boolean shouldPair(final IceCandidate localCandidate, final IceCandidate remoteCandidate) { if (localCandidate.getSocketAddress().getAddress() .equals(remoteCandidate.getSocketAddress().getAddress())) { return false; } // This is specified in ICE section 5.7.1 return ((localCandidate.getComponentId() == remoteCandidate.getComponentId()) && addressTypesMatch(localCandidate, remoteCandidate) && transportTypesMatch( localCandidate, remoteCandidate)); } private static boolean addressTypesMatch(final IceCandidate localCandidate, final IceCandidate remoteCandidate) { final InetAddress localAddress = localCandidate.getSocketAddress().getAddress(); final InetAddress remoteAddress = remoteCandidate.getSocketAddress().getAddress(); final boolean localIsIpV4 = localAddress instanceof Inet4Address; final boolean remoteIsIpV4 = remoteAddress instanceof Inet4Address; if (localIsIpV4) { return remoteIsIpV4; } else { return !remoteIsIpV4; } } private static boolean transportTypesMatch( final IceCandidate localCandidate, final IceCandidate remoteCandidate) { final IceTransportProtocol localTransport = localCandidate .getTransport(); final IceTransportProtocol remoteTransport = remoteCandidate .getTransport(); switch (localTransport) { case UDP: return remoteTransport == IceTransportProtocol.UDP; case TCP_SO: return remoteTransport == IceTransportProtocol.TCP_SO; case TCP_ACT: return remoteTransport == IceTransportProtocol.TCP_PASS; case TCP_PASS: return remoteTransport == IceTransportProtocol.TCP_ACT; } return false; } public boolean hasHigherPriorityPendingPair(final IceCandidatePair pair) { final long priority = pair.getPriority(); final Predicate triggeredPred = new Predicate() { public boolean evaluate(final IceCandidatePair curPair) { if (curPair.getPriority() > priority) return true; return false; } }; if (matchesAny(this.m_triggeredQueue, triggeredPred)) { return true; } final Predicate pred = new Predicate() { public boolean evaluate(final IceCandidatePair curPair) { if (curPair.getPriority() > priority) { final IceCandidatePairState state = curPair.getState(); switch (state) { case FROZEN: // Fall through. case WAITING: // Fall through. case IN_PROGRESS: return true; case SUCCEEDED: // Fall through. case FAILED: return false; } } return false; } }; return matchesAny(pred); } public void removeWaitingAndFrozenPairs(final IceCandidatePair nominatedPair) { m_log.debug("Removing waiting and frozen pairs..."); final Predicate pred = new Predicate() { public boolean evaluate(final IceCandidatePair curPair) { final IceCandidatePairState state = curPair.getState(); switch (state) { case FROZEN: // Fall through. case WAITING: m_log.debug("Closing pair:\n{}", curPair); curPair.close(); /* * if (curPair.isTcp()) { curPair.close(); } else { final * IceCandidate nominatedLocal = pair.getLocalCandidate(); * final IceCandidate local = curPair.getLocalCandidate(); * * // For UDP, we need to check that the local // candidate * is not the same as the local // candidate for the * nominated pair. If it is, // this will inadvertently * close the session for // the nominated pair, which would * be bad! if (!local.getSocketAddress().equals( * nominatedLocal.getSocketAddress())) { curPair.close(); } * } */ return true; case IN_PROGRESS: // The following is at SHOULD strength in 8.1.2. We // do this for both triggered checks and the check list // because I see no reason not to cancel the triggered // one here too, although the draft seems to indicate // we should only do it for the normal check list. if (curPair.getPriority() < nominatedPair.getPriority()) { m_log.debug("Canceling IN-PROGRESS pair {}", curPair); curPair.cancelStunTransaction(); } else { m_log.debug("Not canceling higher priority " + "IN-PROGRESS pair: {}", curPair); } break; case SUCCEEDED: // Do nothing. case FAILED: // Do nothing. } return false; } }; synchronized (this) { for (final Iterator iter = m_pairs.iterator(); iter .hasNext();) { final IceCandidatePair curPair = iter.next(); if (pred.evaluate(curPair)) { iter.remove(); } } for (final Iterator iter = m_triggeredQueue .iterator(); iter.hasNext();) { final IceCandidatePair curPair = iter.next(); if (pred.evaluate(curPair)) { iter.remove(); } } } } public void executeOnPairs(final Closure closure) { executeOnPairs(this.m_pairs, closure); } public IceCandidatePair selectPair(final Predicate pred) { synchronized (this) { final CollectionUtils utils = new CollectionUtilsImpl(); return utils.selectFirst(this.m_pairs, pred); } } public IceCandidatePair selectAnyPair(final Predicate pred) { synchronized (this) { final CollectionUtils utils = new CollectionUtilsImpl(); final IceCandidatePair pair = utils.selectFirst(this.m_pairs, pred); if (pair != null) { return pair; } return utils.selectFirst(this.m_triggeredQueue, pred); } } public boolean matchesAny(final Predicate pred) { return matchesAny(this.m_pairs, pred); } public boolean matchesAll(final Predicate pred) { return matchesAll(this.m_pairs, pred); } private boolean matchesAny(final Collection pairs, final Predicate pred) { synchronized (this) { final CollectionUtils utils = new CollectionUtilsImpl(); return utils.matchesAny(pairs, pred); } } private boolean matchesAll(final Collection pairs, final Predicate pred) { synchronized (this) { final CollectionUtils utils = new CollectionUtilsImpl(); return utils.matchesAll(pairs, pred); } } private void executeOnPairs(final Collection pairs, final Closure closure) { synchronized (this) { final CollectionUtils utils = new CollectionUtilsImpl(); utils.forAllDo(pairs, closure); } } public void close() { m_log.info("Closing check list..."); final Closure close = new Closure() { public void execute(final IceCandidatePair pair) { pair.close(); } }; executeOnPairs(close); executeOnPairs(this.m_triggeredQueue, close); executeOnPairs(this.allPairs, close); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy