diff options
author | Emil Ivov <emcho@jitsi.org> | 2005-11-02 14:08:43 +0000 |
---|---|---|
committer | Emil Ivov <emcho@jitsi.org> | 2005-11-02 14:08:43 +0000 |
commit | c82e7e1327fd89ec30929a7d4e529039b751bf41 (patch) | |
tree | b2694122644e90a21bd0348f81a53a45841a37dd /src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java | |
parent | 462770b17bdbdffcf61b8862635738e46d58e505 (diff) | |
download | jitsi-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.java | 634 |
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); + } + + + } + + } +} |