/* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.service.protocol.media; import java.net.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.netaddr.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; /** * TransportManagers are responsible for allocating ports, gathering * local candidates and managing ICE whenever we are using it. * * @param the peer extension class like for example CallPeerSipImpl * or CallPeerJabberImpl * * @author Emil Ivov */ public abstract class TransportManager> { /** * The Logger used by the TransportManager * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(TransportManager.class.getName()); /** * The minimum port number that we'd like our RTP sockets to bind upon. */ private static int minMediaPort = 5000; /** * The maximum port number that we'd like our RTP sockets to bind upon. */ private static int maxMediaPort = 6000; /** * The port that we should try to bind our next media stream's RTP socket * to. */ protected static int nextMediaPortToTry = minMediaPort; /** * The RTP/RTCP socket couple that this media handler should use to send * and receive audio flows through. */ private StreamConnector audioStreamConnector = null; /** * The RTP/RTCP socket couple that this media handler should use to send * and receive video flows through. */ private StreamConnector videoStreamConnector = null; /** * The {@link MediaAwareCallPeer} whose traffic we will be taking care of. */ private U callPeer; /** * Creates a new instance of this transport manager, binding it to the * specified peer. * * @param callPeer the {@link MediaAwareCallPeer} whose traffic we will be * taking care of. */ protected TransportManager(U callPeer) { this.callPeer = callPeer; } /** * Returns the StreamConnector instance that this media handler * should use for streams of the specified mediaType. The method * would also create a new StreamConnector if no connector has * been initialized for this mediaType yet or in case one * of its underlying sockets has been closed. * * @param mediaType the MediaType that we'd like to create a connector for. * * @return this media handler's StreamConnector for the specified * mediaType. * * @throws OperationFailedException in case we failed to initialize our * connector. */ public StreamConnector getStreamConnector(MediaType mediaType) throws OperationFailedException { if (mediaType == MediaType.AUDIO) { if ( audioStreamConnector == null || audioStreamConnector.getDataSocket().isClosed() || audioStreamConnector.getControlSocket().isClosed()) { audioStreamConnector = createStreamConnector(); } return audioStreamConnector; } else { if ( videoStreamConnector == null || videoStreamConnector.getDataSocket().isClosed() || videoStreamConnector.getControlSocket().isClosed()) { videoStreamConnector = createStreamConnector(); } return videoStreamConnector; } } /** * Closes both the control and the data socket of the specified connector * and releases its reference (if it wasn't the case already). * * @param mediaType the type of the connector we'd like to close. */ public void closeStreamConnector(MediaType mediaType) { StreamConnector connector = (mediaType == MediaType.VIDEO) ? videoStreamConnector : audioStreamConnector; if(connector == null) return; synchronized(connector) { connector.getDataSocket().close(); connector.getControlSocket().close(); connector = null; } } /** * Creates a media StreamConnector. The method takes into account * the minimum and maximum media port boundaries. * * @return a new StreamConnector. * * @throws OperationFailedException if we fail binding the the sockets. */ protected StreamConnector createStreamConnector() throws OperationFailedException { NetworkAddressManagerService nam = ProtocolMediaActivator.getNetworkAddressManagerService(); InetAddress intendedDestination = getIntendedDestination(getCallPeer()); InetAddress localHostForPeer = nam.getLocalHost(intendedDestination); //make sure our port numbers reflect the configuration service settings initializePortNumbers(); //create the RTP socket. DatagramSocket rtpSocket = null; try { rtpSocket = nam.createDatagramSocket( localHostForPeer, nextMediaPortToTry, minMediaPort, maxMediaPort); } catch (Exception exc) { throw new OperationFailedException( "Failed to allocate the network ports necessary for the call.", OperationFailedException.INTERNAL_ERROR, exc); } //make sure that next time we don't try to bind on occupied ports nextMediaPortToTry = rtpSocket.getLocalPort() + 1; //create the RTCP socket, preferably on the port following our RTP one. DatagramSocket rtcpSocket = null; try { rtcpSocket = nam.createDatagramSocket(localHostForPeer, nextMediaPortToTry, minMediaPort, maxMediaPort); } catch (Exception exc) { throw new OperationFailedException( "Failed to allocate the network ports necessary for the call.", OperationFailedException.INTERNAL_ERROR, exc); } //make sure that next time we don't try to bind on occupied ports nextMediaPortToTry = rtcpSocket.getLocalPort() + 1; if (nextMediaPortToTry > maxMediaPort -1)// take RTCP into account. nextMediaPortToTry = minMediaPort; //create the RTCP socket DefaultStreamConnector connector = new DefaultStreamConnector( rtpSocket, rtcpSocket); return connector; } /** * (Re)Sets the minPortNumber and maxPortNumber to their * defaults or to the values specified in the ConfigurationService. */ protected void initializePortNumbers() { //first reset to default values minMediaPort = 5000; maxMediaPort = 6000; //then set to anything the user might have specified. String minPortNumberStr = ProtocolMediaActivator.getConfigurationService() .getString(OperationSetBasicTelephony .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME); if (minPortNumberStr != null) { try { minMediaPort = Integer.parseInt(minPortNumberStr); } catch (NumberFormatException ex) { logger.warn(minPortNumberStr + " is not a valid min port number value. " + "using min port " + minMediaPort); } } String maxPortNumberStr = ProtocolMediaActivator.getConfigurationService() .getString(OperationSetBasicTelephony .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME); if (maxPortNumberStr != null) { try { maxMediaPort = Integer.parseInt(maxPortNumberStr); } catch (NumberFormatException ex) { logger.warn(maxPortNumberStr + " is not a valid max port number value. " +"using max port " + maxMediaPort, ex); } } } /** * Returns the InetAddress that we are using in one of our * StreamConnectors or, in case we don't have any connectors yet * the address returned by the our network address manager as the best local * address to use when contacting the CallPeer associated with this * MediaHandler. This method is primarily meant for use with the * o= and c= fields of a newly created session description. The point is * that we create our StreamConnectors when constructing the media * descriptions so we already have a specific local address assigned to them * at the time we get ready to create the c= and o= fields. It is therefore * better to try and return one of these addresses before trying the net * address manager again ang running the slight risk of getting a different * address. * * @return an InetAddress that we use in one of the * StreamConnectors in this class. */ public InetAddress getLastUsedLocalHost() { if (audioStreamConnector != null) return audioStreamConnector.getDataSocket().getLocalAddress(); if (videoStreamConnector != null) return videoStreamConnector.getDataSocket().getLocalAddress(); NetworkAddressManagerService nam = ProtocolMediaActivator.getNetworkAddressManagerService(); InetAddress intendedDestination = getIntendedDestination(getCallPeer()); return nam.getLocalHost(intendedDestination); } /** * Send empty UDP packet to target destination data/control ports * in order to open port on NAT or RTP proxy if any. * * @param target MediaStreamTarget * @param type the {@link MediaType} of the connector we'd like to send * the hole punching packet through. */ public void sendHolePunchPacket(MediaStreamTarget target, MediaType type) { if (logger.isInfoEnabled()) logger.info("Try to open port on NAT if any"); try { StreamConnector connector = getStreamConnector(type); synchronized(connector) { /* data port (RTP) */ connector.getDataSocket().send(new DatagramPacket( new byte[0], 0, target.getDataAddress().getAddress(), target.getDataAddress().getPort())); /* control port (RTCP) */ connector.getControlSocket().send(new DatagramPacket( new byte[0], 0, target.getControlAddress().getAddress(), target.getControlAddress().getPort())); } } catch(Exception e) { logger.error("Error cannot send to remote peer", e); } } /** * Returns the InetAddress that is most likely to be used as a * next hop when contacting the specified destination. This is * an utility method that is used whenever we have to choose one of our * local addresses to put in the Via, Contact or (in the case of no * registrar accounts) From headers. * * @param peer the CallPeer that we would contact. * * @return the InetAddress that is most likely to be to be used * as a next hop when contacting the specified destination. * * @throws IllegalArgumentException if destination is not a valid * host/ip/fqdn */ protected abstract InetAddress getIntendedDestination(U peer); /** * Returns the {@link MediaAwareCallPeer} that this transport manager is * serving. * * @return the {@link MediaAwareCallPeer} that this transport manager is * serving. */ public U getCallPeer() { return callPeer; } }