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

org.lastbamboo.common.ice.transport.IceUdpStunChecker Maven / Gradle / Ivy

The newest version!
package org.lastbamboo.common.ice.transport;

import java.net.InetSocketAddress;

import org.apache.commons.id.uuid.UUID;
import org.littleshoot.mina.common.IoSession;
import org.littleshoot.stun.stack.message.BindingRequest;
import org.littleshoot.stun.stack.message.CanceledStunMessage;
import org.littleshoot.stun.stack.message.NullStunMessage;
import org.littleshoot.stun.stack.message.StunMessage;
import org.littleshoot.stun.stack.transaction.StunTransactionTracker;
import org.littleshoot.util.RuntimeIoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class that performs STUN connectivity checks for ICE over UDP. Each 
 * ICE candidate pair has its own connectivity checker. 
 */
public class IceUdpStunChecker extends AbstractIceStunChecker {

    private static final Logger LOG = 
        LoggerFactory.getLogger(IceUdpStunChecker.class);

    /**
     * Creates a new UDP STUN connectivity checker.
     * 
     * @param session The MINA session over which the checks will take place.
     * @param transactionTracker The class for keeping track of STUN 
     * transactions for checks.
     */
    public IceUdpStunChecker(final IoSession session,
            final StunTransactionTracker transactionTracker) {
        super(session, transactionTracker);
    }
    
    @Override
    protected StunMessage writeInternal(final BindingRequest bindingRequest,
            final long rto) {
        if (this.m_transactionCanceled) {
            return new CanceledStunMessage();
        }
        if (this.m_writeCallsForChecker > 1) {
            throw new RuntimeIoException("Too many write calls: "
                    + this.m_writeCallsForChecker);
        }
        if (bindingRequest == null) {
            throw new NullPointerException("Null Binding Request");
        }

        // This method will retransmit the same request multiple times because
        // it's being sent unreliably. All of these requests will be
        // identical, using the same transaction ID.
        final UUID id = bindingRequest.getTransactionId();
        final InetSocketAddress localAddress = 
            (InetSocketAddress) this.m_ioSession.getLocalAddress();
        final InetSocketAddress remoteAddress = 
            (InetSocketAddress) this.m_ioSession.getRemoteAddress();

        this.m_transactionTracker.addTransaction(bindingRequest, this,
                localAddress, remoteAddress);

        synchronized (m_requestLock) {
            int requests = 0;

            long waitTime = 0L;

            while (!m_idsToResponses.containsKey(id) && requests < 7
                    && !this.m_transactionCanceled) {
                waitIfNoResponse(bindingRequest, waitTime);

                // If the transaction was canceled during the wait, then
                // don't send the request.
                if (this.m_transactionCanceled) {
                    break;
                }

                // See draft-ietf-behave-rfc3489bis-06.txt section 7.1. We
                // continually send the same request until we receive a
                // response, never sending more that 7 requests and using
                // an expanding interval between requests based on the
                // estimated round-trip-time to the server. This is because
                // some requests can be lost with UDP.

                // We only send if its connected because another thread might
                // have close the session (if ICE processing has finished and
                // this pair is not being used, for example).
                if (this.m_ioSession.isConnected()) {
                    LOG.info("Writing binding request: {} on {}", 
                        bindingRequest, this.m_ioSession);
                    this.m_ioSession.write(bindingRequest);
                }

                // Wait a little longer with each send.
                waitTime = (2 * waitTime) + rto;

                requests++;
                // m_log.debug("Wrote Binding Request number: {}", requests);
            }

            // Now we wait for 1.6 seconds after the last request was sent.
            // If we still don't receive a response, then the transaction
            // has failed.
            if (!this.m_transactionCanceled) {
                waitIfNoResponse(bindingRequest, 1600);
            }

            // Even if the transaction was canceled, we still may have
            // received a successful response. If we did, we process it as
            // usual. This is specified in 7.2.1.4.
            if (m_idsToResponses.containsKey(id)) {
                final StunMessage response = this.m_idsToResponses.remove(id);
                LOG.debug("Received STUN response: {}", response);
                return response;
            }

            if (this.m_transactionCanceled || this.m_closed
                    || this.m_ioSession.isClosing()) {
                LOG.debug("The transaction was canceled!");
                return new CanceledStunMessage();
            }

            else {
                // This will happen quite often, such as when we haven't
                // yet successfully punched a hole in the firewall.
                LOG.debug("Did not get response on: {}", this.m_ioSession);
                return new NullStunMessage();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy