/* -*- Mode: java; c-basic-indent: 4; tab-width: 4 -*- */ package freenet.support.transport.ip; import java.net.DatagramSocket; import java.net.Inet6Address; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import freenet.io.AddressIdentifier; import freenet.node.NodeIPDetector; import freenet.support.Executor; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.io.InetAddressComparator; /** * A class to autodetect our IP address(es) */ public class IPAddressDetector implements Runnable { private static volatile boolean logDEBUG; static { Logger.registerClass(IPAddressDetector.class); } //private String preferedAddressString = null; private final long interval; private final NodeIPDetector detector; /** * * @param interval * @param detector */ public IPAddressDetector(long interval, NodeIPDetector detector) { this.interval = interval; this.detector = detector; } /** * @return our name */ public String getCheckpointName() { return "Autodetection of IP addresses"; } /** * @return next scheduling point */ public long nextCheckpoint() { return System.currentTimeMillis() + interval; // We are pretty cheap } InetAddress[] lastAddressList = null; long lastDetectedTime = -1; /** Fetch the currently detected IP address. If not detected yet, run the * detection. DO NOT callback to detector.redetectAddresses(). * @return */ public InetAddress[] getAddressNoCallback() { if(System.currentTimeMillis() > (lastDetectedTime + interval)) { checkpoint(); } return lastAddressList == null ? new InetAddress[0] : lastAddressList; } /** * Fetches the currently detected IP address. If not detected yet a detection is forced. * If the IP address list changes, call the callback on the detector, off-thread, using the * given Executor. This method is intended to be called by code other than the detector * itself. * @return Detected ip addresses */ public InetAddress[] getAddress(Executor executor) { assert(executor != null); if(System.currentTimeMillis() > (lastDetectedTime + interval)) { if(checkpoint()) { executor.execute(new Runnable() { @Override public void run() { detector.redetectAddress(); } }); } } return lastAddressList == null ? new InetAddress[0] : lastAddressList; } boolean old = false; /** * Execute a checkpoint - detect our internet IP address and log it */ protected synchronized boolean checkpoint() { final boolean logDEBUG = IPAddressDetector.logDEBUG; List<InetAddress> addrs = new ArrayList<InetAddress>(); Enumeration<java.net.NetworkInterface> interfaces = null; try { interfaces = java.net.NetworkInterface.getNetworkInterfaces(); } catch (SocketException e) { Logger.error( this, "SocketException trying to detect NetworkInterfaces: "+e, e); addrs.add(oldDetect()); old = true; } if (!old) { while (interfaces.hasMoreElements()) { java.net.NetworkInterface iface = interfaces.nextElement(); if (logDEBUG) Logger.debug( this, "Scanning NetworkInterface " + iface.getDisplayName()); int ifaceMTU = 0; try { if (!iface.isLoopback()) { ifaceMTU = iface.getMTU(); //MTU is retrieved directly instead of using //a plugin if (logDEBUG) Logger.debug( this, "MTU = " + ifaceMTU); } } catch (SocketException e) { Logger.error( this, "SocketException trying to retrieve the MTU NetworkInterfaces: "+e, e); ifaceMTU = 0; //code for ignoring this MTU } Enumeration<InetAddress> ee = iface.getInetAddresses(); while (ee.hasMoreElements()) { InetAddress addr = ee.nextElement(); //telling the NodeIPDetector object about the MTU only if MTU != 0 // MTU = 0 means error in retrieving it //FIXME: We should(n't) report MTU for local IPs if (ifaceMTU > 0) detector.reportMTU(ifaceMTU, addr instanceof Inet6Address); if ((addr instanceof Inet6Address) && !(addr.isLinkLocalAddress() || IPUtil.isSiteLocalAddress(addr))) { try { // strip scope_id from global addresses addr = InetAddress.getByAddress(addr.getAddress()); } catch(UnknownHostException e) { // ignore/impossible } } addrs.add(addr); if (logDEBUG) Logger.debug( this, "Adding address " + addr + " from " + iface.getDisplayName()); } if (logDEBUG) Logger.debug( this, "Finished scanning interface " + iface.getDisplayName()); } if (logDEBUG) Logger.debug( this, "Finished scanning interfaces"); } InetAddress[] oldAddressList = lastAddressList; onGetAddresses(addrs); lastDetectedTime = System.currentTimeMillis(); return addressListChanged(oldAddressList, lastAddressList); } private boolean addressListChanged(InetAddress[] oldList, InetAddress[] newList) { if(oldList == null) return newList != null; if(oldList == newList) return false; if(oldList.length != newList.length) return true; InetAddress[] a = Arrays.copyOf(oldList, oldList.length); InetAddress[] b = Arrays.copyOf(newList, newList.length); Arrays.sort(a, InetAddressComparator.COMPARATOR); Arrays.sort(b, InetAddressComparator.COMPARATOR); return !Arrays.deepEquals(a, b); } /** * * @return */ protected InetAddress oldDetect() { boolean shouldLog = Logger.shouldLog(LogLevel.DEBUG, this); if (shouldLog) Logger.debug( this, "Running old style detection code"); DatagramSocket ds = null; try { try { ds = new DatagramSocket(); } catch (SocketException e) { Logger.error(this, "SocketException", e); return null; } // This does not transfer any data // The ip is a.root-servers.net, 53 is DNS try { ds.connect(InetAddress.getByName("198.41.0.4"), 53); } catch (UnknownHostException ex) { Logger.error(this, "UnknownHostException", ex); return null; } return ds.getLocalAddress(); } finally { if (ds != null) { ds.close(); } } } /** * Do something with the list of detected IP addresses. * * @param addrs * Vector of InetAddresses */ protected void onGetAddresses(List<InetAddress> addrs) { final boolean logDEBUG = IPAddressDetector.logDEBUG; List<InetAddress> output = new ArrayList<InetAddress>(); if (logDEBUG) Logger.debug( this, "onGetAddresses found " + addrs.size() + " potential addresses)"); if (addrs.size() == 0) { Logger.error(this, "No addresses found!"); lastAddressList = null; return; } else { // InetAddress lastNonValidAddress = null; for (int x = 0; x < addrs.size(); x++) { if (addrs.get(x) != null) { InetAddress i = addrs.get(x); if (logDEBUG) Logger.debug( this, "Address " + x + ": " + i); if(i.isAnyLocalAddress()) { // Wildcard address, 0.0.0.0, ignore. } else if(i.isLinkLocalAddress() || i.isLoopbackAddress() || i.isSiteLocalAddress()) { // Will be filtered out later if necessary. output.add(i); } else if(i.isMulticastAddress()) { // Ignore } else { // Ignore ISATAP addresses // @see http://archives.freenetproject.org/message/20071129.220955.ac2a2a36.en.html if(!AddressIdentifier.isAnISATAPIPv6Address(i.toString())) output.add(i); } } } } lastAddressList = output.toArray(new InetAddress[output.size()]); } @Override public void run() { freenet.support.Logger.OSThread.logPID(this); while(true) { try { Thread.sleep(interval); } catch (InterruptedException e) { // Ignore } try { if(checkpoint()) { detector.redetectAddress(); } } catch (Throwable t) { Logger.error(this, "Caught "+t, t); } } } /** * */ public void clearCached() { lastAddressList = null; lastDetectedTime = -1; } }