aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java
diff options
context:
space:
mode:
authorEmil Ivov <emcho@jitsi.org>2005-11-02 14:08:43 +0000
committerEmil Ivov <emcho@jitsi.org>2005-11-02 14:08:43 +0000
commitc82e7e1327fd89ec30929a7d4e529039b751bf41 (patch)
treeb2694122644e90a21bd0348f81a53a45841a37dd /src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java
parent462770b17bdbdffcf61b8862635738e46d58e505 (diff)
downloadjitsi-c82e7e1327fd89ec30929a7d4e529039b751bf41.zip
jitsi-c82e7e1327fd89ec30929a7d4e529039b751bf41.tar.gz
jitsi-c82e7e1327fd89ec30929a7d4e529039b751bf41.tar.bz2
Initial sip-communicator-1.0 commit
Diffstat (limited to 'src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java')
-rw-r--r--src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java634
1 files changed, 634 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java
new file mode 100644
index 0000000..8409033
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java
@@ -0,0 +1,634 @@
+/*
+ * 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.impl.netaddr;
+
+import java.net.*;
+import java.util.Enumeration;
+
+import net.java.sip.communicator.util.*;
+import net.java.stun4j.client.SimpleAddressDetector;
+import net.java.stun4j.*;
+import net.java.sip.communicator.service.netaddr.*;
+import net.java.sip.communicator.service.configuration.*;
+import net.java.sip.communicator.service.configuration.event.*;
+import java.text.*;
+
+
+/**
+ * This implementation of the Network Address Manager allows you to
+ * intelligently retrieve the address of your localhost according to preferences
+ * specified in a number of properties like:
+ * <br>
+ * net.java.sip.communicator.STUN_SERVER_ADDRESS - the address of the stun
+ * server to use for NAT traversal
+ * <br>
+ * net.java.sip.communicator.STUN_SERVER_PORT - the port of the stun server
+ * to use for NAT traversal
+ * <br>
+ * java.net.preferIPv6Addresses - a system property specifying weather ipv6
+ * addresses are to be preferred in address resolution (default is false for
+ * backward compatibility)
+ * <br>
+ * net.java.sip.communicator.common.PREFERRED_NETWORK_ADDRESS - the address
+ * that the user would like to use. (If this is a valid address it will be
+ * returned in getLocalhost() calls)
+ * <br>
+ * net.java.sip.communicator.common.PREFERRED_NETWORK_INTERFACE - the network
+ * interface that the user would like to use for fommunication (addresses
+ * belonging to that interface will be prefered when selecting a localhost
+ * address)
+ *
+ * @todo further explain the way the service works. explain address selection
+ * algorithms and priorities.
+ *
+ * @author Emil Ivov
+ * @author Pierre Floury
+ */
+public class NetworkAddressManagerServiceImpl
+ implements NetworkAddressManagerService, VetoableChangeListener
+{
+ private static Logger logger =
+ Logger.getLogger(NetworkAddressManagerServiceImpl.class);
+
+ /**
+ * The name of the property containing the stun server address.
+ */
+ private static final String PROP_STUN_SERVER_ADDRESS
+ = "net.java.sip.communicator.STUN_SERVER_ADDRESS";
+ /**
+ * the port number of the stun server to use for NAT traversal
+ */
+ private static final String PROP_STUN_SERVER_PORT
+ = "net.java.sip.communicator.STUN_SERVER_PORT";
+ /**
+ * a system property specifying weather ipv6
+ * addresses are to be preferred in address resolution (default is false for
+ * backward compatibility)
+ */
+ private static final String PROP_PREF_IPV6_ADDRS
+ = "java.net.preferIPv6Addresses";
+
+ /**
+ * If an application has a preference to only use IPv4 sockets then this
+ * property can be set to true.
+ */
+ private static final String PROP_PREF_IPV4_STACK
+ = "java.net.preferIPv4Stack";
+
+ /**
+ * the address that the user would like to use. (If this is a valid address
+ * it will be returned in getLocalhost() calls)
+ */
+ private static final String PROP_PREFERRED_NET_ADDRESS
+ = "net.java.sip.communicator.common.PREFERRED_NETWORK_ADDRESS";
+
+ /**
+ * the network interface that the user would like to use for fommunication
+ * (addresses belonging to that interface will be prefered when selecting a
+ * localhost address)
+ */
+ private static final String PROP_PREFERRED_NET_IFACE
+ = "net.java.sip.communicator.common.PREFERRED_NETWORK_INTERFACE";
+
+ /** The configuration service to use when retrieving conf property values*/
+ private ConfigurationService configurationService = null;
+
+ /** A stun4j address resolver */
+ private SimpleAddressDetector detector = null;
+
+ /** Specifies whether or not STUN should be used for NAT traversal */
+ private boolean useStun = true;
+ private static final int RANDOM_PORT = 55055;
+ private static final String WINDOWS_AUTO_CONFIGURED_ADDRESS_PREFIX = "169";
+
+ /**
+ * A default constructor.
+ *
+ * @param configurationService the configruation service that this address
+ * manager should use for retrieving configuration properties.
+ */
+ NetworkAddressManagerServiceImpl(ConfigurationService configurationService)
+ {
+ this.configurationService = configurationService;
+ }
+
+ /**
+ * Initializes this network address manager service implementation and
+ * starts all processes/threads associated with this address manager, such
+ * as a stun firewall/nat detector, keep alive threads, binding lifetime
+ * discovery threads and etc. The method may also be used after a call to
+ * stop() as a reinitialization technique.
+ */
+ public void start()
+ {
+ try
+ {
+ logger.logEntry();
+
+ // init stun
+ String stunAddressStr = null;
+ int port = -1;
+ stunAddressStr = configurationService.getString(
+ PROP_STUN_SERVER_ADDRESS);
+ String portStr = configurationService.getString(
+ PROP_STUN_SERVER_PORT);
+
+ //in case the user prefers ipv6 addresses we don't want to use
+ //stun
+ boolean preferIPv6Addresses = Boolean.getBoolean(
+ PROP_PREF_IPV6_ADDRS);
+
+ if (stunAddressStr == null
+ || portStr == null
+ || preferIPv6Addresses)
+ {
+ useStun = false;
+ //user doesn't want stun - bail out
+ return;
+ }
+
+ port = Integer.valueOf(portStr).intValue();
+
+ detector = new SimpleAddressDetector(
+ new StunAddress(stunAddressStr, port));
+
+ if (logger.isDebugEnabled())
+ logger.debug(
+ "Created a STUN Address detector for the following "
+ + "STUN server: "
+ + stunAddressStr + ":" + port);
+
+
+ try
+ {
+ detector.start();
+ logger.debug("STUN server started;");
+ }
+ catch (StunException ex)
+ {
+ logger.error(
+ "Failed to start the STUN Address Detector. " +
+ detector.toString());
+ logger.debug("Disabling stun and continuing bravely!");
+ detector = null;
+ useStun = false;
+ }
+
+ //make sure that someone doesn't set invalid stun address and port
+ configurationService.addVetoableChangeListener(
+ PROP_STUN_SERVER_ADDRESS, this);
+ configurationService.addVetoableChangeListener(
+ PROP_STUN_SERVER_PORT, this);
+
+ //don't register a property listener. reinitialization is supposed
+ //to only happen after a stop(), start() call seq
+ }
+ finally
+ {
+ logger.logExit();
+ }
+ }
+
+ /**
+ * Kills all threads/processes lauched by this thread and prepares it for
+ * shutdown. You may use this method as a reinitialization technique (
+ * you'll have to call start afterwards)
+ */
+ public void stop()
+ {
+ try
+ {
+ try{
+ detector.shutDown();
+ }catch (Exception ex){
+ logger.debug("Failed to properly shutdown a stun detector: "
+ +ex.getMessage());
+
+ }
+ detector = null;
+ useStun = false;
+
+ //remove the listeners
+ configurationService.removeVetoableChangeListener(
+ PROP_STUN_SERVER_ADDRESS, this);
+
+ configurationService.removeVetoableChangeListener(
+ PROP_STUN_SERVER_PORT, this);
+
+ }
+ finally
+ {
+ logger.logExit();
+ }
+
+ }
+
+ /**
+ * Returns an InetAddress instance that represents the localhost, and that
+ * a socket can bind upon.
+ *
+ * @return an InetAddress instance representing the local host. The returned
+ * value may also contain the "any" inet address (i.e. 0.0.0.0 or ::0)
+ */
+ public InetAddress getLocalHost()
+ {
+ return getLocalHost(true);
+ }
+
+
+ /**
+ * Returns an InetAddress instance that represents the localhost, and that
+ * a socket can bind upon.
+ *
+ * @param anyAddressIsAccepted are (0.0.0.0 / ::0) addresses accepted as a
+ * return value.
+ * @return the address that was detected the address of the localhost.
+ */
+ public InetAddress getLocalHost(boolean anyAddressIsAccepted)
+ {
+ try
+ {
+ logger.logEntry();
+ InetAddress localHost = null;
+ InetAddress mappedAddress = null;
+ InetAddress stunConfirmedAddress = null;
+ InetAddress linkLocalAddress = null;
+ InetAddress publicAddress = null;
+ String selectedInterface = null;
+
+ //let's check whether the user has any preferences concerning addrs
+ String preferredAddr =
+ configurationService.getString(PROP_PREFERRED_NET_ADDRESS);
+ String preferredIface =
+ configurationService.getString(PROP_PREFERRED_NET_IFACE);
+
+ boolean preferIPv4Stack = Boolean.getBoolean(PROP_PREF_IPV4_STACK);
+ boolean preferIPv6Addrs = Boolean.getBoolean(PROP_PREF_IPV6_ADDRS);
+
+ try
+ {
+ //check whether we have a public address that matches one of
+ //the local interfaces if not - return the first one that
+ //is not the loopback
+
+ //retrieve and store a STUN binding if possible
+ if (useStun)
+ {
+ StunAddress stunMappedAddress =
+ queryStunServer(RANDOM_PORT);
+
+ mappedAddress = (stunMappedAddress == null)
+ ? null
+ : stunMappedAddress.getSocketAddress().getAddress();
+ }
+
+ Enumeration localIfaces =
+ NetworkInterface.getNetworkInterfaces();
+
+ //do a loop over all addresses of all interfaces and return
+ //the first that we judge correct.
+
+ //interfaces loop
+ interfaces_loop:
+ while (localIfaces.hasMoreElements())
+ {
+ NetworkInterface iFace =
+ (NetworkInterface) localIfaces.nextElement();
+
+ Enumeration addresses = iFace.getInetAddresses();
+
+ //addresses loop
+ addresses_loop:
+ while (addresses.hasMoreElements()) {
+ InetAddress address =
+ (InetAddress) addresses.nextElement();
+ //ignore link local addresses
+ if (address.isAnyLocalAddress()
+ || address.isLinkLocalAddress()
+ || address.isLoopbackAddress()
+ || isWindowsAutoConfiguredIPv4Address(address))
+ {
+ //address is phony - go on to the next one
+ continue addresses_loop;
+ }
+ //see whether this is the address used in STUN communic.
+ if (mappedAddress != null
+ && mappedAddress.equals(address)) {
+ if (logger.isDebugEnabled())
+ logger.debug("Returninng localhost: Mapped "
+ + "address = Public address = "
+ + address);
+ //the addr matches the one seen by the STUN server
+ //no doubt that it's a working public
+ //address.
+
+ stunConfirmedAddress = address;
+ }
+ //link local addr
+ else if (isLinkLocalIPv4Address(address))
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("Found Linklocal ipv4 address "
+ + address);
+ linkLocalAddress = address;
+ }
+ //publicly routable addr
+ else {
+ if (logger.isDebugEnabled())
+ logger.debug("Found a public address "
+ + address);
+
+ //now befo we store this address, make sure we don't
+ //already have one that suits us better and bail out
+ //if this is the case
+
+ if (//we already have the address prefferred by user
+ ( publicAddress != null
+ && preferredAddr!= null
+ && preferredAddr.equals(publicAddress.
+ getHostAddress()))
+ //we already have an address on an iface
+ //preferred by the user
+ ||(publicAddress != null
+ && selectedInterface != null
+ && preferredIface != null
+ && preferredIface.equals(selectedInterface))
+ //in case we have an ipv4 addr and don't
+ //want to change it for an ipv6
+ ||(publicAddress != null
+ && publicAddress instanceof Inet4Address
+ && address instanceof Inet6Address
+ && preferIPv4Stack)
+ //in case we have an ipv6 addr and don't
+ //want to change it for an ipv4
+ ||(publicAddress != null
+ && publicAddress instanceof Inet6Address
+ && address instanceof Inet4Address
+ && !preferIPv4Stack)
+ )
+ {
+ continue;
+ }
+ publicAddress = address;
+ selectedInterface = iFace.getDisplayName();
+ }
+ }//addresses loop
+ }//interfaces loop
+
+ //if we have an address confirmed by STUN msg exchanges - we'll
+ //return it unless the user had really insisted on IPv6 addresses.
+ if(stunConfirmedAddress != null
+ && ! preferIPv6Addrs){
+ logger.debug("Returning stun confirmed address");
+ return stunConfirmedAddress;
+ }
+ //return the address that was selected during the loop above.
+ if (publicAddress != null) {
+ logger.debug("Returning public address");
+ return publicAddress;
+ }
+ if (linkLocalAddress != null) {
+ logger.debug("Returning link local address");
+ return linkLocalAddress;
+ }
+ if (anyAddressIsAccepted)
+ localHost = new InetSocketAddress(RANDOM_PORT).getAddress();
+ else
+ localHost = InetAddress.getLocalHost();
+ }
+ catch (Exception ex) {
+ logger.error("Failed to determine the localhost address, "
+ +"returning the any address (0.0.0.0/::0)", ex);
+ //get the address part of an InetSocketAddress for a random port.
+ localHost = new InetSocketAddress(RANDOM_PORT).getAddress();
+ }
+ if (logger.isDebugEnabled())
+ logger.debug("Returning localhost address=" + localHost);
+ return localHost;
+ }
+ finally
+ {
+ logger.logExit();
+ }
+ }
+
+ /**
+ * The method queries a Stun server for a binding for the specified port.
+ * @param port the port to resolve (the stun message gets sent trhough that
+ * port)
+ * @return StunAddress the address returned by the stun server or null
+ * if an error occurred or no address was returned
+ */
+ private StunAddress queryStunServer(int port)
+ {
+
+ try{
+ logger.logEntry();
+ StunAddress mappedAddress = null;
+ if (detector != null && useStun) {
+ try {
+ mappedAddress = detector.getMappingFor(port);
+ if (logger.isDebugEnabled())
+ logger.debug("For port:"
+ + port + "a Stun server returned the "
+ +"following mapping [" + mappedAddress);
+ }
+ catch (StunException ex) {
+ logger.error(
+ "Failed to retrive mapped address port:" +port, ex);
+ mappedAddress = null;
+ }
+ }
+ return mappedAddress;
+ }
+ finally{
+ logger.logExit();
+ }
+ }
+
+ /**
+ * Determines whether the address is the result of windows auto configuration.
+ * (i.e. One that is in the 169.254.0.0 network)
+ * @param add the address to inspect
+ * @return true if the address is autoconfigured by windows, false otherwise.
+ */
+ private static boolean isWindowsAutoConfiguredIPv4Address(InetAddress add)
+ {
+ return (add.getAddress()[0] & 0xFF) == 169
+ && (add.getAddress()[1] & 0xFF) == 254;
+ }
+
+ /**
+ * Determines whether the address is an IPv4 link local address. IPv4 link
+ * local addresses are those in the following networks:
+ *
+ * 10.0.0.0 to 10.255.255.255
+ * 172.16.0.0 to 172.31.255.255
+ * 192.168.0.0 to 192.168.255.255
+ *
+ * @param add the address to inspect
+ * @return true if add is a link local ipv4 address and false if not.
+ */
+ private static boolean isLinkLocalIPv4Address(InetAddress add)
+ {
+ if(add instanceof Inet4Address)
+ {
+ byte address[] = add.getAddress();
+ if ( (address[0] & 0xFF) == 10)
+ return true;
+ if ( (address[0] & 0xFF) == 172
+ && (address[1] & 0xFF) >= 16 && address[1] <= 31)
+ return true;
+ if ( (address[0] & 0xFF) == 192
+ && (address[1] & 0xFF) == 168)
+ return true;
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Tries to obtain a mapped/public address for the specified port (possibly
+ * by executing a STUN query).
+ *
+ * @param port the port whose mapping we are interested in.
+ * @return a public address corresponding to the specified port or null
+ * if all attempts to retrieve such an address have failed.
+ */
+ public InetSocketAddress getPublicAddressFor(int port)
+ {
+ try {
+ logger.logEntry();
+ if (!useStun) {
+ logger.debug(
+ "Stun is disabled, skipping mapped address recovery.");
+ return new InetSocketAddress(getLocalHost(), port);
+ }
+ StunAddress mappedAddress = queryStunServer(port);
+ InetSocketAddress result = null;
+ if (mappedAddress != null)
+ result = mappedAddress.getSocketAddress();
+ else {
+ //Apparently STUN failed. Let's try to temporarily disble it
+ //and use algorithms in getLocalHost(). ... We should probably
+ //eveng think about completely disabling stun, and not only
+ //temporarily.
+ //Bug report - John J. Barton - IBM
+ InetAddress localHost = getLocalHost(false);
+ result = new InetSocketAddress(localHost, port);
+ }
+ if (logger.isDebugEnabled())
+ logger.debug("Returning mapping for port:"
+ + port +" as follows: " + result);
+ return result;
+ }
+ finally {
+ logger.logExit();
+ }
+ }
+
+ /**
+ * This method gets called when a bound property is changed.
+ * @param evt A PropertyChangeEvent object describing the event source
+ * and the property that has changed.
+ */
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ //there's no point in implementing this method as we have no way of
+ //knowing whether the current property change event is the only event
+ //we're going to get or whether another one is going to follow..
+
+ //in the case of a STUN_SERVER_ADDRESS property change for example
+ //there's no way of knowing whether a STUN_SERVER_PORT property change
+ //will follow or not.
+
+ //Reinitializaion will therefore only happen if the reinitialize()
+ //method is called.
+ }
+
+ /**
+ * This method gets called when a property we're interested in is about to
+ * change. In case we don't like the new value we throw a
+ * PropertyVetoException to prevent the actual change from happening.
+ *
+ * @param evt a <code>PropertyChangeEvent</code> object describing the
+ * event source and the property that will change.
+ * @exception PropertyVetoException if we don't want the change to happen.
+ */
+ public void vetoableChange(PropertyChangeEvent evt) throws
+ PropertyVetoException
+ {
+ if (evt.getPropertyName().equals(PROP_STUN_SERVER_ADDRESS))
+ {
+ //make sure that we have a valid fqdn or ip address.
+
+ //null or empty port is ok since it implies turning STUN off.
+ if (evt.getNewValue() == null)
+ return;
+
+ String host = evt.getNewValue().toString();
+ if (host.trim().length() == 0)
+ return;
+
+ boolean ipv6Expected = false;
+ if (host.charAt(0) == '[')
+ {
+ // This is supposed to be an IPv6 litteral
+ if (host.length() > 2 &&
+ host.charAt(host.length() - 1) == ']')
+ {
+ host = host.substring(1, host.length() - 1);
+ ipv6Expected = true;
+ }
+ else
+ {
+ // This was supposed to be a IPv6 address, but it's not!
+ throw new PropertyVetoException(
+ "Invalid address string" + host, evt);
+ }
+ }
+
+ for(int i = 0; i < host.length(); i++)
+ {
+ char c = host.charAt(i);
+ if( Character.isLetterOrDigit(c))
+ continue;
+
+ if( (c != '.' && c!= ':')
+ ||( c == '.' && ipv6Expected)
+ ||( c == ':' && !ipv6Expected))
+ throw new PropertyVetoException(
+ host + " is not a valid address nor host name",
+ evt);
+ }
+
+ }//is prop_stun_server_address
+ else if (evt.getPropertyName().equals(PROP_STUN_SERVER_PORT)){
+
+ //null or empty port is ok since it implies turning STUN off.
+ if (evt.getNewValue() == null)
+ return;
+
+ String port = evt.getNewValue().toString();
+ if (port.trim().length() == 0)
+ return;
+
+ try
+ {
+ Integer.valueOf(evt.getNewValue().toString());
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new PropertyVetoException(
+ port + " is not a valid port! " + ex.getMessage(), evt);
+ }
+
+
+ }
+
+ }
+}