package com.pugh.sockso.web; import com.pugh.sockso.Constants; import com.pugh.sockso.Options; import com.pugh.sockso.Properties; import com.pugh.sockso.Utils; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.net.URL; import java.net.HttpURLConnection; import java.net.UnknownHostException; import java.net.SocketTimeoutException; import java.util.Date; import java.util.regex.Pattern; import java.util.regex.Matcher; import joptsimple.OptionSet; import org.apache.log4j.Logger; import com.google.inject.Inject; /** * This class is responsible for finding the IP address to use for Sockso * */ public class IpFinder { public static Logger log = Logger.getLogger( IpFinder.class ); public static final String LOOPBACK = "127.0.0.1"; private final OptionSet options; private final Properties p; private String ip; /** * Constructor, with OptionSet of command line args * * @param p * @param options * */ @Inject public IpFinder( final Properties p, final OptionSet options ) { this.options = options; this.p = p; ip = LOOPBACK; } /** * Inits the object, loading any stored info, reading options, etc... * This needs to be called before the object is used. * */ public void init() { ip = getIpFromOptions(); if ( ip == null ) { new Thread() { @Override public void run() { while ( true ) { update(); try { Thread.sleep(Constants.SERVER_IP_TIMEOUT); } catch ( final InterruptedException e ) { /* nothing */ } } } }.start(); } } /** * Refreshes the IP, but invalidates cache first so it's not used. This * is a "hard" refresh. * */ public void refresh() { p.set( Constants.SERVER_HOST_LAST_UPDATED, 0 ); update(); } /** * Refreshes the IP by first looking for it saved in properties, then * trying to fetch ip from internet, then using local lan ip, then finally * using loopback. * */ protected void update() { ip = null; // 1. from properties if ( ip == null ) { ip = getIpFromProperties(); } // 2. from internet if ( ip == null ) { ip = getIpFromInternet(); } // 3. from intranet if ( ip == null ) { ip = getIpFromIntranet(); } // 4. otherwise loopback if ( ip == null ) { ip = LOOPBACK; } save(); log.debug( "Using IP " +ip ); } /** * saves the ip to the properties, also saving the time so we can work out * when the ip has gone stale. * */ protected void save() { p.set( Constants.SERVER_HOST, ip ); p.set( Constants.SERVER_HOST_LAST_UPDATED, Long.toString(new Date().getTime()) ); p.save(); } /** * Tries to fetch the IP address from the options, returns null if nothing * was specified. * * @return * */ protected String getIpFromOptions() { log.debug( "Get IP from options" ); return ( options != null && options.has(Options.OPT_IP) ) ? options.valueOf( Options.OPT_IP ).toString() : null; } /** * Tried to fetch the IP address from the properties. If it's timeout has * expired then null is returned. * * @return * */ protected String getIpFromProperties() { log.debug( "Get IP from properties" ); final String possibleIp = p.get( Constants.SERVER_HOST ); final long lastUpdated = p.get( Constants.SERVER_HOST_LAST_UPDATED, 0 ); final long ipTimeout = new Date().getTime() - Constants.SERVER_IP_TIMEOUT; return ( possibleIp.equals("") || lastUpdated < ipTimeout ) ? null : possibleIp; } /** * tries to get the global ip address from whatismyip.org, returns a boolean * indicating success or not * * @return success or not * */ private String getIpFromInternet() { log.debug( "Get IP from internet" ); try { final String natUrl = Constants.WEBSITE_URL + "/nat/ip/"; log.info( "Fetching IP from " + natUrl ); final URL url = new URL( natUrl ); final HttpURLConnection cnn = (HttpURLConnection) url.openConnection(); cnn.setRequestMethod( "GET" ); return getIpFromUrl( cnn ); } catch ( final SocketTimeoutException e ) { log.warn( e ); } catch ( final UnknownHostException e ) { // this is ok? just no inet? log.warn( e ); } catch ( final IOException e ) { log.warn( e ); } return null; } /** * tries to fetch the local ip address, returns a boolean indicating if this * went well. * * @return success * */ private String getIpFromIntranet() { log.debug( "Get IP from intranet" ); try { return Utils.getLocalIp(); } catch ( UnknownHostException e ) { log.warn(e); } return null; } /** * tries to read an IP address from a url * * @param cnn * * @return * * @throws java.io.IOException * */ protected String getIpFromUrl( final HttpURLConnection cnn ) throws IOException { BufferedReader in = null; try { // read input and check format matches an IP address in = new BufferedReader( new InputStreamReader(cnn.getInputStream()) ); final String s = in.readLine(); if ( !isValidIpFormat(s) ) { throw new IOException( "Invalid response: " + s ); } return s; } finally { Utils.close(in); } } /** * Checks if a string is in a valid IP address format * * @param possibleIp * * @return * */ protected boolean isValidIpFormat( final String possibleIp ) { final Matcher m = Pattern .compile( "\\d+.\\d+.\\d+.\\d+" ) .matcher( possibleIp ); return m.matches(); } /** * Returns the current ip address we have * * @return * */ public String getIp() { return ip; } }