/** * 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() { // init stun String stunAddressStr = null; int port = -1; stunAddressStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_ADDRESS); String portStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_PORT); this.localHostFinderSocket = initRandomPortSocket(); if (stunAddressStr == null || portStr == null) { useStun = false; // we use the default stun server address only for chosing a public // route and not for stun queries. stunServerAddress = new StunAddress(DEFAULT_STUN_SERVER_ADDRESS, DEFAULT_STUN_SERVER_PORT); logger.info( "Stun server address(" + stunAddressStr + ")/port(" + portStr + ") not set (or invalid). Disabling STUN."); } else { try { port = Integer.valueOf(portStr).intValue(); } catch (NumberFormatException ex) { logger.error(portStr + " is not a valid port number. " + "Defaulting to 3478", ex); port = 3478; } stunServerAddress = new StunAddress(stunAddressStr, port); detector = new SimpleAddressDetector(stunServerAddress); if (logger.isDebugEnabled()) { logger.debug( "Created a STUN Address detector for the following " + "STUN server: " + stunAddressStr + ":" + port); } detector.start(); logger.debug("STUN server detector started;"); // make sure that someone doesn't set invalid stun address and port NetaddrActivator.getConfigurationService() .addVetoableChangeListener(PROP_STUN_SERVER_ADDRESS, this); NetaddrActivator.getConfigurationService() .addVetoableChangeListener(PROP_STUN_SERVER_PORT, this); // now start a thread query to the stun server and only set the // useStun flag to true if it succeeds. launchStunServerTest(); } }
/** * Initializes and binds a socket that on a random port number. The method would try to bind on a * random port and retry 5 times until a free port is found. * * @return the socket that we have initialized on a randomport number. */ private DatagramSocket initRandomPortSocket() { DatagramSocket resultSocket = null; String bindRetriesStr = NetaddrActivator.getConfigurationService().getString(BIND_RETRIES_PROPERTY_NAME); int bindRetries = 5; if (bindRetriesStr != null) { try { bindRetries = Integer.parseInt(bindRetriesStr); } catch (NumberFormatException ex) { logger.error( bindRetriesStr + " does not appear to be an integer. " + "Defaulting port bind retries to " + bindRetries, ex); } } int currentlyTriedPort = NetworkUtils.getRandomPortNumber(); // we'll first try to bind to a random port. if this fails we'll try // again (bindRetries times in all) until we find a free local port. for (int i = 0; i < bindRetries; i++) { try { resultSocket = new DatagramSocket(currentlyTriedPort); // we succeeded - break so that we don't try to bind again break; } catch (SocketException exc) { if (exc.getMessage().indexOf("Address already in use") == -1) { logger.fatal( "An exception occurred while trying to create" + "a local host discovery socket.", exc); resultSocket = null; return null; } // port seems to be taken. try another one. logger.debug("Port " + currentlyTriedPort + " seems in use."); currentlyTriedPort = NetworkUtils.getRandomPortNumber(); logger.debug("Retrying bind on port " + currentlyTriedPort); } } return resultSocket; }
/** The diagnostics code itself. */ public void run() { logger.debug("Started a diag kit for entry: " + addressEntry); // implements the algorithm from AssigningAddressPreferences.png setDiagnosticsStatus(this.DIAGNOSTICS_STATUS_DISOVERING_CONFIG); InetAddress address = addressEntry.getInetAddress(); // is this an ipv6 address if (addressEntry.isIPv6()) { if (addressEntry.isLinkLocal()) { addressEntry.setAddressPreference(ADDR_PREF_LOCAL_IPV6); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } if (addressEntry.is6to4()) { // right now we don't support these. we should though ... one day addressEntry.setAddressPreference(AddressPreference.MIN); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } // if we get here then we are a globally routable ipv6 addr addressEntry.setAddressPreference(ADDR_PREF_GLOBAL_IPV6); setDiagnosticsStatus(DIAGNOSTICS_STATUS_COMPLETED); // should do some connectivity testing here and proceed with firewall // discovery but since stun4j does not support ipv6 yet, this too // will happen another day. return; } // from now on we're only dealing with IPv4 if (addressEntry.isIPv4LinkLocalAutoconf()) { // not sure whether these are used for anything. addressEntry.setAddressPreference(AddressPreference.MIN); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } // first try and see what we can infer from just looking at the // address if (addressEntry.isLinkLocalIPv4Address()) { addressEntry.setAddressPreference(ADDR_PREF_PRIVATE_IPV4); } else { // public address addressEntry.setAddressPreference(ADDR_PREF_GLOBAL_IPV4); } if (!useStun) { // if we're configured not to run stun - we're done. setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } // start stunning for (int i = 0; i < bindRetries; i++) { StunAddress localStunAddress = new StunAddress(address, 1024 + (int) (Math.random() * 64512)); try { stunClient = new StunClient(localStunAddress); stunClient.start(); logger.debug("Successfully started StunClient for " + localStunAddress + "."); break; } catch (StunException ex) { if (ex.getCause() instanceof SocketException && i < bindRetries) { logger.debug("Failed to bind to " + localStunAddress + ". Retrying ..."); logger.debug("Exception was ", ex); continue; } logger.error( "Failed to start a stun client for address entry [" + addressEntry.toString() + "]:" + localStunAddress.getPort() + ". Ceasing attempts", ex); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } } // De Stun Test I StunMessageEvent event = null; try { event = stunClient.doStunTestI(primaryStunServerAddress); } catch (StunException ex) { logger.error("Failed to perform STUN Test I for address entry" + addressEntry.toString(), ex); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } if (event == null) { // didn't get a response - we either don't have connectivity or the // server is down /** @todo if possible try another stun server here. we should support multiple stun servers */ logger.debug("There seems to be no inet connectivity for " + addressEntry); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); logger.debug("stun test 1 failed"); return; } // the moment of the truth - are we behind a NAT? boolean isPublic; Message stunResponse = event.getMessage(); Attribute mappedAttr = stunResponse.getAttribute(Attribute.MAPPED_ADDRESS); StunAddress mappedAddrFromTestI = ((MappedAddressAttribute) mappedAttr).getAddress(); Attribute changedAddressAttributeFromTestI = stunResponse.getAttribute(Attribute.CHANGED_ADDRESS); StunAddress secondaryStunServerAddress = ((ChangedAddressAttribute) changedAddressAttributeFromTestI).getAddress(); /** * @todo verify whether the stun server returned the same address for the primary and secondary * server and act accordingly */ if (mappedAddrFromTestI == null) { logger.error( "Stun Server did not return a mapped address for entry " + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); return; } if (mappedAddrFromTestI.equals(event.getSourceAccessPoint().getAddress())) { isPublic = true; } else { isPublic = false; } // do STUN Test II try { event = stunClient.doStunTestII(primaryStunServerAddress); } catch (StunException ex) { logger.error( "Failed to perform STUN Test II for address entry" + addressEntry.toString(), ex); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); logger.debug("stun test 2 failed"); return; } if (event != null) { logger.error("Secondary STUN server is down" + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } // might mean that either the secondary stun server is down // or that we are behind a restrictive firewall. Let's find out // which. try { event = stunClient.doStunTestI(secondaryStunServerAddress); logger.debug("stun test 1 succeeded with s server 2"); } catch (StunException ex) { logger.error("Failed to perform STUN Test I for address entry" + addressEntry.toString(), ex); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } if (event == null) { // secondary stun server is down logger.error("Secondary STUN server is down" + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } // we are at least behind a port restricted nat stunResponse = event.getMessage(); mappedAttr = stunResponse.getAttribute(Attribute.MAPPED_ADDRESS); StunAddress mappedAddrFromSecServer = ((MappedAddressAttribute) mappedAttr).getAddress(); if (!mappedAddrFromTestI.equals(mappedAddrFromSecServer)) { // secondary stun server is down logger.debug("We are behind a symmetric nat" + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } // now let's run test III so that we could guess whether or not we're // behind a port restricted nat/fw or simply a restricted one. try { event = stunClient.doStunTestIII(primaryStunServerAddress); logger.debug("stun test 3 succeeded with s server 1"); } catch (StunException ex) { logger.error( "Failed to perform STUN Test III for address entry" + addressEntry.toString(), ex); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } if (event == null) { logger.debug("We are behind a port restricted NAT or fw" + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); return; } logger.debug("We are behind a restricted NAT or fw" + addressEntry.toString()); setDiagnosticsStatus(DIAGNOSTICS_STATUS_TERMINATED); stunClient.shutDown(); }