diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index 850183146..4daecb833 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -745,7 +745,7 @@ protected IceMediaStream createIceStream(String media) { //the following call involves STUN processing so it may take a while stream = getNetAddrMgr().createIceStream( - getNextMediaPortToTry(), media, iceAgent); + getPortTracker(media).getPort(), media, iceAgent); } catch (Exception ex) { @@ -761,14 +761,9 @@ protected IceMediaStream createIceStream(String media) //would simply include one more bind retry. try { - setNextMediaPortToTry( - 1 - + stream - .getComponent(Component.RTCP) - .getLocalCandidates() - .get(0) - .getTransportAddress() - .getPort()); + getPortTracker(media).setNextPort( + 1 + stream.getComponent(Component.RTCP).getLocalCandidates() + .get(0).getTransportAddress() .getPort()); } catch(Throwable t) { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java index 37d34c0ae..ffb78afcf 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/TransportManagerGTalkImpl.java @@ -847,15 +847,18 @@ else if(mediaType == MediaType.VIDEO) * name. * * @param media the name of the stream we'd like to create. - * * @param rtcp if true allocate an RTCP port + * @param portTracker the port tracker that we should use to obtain ports + * for this stream. * * @return the newly created {@link IceMediaStream} * * @throws OperationFailedException if binding on the specified media stream * fails for some reason. */ - private IceMediaStream createIceStream(String media, boolean rtcp) + private IceMediaStream createIceStream(String media, + boolean rtcp, + PortTracker portTracker) throws OperationFailedException { IceMediaStream stream; @@ -864,15 +867,15 @@ private IceMediaStream createIceStream(String media, boolean rtcp) { //the following call involves STUN processing so it may take a while stream = iceAgent.createMediaStream(media); - int rtpPort = getNextMediaPortToTry(); + int rtpPort = portTracker.getPort(); //rtp iceAgent.createComponent(stream, Transport.UDP, rtpPort, rtpPort, rtpPort + 100); if(rtcp) - iceAgent.createComponent(stream, Transport.UDP, - rtpPort + 1, rtpPort + 1, rtpPort + 101); + iceAgent.createComponent(stream, Transport.UDP, rtpPort, + portTracker.getMinPort(), portTracker.getMaxPort()); } catch (Exception ex) { @@ -888,14 +891,10 @@ private IceMediaStream createIceStream(String media, boolean rtcp) //would simply include one more bind retry. try { - setNextMediaPortToTry( - 1 - + stream - .getComponent(rtcp ? Component.RTCP : Component.RTP) - .getLocalCandidates() - .get(0) - .getTransportAddress() - .getPort()); + portTracker.setNextPort(1 + stream + .getComponent(rtcp ? Component.RTCP : Component.RTP) + .getLocalCandidates().get(0).getTransportAddress() + .getPort()); } catch(Throwable t) { @@ -950,15 +949,17 @@ else if(ext.getNamespace().equals( if(audio) { - IceMediaStream stream = createIceStream("rtp", video); + IceMediaStream stream = createIceStream( + "rtp", video, getPortTracker(MediaType.AUDIO)); - candidates.addAll(GTalkPacketFactory.createCandidates("rtp", - stream)); + candidates.addAll(GTalkPacketFactory.createCandidates( + "rtp", stream)); } if(video) { - IceMediaStream stream = createIceStream("video_rtp", true); + IceMediaStream stream = createIceStream( + "video_rtp", true, getPortTracker(MediaType.VIDEO)); candidates.addAll( GTalkPacketFactory.createCandidates("video_rtp", stream)); } diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java index e971f4155..eab77ba90 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java @@ -29,6 +29,13 @@ public interface OperationSetBasicTelephony extends OperationSet { + /** + * The name of the property that contains the maximum port number that we'd + * like our RTP managers to bind upon. + */ + public static final String MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MAX_MEDIA_PORT_NUMBER"; + /** * The name of the property that contains the minimum port number that we'd * like our RTP managers to bind upon. @@ -36,12 +43,52 @@ public interface OperationSetBasicTelephony public static final String MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME = "net.java.sip.communicator.service.protocol.MIN_MEDIA_PORT_NUMBER"; + + /** + * The name of the property that contains the minimum port number that we'd + * like our Video RTP managers to bind upon. + */ + public static final String MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MIN_VIDEO_PORT_NUMBER"; + /** * The name of the property that contains the maximum port number that we'd - * like our RTP managers to bind upon. + * like our Video RTP managers to bind upon. */ - public static final String MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME - = "net.java.sip.communicator.service.protocol.MAX_MEDIA_PORT_NUMBER"; + public static final String MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MAX_VIDEO_PORT_NUMBER"; + + + /** + * The name of the property that contains the minimum port number that we'd + * like our Audio RTP managers to bind upon. + */ + public static final String MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MIN_AUDIO_PORT_NUMBER"; + + /** + * The name of the property that contains the maximum port number that we'd + * like our Audio RTP managers to bind upon. + */ + public static final String MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MAX_AUDIO_PORT_NUMBER"; + + + /** + * The name of the property that contains the minimum port number that we'd + * like our Data Channel (e.g. Pseudo TCP) managers to bind upon. + */ + public static final String MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MIN_DATA_CHANNEL_PORT_NUMBER"; + + /** + * The name of the property that contains the maximum port number that we'd + * like our Data Channel RTP managers to bind upon. + */ + public static final String MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME + = "net.java.sip.communicator.service.protocol.MAX_DATA_CHANNEL_PORT_NUMBER"; + + /** * Reason code used to hangup peer, indicates normal hangup. diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java index ede1b3aba..31698a401 100644 --- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java +++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java @@ -37,29 +37,39 @@ public abstract class TransportManager> = Logger.getLogger(TransportManager.class); /** - * The minimum port number that we'd like our RTP sockets to bind upon. + * The port tracker that we should use when binding generic media streams. *

* Initialized by {@link #initializePortNumbers()}. *

*/ - private static int minMediaPort = -1; + private static PortTracker defaultPortTracker = new PortTracker(5000, 6000); /** - * The maximum port number that we'd like our RTP sockets to bind upon. + * The port tracker that we should use when binding video media streams. *

- * Initialized by {@link #initializePortNumbers()}. + * Potentially initialized by {@link #initializePortNumbers()} if the + * necessary properties are set. *

*/ - private static int maxMediaPort = -1; + private static PortTracker videoPortTracker = null; /** - * The port that we should try to bind our next media stream's RTP socket - * to. + * The port tracker that we should use when binding data channels. *

- * Initialized by {@link #initializePortNumbers()}. + * Potentially initialized by {@link #initializePortNumbers()} if the + * necessary properties are set + *

+ */ + private static PortTracker dataChannelPortTracker = null; + + /** + * The port tracker that we should use when binding data media streams. + *

+ * Potentially initialized by {@link #initializePortNumbers()} if the + * necessary properties are set *

*/ - private static int nextMediaPortToTry = -1; + private static PortTracker audioPortTracker = null; /** * RTP audio DSCP configuration property name. @@ -106,7 +116,7 @@ public abstract class TransportManager> */ protected TransportManager(U callPeer) { - this.callPeer = callPeer; + this.callPeer = callPeer; } /** @@ -229,12 +239,15 @@ protected StreamConnector createStreamConnector(MediaType mediaType) //make sure our port numbers reflect the configuration service settings initializePortNumbers(); + PortTracker portTracker = getPortTracker(mediaType); + //create the RTP socket. DatagramSocket rtpSocket = null; try { - rtpSocket = nam.createDatagramSocket( localHostForPeer, - nextMediaPortToTry, minMediaPort, maxMediaPort); + rtpSocket = nam.createDatagramSocket( + localHostForPeer, portTracker.getPort(), + portTracker.getMinPort(), portTracker.getMaxPort()); } catch (Exception exc) { @@ -244,14 +257,16 @@ protected StreamConnector createStreamConnector(MediaType mediaType) } //make sure that next time we don't try to bind on occupied ports - nextMediaPortToTry = rtpSocket.getLocalPort() + 1; + //also, refuse validation in case someone set the tracker range to 1 + portTracker.setNextPort( rtpSocket.getLocalPort() + 1, false); //create the RTCP socket, preferably on the port following our RTP one. DatagramSocket rtcpSocket = null; try { - rtcpSocket = nam.createDatagramSocket(localHostForPeer, - nextMediaPortToTry, minMediaPort, maxMediaPort); + rtcpSocket = nam.createDatagramSocket( + localHostForPeer, portTracker.getPort(), + portTracker.getMinPort(), portTracker.getMaxPort()); } catch (Exception exc) { @@ -262,76 +277,68 @@ protected StreamConnector createStreamConnector(MediaType mediaType) } //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; + portTracker.setNextPort( rtcpSocket.getLocalPort() + 1); return new DefaultStreamConnector(rtpSocket, rtcpSocket); } /** - * (Re)Sets the minPortNumber and maxPortNumber to their - * defaults or to the values specified in the ConfigurationService. + * (Re)Sets the all the port allocators to reflect current values specified + * in the ConfigurationService. Calling this method may very well + * result in creating new port allocators or destroying exising ones. */ protected static void initializePortNumbers() { - //first reset to default values - minMediaPort = 5000; - maxMediaPort = 6000; - - //then set to anything the user might have specified. + //try the default tracker first ConfigurationService configuration = ProtocolMediaActivator.getConfigurationService(); - String minPortNumberStr - = configuration.getString( - OperationSetBasicTelephony - .MIN_MEDIA_PORT_NUMBER_PROPERTY_NAME); + String minPortNumberStr = configuration.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 = configuration.getString( + OperationSetBasicTelephony.MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME); - String maxPortNumberStr - = configuration.getString( - OperationSetBasicTelephony - .MAX_MEDIA_PORT_NUMBER_PROPERTY_NAME); + //try to send the specified range. If there's no specified range in + //configuration, we'll just leave the tracker as it is: [5000 to 6000] + defaultPortTracker.tryRange(minPortNumberStr, maxPortNumberStr); - 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); - } - } - /* - * Make sure that nextMediaPortToTry is within the range of minMediaPort - * and maxMediaPort as - * NetworkAddressManagerServiceImpl#createDatagramSocket(InetAddress, - * int, int, int) does. - */ - if ((minMediaPort <= maxMediaPort) - && ((nextMediaPortToTry < minMediaPort) - || (nextMediaPortToTry > maxMediaPort))) - nextMediaPortToTry = minMediaPort; + //try the VIDEO tracker + minPortNumberStr = configuration.getString( + OperationSetBasicTelephony.MIN_VIDEO_PORT_NUMBER_PROPERTY_NAME); + + maxPortNumberStr = configuration.getString( + OperationSetBasicTelephony.MAX_VIDEO_PORT_NUMBER_PROPERTY_NAME); + + //try to send the specified range. If there's no specified range in + //configuration, we'll just leave this tracker to null + videoPortTracker + = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr); + + + //try the AUDIO tracker + minPortNumberStr = configuration.getString( + OperationSetBasicTelephony.MIN_AUDIO_PORT_NUMBER_PROPERTY_NAME); + + maxPortNumberStr = configuration.getString( + OperationSetBasicTelephony.MAX_AUDIO_PORT_NUMBER_PROPERTY_NAME); + + //try to send the specified range. If there's no specified range in + //configuration, we'll just leave this tracker to null + audioPortTracker + = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr); + + //try the DATA CHANNEL tracker + minPortNumberStr = configuration.getString(OperationSetBasicTelephony + .MIN_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME); + + maxPortNumberStr = configuration.getString(OperationSetBasicTelephony + .MAX_DATA_CHANNEL_PORT_NUMBER_PROPERTY_NAME); + + //try to send the specified range. If there's no specified range in + //configuration, we'll just leave this tracker to null + dataChannelPortTracker + = PortTracker.createTracker(minPortNumberStr, maxPortNumberStr); } /** @@ -554,29 +561,54 @@ public U getCallPeer() } /** - * Gets the port that we should try to bind our next media stream's RTP - * socket to. + * Returns the port tracker that we are supposed to use when binding ports + * for the specified {@link MediaType}. + * + * @param mediaType the media type that we want to obtain a locator for. * - * @return the port that we should try to bind our next media stream's RTP - * socket to + * @return the port tracker that we are supposed to use when binding ports + * for the specified {@link MediaType}. */ - protected static int getNextMediaPortToTry() + protected static PortTracker getPortTracker(MediaType mediaType) { - if (nextMediaPortToTry == -1) - initializePortNumbers(); - return nextMediaPortToTry; + if(MediaType.AUDIO == mediaType && audioPortTracker != null) + return audioPortTracker; + + if(MediaType.VIDEO == mediaType && videoPortTracker != null) + return videoPortTracker; + + return defaultPortTracker; } /** - * Sets the port that we should try to bind our next media stream's RTP - * socket to + * Returns the port tracker that we are supposed to use when binding ports + * for the {@link MediaType} indicated by the string param. If we do not + * recognize the string as a valid media type, we simply return the default + * port tracker. + * + * @param mediaTypeStr the name of the media type that we want to obtain a + * locator for. * - * @param nextMediaPortToTry the port that we should try to bind our next - * media stream's RTP socket to + * @return the port tracker that we are supposed to use when binding ports + * for the {@link MediaType} with the specified name or the default tracker + * in case the name doesn't ring a bell. */ - protected static void setNextMediaPortToTry(int nextMediaPortToTry) + protected static PortTracker getPortTracker(String mediaTypeStr) { - TransportManager.nextMediaPortToTry = nextMediaPortToTry; + try + { + MediaType mediaType = MediaType.parseString(mediaTypeStr); + + return getPortTracker(mediaType); + } + catch (Exception exc) + { + logger.info( + "Returning default port tracker for unrecognized media type: " + + mediaTypeStr); + + return defaultPortTracker; + } } /** diff --git a/src/net/java/sip/communicator/util/PortTracker.java b/src/net/java/sip/communicator/util/PortTracker.java new file mode 100644 index 000000000..9b6fda54b --- /dev/null +++ b/src/net/java/sip/communicator/util/PortTracker.java @@ -0,0 +1,222 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util; + +/** + * The PortTracker class allows for a controlled selection of bind + * ports. This is typically useful in cases where we would like to set bounds + * for the ports that we are going to use for a particular socket. For example, + * at the time of writing of this class, this policy allows Jitsi to bind RTP + * sockets on ports that are always between 5000 and 6000 (default values). It + * is also used to allow for different port ranges for Audio and Video streams. + * + * @author Emil Ivov + */ +public class PortTracker +{ + /** + * The local logger. + */ + private static final Logger logger = Logger.getLogger(NetworkUtils.class); + + /** + * The minimum port number that this allocator would be allocate to return. + */ + private int minPort = NetworkUtils.MIN_PORT_NUMBER; + + /** + * The maximum port number that this allocator would be allocate to return. + */ + private int maxPort = NetworkUtils.MAX_PORT_NUMBER; + + /** + * The next port that we will return if asked. + */ + private int port = -1; + + /** + * Initializes a port tracker with the specified port range. + * + * @param minPort the minimum port that we would like to bind on + * @param maxPort the maximum port that we would like to bind on + */ + public PortTracker(int minPort, int maxPort) + { + setRange(minPort, maxPort); + } + + /** + * Returns the next port that the using class is supposed to try allocating. + * + * @return the next port that the using class is supposed to try allocating. + */ + public int getPort() + { + return port; + } + + /** + * (Re)Sets the range that this tracker returns values in. The method would + * also update the value of the next port to allocate in case it is + * currently outside the specified range. The method also allows configuring + * this allocator in a way that it would always return the same port. This + * would happen when newMinPort is equal to newMaxPort + * which would make both equal to the only possible value. + * + * @param newMinPort the minimum port that we would like to bind on + * @param newMaxPort the maximum port that we would like to bind on + * + * @throws IllegalArgumentException if the arguments do not correspond to + * valid port numbers, or in case newMaxPort < newMinPort + */ + public void setRange(int newMinPort, int newMaxPort) + throws IllegalArgumentException + { + //validate + if( !NetworkUtils.isValidPortNumber(newMinPort) + || !NetworkUtils.isValidPortNumber(newMaxPort) + || newMaxPort < newMinPort) + { + throw new IllegalArgumentException( + "[" + newMinPort + " to " + + newMaxPort + "] is not a valid port range."); + } + + //reset bounds + minPort = newMinPort; + maxPort = newMaxPort; + + /* + * Make sure that nextPort is within the specified range. Preserve value + * if already valid. + */ + if (port < minPort || port > maxPort) + { + port = minPort; + } + } + + /** + * Attempts to set the range specified by the min and max port string + * params. If the attempt fails, for reasons such as invalid porameters, + * this method will simply return without an exception and without an impact + * on the state of this class. + * + * @param newMinPortString the minimum port that we would like to bind on + * @param newMaxPortString the maximum port that we would like to bind on + */ + public void tryRange(String newMinPortString, String newMaxPortString) + { + try + { + int newMinPort = Integer.parseInt(newMinPortString); + int newMaxPort = Integer.parseInt(newMaxPortString); + + setRange(newMinPort, newMaxPort); + } + catch(Exception exc)//Null, NumberFormat, IllegalArgument + { + logger.info("Ignoring invalid port range ["+ newMinPortString + + " to " + newMaxPortString +"]"); + + + logger.debug("Cause: ", exc); + } + } + + /** + * Sets the next port to specified value unless allowing the caller to + * request validation and force the port into the range that this tracker + * operates in. + * + * @param nextPort the next port we'd like this tracker to return. + * @param validate determines whether this tracker should bring the new + * value into its current range. + */ + public void setNextPort(int nextPort, boolean validate) + { + /* + * Make sure that nextPort is within the specified range unless + */ + if ((nextPort < minPort || nextPort > maxPort ) && validate) + { + port = minPort; + } + else + { + this.port = nextPort; + } + } + + /** + * Sets the next port to specified value unless it is outside the range that + * this tracker operates in, in which case it sets it to the minimal + * possible. + * + * @param nextPort the next port we'd like this tracker to return. + */ + public void setNextPort(int nextPort) + { + setNextPort(nextPort, true); + } + + /** + * Returns the lowest/minimum port that this tracker would use. + * + * @return the minimum port number allowed by this tracker. + */ + public int getMinPort() + { + return minPort; + } + + /** + * Returns the highest/maximum port that this tracker would use. + * + * @return the maximum port number allowed by this tracker. + */ + public int getMaxPort() + { + return maxPort; + } + + /** + * Attempts to create a port tracker that uses the min and max values + * indicated by the newMinPortString and newMinPortString + * strings and returns it if successful. The method fails silently + * (returning null) otherwise. + * + * @param newMinPortString the {@link String} containing the minimum port + * number that this tracker should allow. + * @param newMaxPortString the {@link String} containing the minimum port + * number that this tracker should allow. + * + * @return the newly created port tracker or null if the string + * params do not contain valid port numbers. + */ + public static PortTracker createTracker(String newMinPortString, + String newMaxPortString) + { + try + { + int minPort = Integer.parseInt(newMinPortString); + int maxPort = Integer.parseInt(newMaxPortString); + + return new PortTracker(minPort, maxPort); + } + catch(Exception exc)//Null, NumberFormat, IllegalArgument + { + logger.info("Ignoring invalid port range ["+ newMinPortString + + " to " + newMaxPortString +"]"); + + + logger.debug("Cause: ", exc); + return null; + } + } + +}