Package freenet.node

Source Code of freenet.node.NodeIPDetector

package freenet.node;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import freenet.config.InvalidConfigValueException;
import freenet.config.SubConfig;
import freenet.io.comm.FreenetInetAddress;
import freenet.io.comm.Peer;
import freenet.io.comm.UdpSocketHandler;
import freenet.l10n.NodeL10n;
import freenet.node.useralerts.IPUndetectedUserAlert;
import freenet.node.useralerts.InvalidAddressOverrideUserAlert;
import freenet.node.useralerts.SimpleUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.pluginmanager.DetectedIP;
import freenet.pluginmanager.FredPluginBandwidthIndicator;
import freenet.pluginmanager.FredPluginIPDetector;
import freenet.pluginmanager.FredPluginPortForward;
import freenet.support.HTMLNode;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.api.StringCallback;
import freenet.support.io.NativeThread;
import freenet.support.transport.ip.HostnameSyntaxException;
import freenet.support.transport.ip.IPAddressDetector;
import freenet.support.transport.ip.IPUtil;

/**
* Detect the IP address of the node. Doesn't return port numbers, doesn't have access to per-port
* information (NodeCrypto - UdpSocketHandler etc).
*/
public class NodeIPDetector {
  private static volatile boolean logMINOR;
  private static volatile boolean logDEBUG;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
        logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
      }
    });
  }

  /** Parent node */
  final Node node;
  /** Ticker */
  /** Explicit forced IP address */
  FreenetInetAddress overrideIPAddress;
  /** Explicit forced IP address in string form because we want to keep it even if it's invalid and therefore unused */
  String overrideIPAddressString;
  /** IP address from last time */
  FreenetInetAddress oldIPAddress;
  /** Detected IP's and their NAT status from plugins */
  DetectedIP[] pluginDetectedIPs;
  /** Last detected IP address */
  FreenetInetAddress[] lastIPAddress;
 
  private class MinimumMTU {
   
    /** The minimum reported MTU on all detected interfaces */
    private int minimumMTU = Integer.MAX_VALUE;

    /** Report a new MTU from an interface or detector.
     * If the minimum MTU has changed, returns true. */
    boolean report(int mtu) {
      if(mtu <= 0) return false;
      if(mtu < minimumMTU) {
        Logger.normal(this, "Reducing the MTU to "+minimumMTU);
        minimumMTU = mtu;
        return true;
      }
      return false;
    }

    public int get() {
      return minimumMTU > 0 ? minimumMTU : 1500;
    }

  }
 
  private final MinimumMTU minimumMTUIPv4 = new MinimumMTU();
  private final MinimumMTU minimumMTUIPv6 = new MinimumMTU();
 
  /** IP address detector */
  private final IPAddressDetector ipDetector;
  /** Plugin manager for plugin IP address detectors e.g. STUN */
  final IPDetectorPluginManager ipDetectorManager;
  /** UserAlert shown when ipAddressOverride has a hostname/IP address syntax error */
  private InvalidAddressOverrideUserAlert invalidAddressOverrideAlert;
  private boolean hasValidAddressOverride;
  /** UserAlert shown when we can't detect an IP address */
  private IPUndetectedUserAlert primaryIPUndetectedAlert;
  // FIXME redundant? see lastIPAddress
  FreenetInetAddress[] lastIP;
  /** Set when we have grounds to believe that we may be behind a symmetric NAT. */
  boolean maybeSymmetric;
  /** Have the detector plugins been queried (or found to be non-existent)? */
  private boolean hasDetectedPM;
  /** Have we checked peers and local interfaces for our IP address? */
  private boolean hasDetectedIAD;
  /** Subsidiary detectors: NodeIPPortDetector's which rely on this object */
  private NodeIPPortDetector[] portDetectors;
  private boolean hasValidIP;
  private boolean firstDetection = true;
 
  SimpleUserAlert maybeSymmetricAlert;
 
  public NodeIPDetector(Node node) {
    this.node = node;
    ipDetectorManager = new IPDetectorPluginManager(node, this);
    ipDetector = new IPAddressDetector(SECONDS.toMillis(10), this);
    invalidAddressOverrideAlert = new InvalidAddressOverrideUserAlert(node);
    primaryIPUndetectedAlert = new IPUndetectedUserAlert(node);
    portDetectors = new NodeIPPortDetector[0];
  }

  public synchronized void addPortDetector(NodeIPPortDetector detector) {
    portDetectors = Arrays.copyOf(portDetectors, portDetectors.length+1);
    portDetectors[portDetectors.length - 1] = detector;
  }
 
  /**
   * What is my IP address? Use all globally available information (everything which isn't
   * specific to a given port i.e. opennet or darknet) to determine our current IP addresses.
   * Will include more than one IP in many cases when we are not strictly multi-homed. For
   * example, if we have a DNS name set, we will usually return an IP as well.
   *
   * Will warn the user with a UserAlert if we don't have sufficient information.
   */
  FreenetInetAddress[] detectPrimaryIPAddress(boolean dumpLocalAddresses) {
    boolean addedValidIP = false;
    Logger.minor(this, "Redetecting IPs...");
    ArrayList<FreenetInetAddress> addresses = new ArrayList<FreenetInetAddress>();
    if(overrideIPAddress != null) {
      // If the IP is overridden and the override is valid, the override has to be the first element.
      // overrideIPAddress will be null if the override is invalid
      addresses.add(overrideIPAddress);
      if(overrideIPAddress.isRealInternetAddress(false, true, false))
        addedValidIP = true;
    }
   
    if(!node.dontDetect()) {
      addedValidIP |= innerDetect(addresses);
    }
   
       if(node.clientCore != null) {
         boolean hadValidIP;
         synchronized(this) {
           hadValidIP = hasValidIP;
           hasValidIP = addedValidIP;
           if(firstDetection) {
             hadValidIP = !addedValidIP;
             firstDetection = false;
           }
         }
         if(hadValidIP != addedValidIP) {
           if (addedValidIP) {
             if(logMINOR) Logger.minor(this, "Got valid IP");
             onAddedValidIP();
           } else {
             if(logMINOR) Logger.minor(this, "No valid IP");
             onNotAddedValidIP();
           }
         }
       } else if(logMINOR)
         Logger.minor(this, "Client core not loaded");
       synchronized(this) {
         hasValidIP = addedValidIP;
       }
       lastIPAddress = addresses.toArray(new FreenetInetAddress[addresses.size()]);
       if(dumpLocalAddresses) {
         ArrayList<FreenetInetAddress> filtered = new ArrayList<FreenetInetAddress>(lastIPAddress.length);
         for(FreenetInetAddress addr: lastIPAddress) {
           if(addr == null) continue;
           if(addr == overrideIPAddress && addr.hasHostnameNoIP())
             filtered.add(addr);
           else if(addr.hasHostnameNoIP()) continue;
           else if(IPUtil.isValidAddress(addr.getAddress(), false))
             filtered.add(addr);
         }
         return filtered.toArray(new FreenetInetAddress[filtered.size()]);
       }
       return lastIPAddress;
  }
 
  boolean hasValidIP() {
    synchronized(this) {
      return hasValidIP;
    }
  }
 
  private void onAddedValidIP() {
    node.clientCore.alerts.unregister(primaryIPUndetectedAlert);
    node.onAddedValidIP();
  }
 
  private void onNotAddedValidIP() {
    node.clientCore.alerts.register(primaryIPUndetectedAlert);
  }
 
  /**
   * Core of the IP detection algorithm.
   * @param addresses
   * @param addedValidIP
   * @return
   */
  private boolean innerDetect(List<FreenetInetAddress> addresses) {
    boolean addedValidIP = false;
    InetAddress[] detectedAddrs = ipDetector.getAddressNoCallback();
    assert(detectedAddrs != null);
    synchronized(this) {
      hasDetectedIAD = true;
    }
    for(InetAddress detectedAddr: detectedAddrs) {
      FreenetInetAddress addr = new FreenetInetAddress(detectedAddr);
      if(!addresses.contains(addr)) {
        Logger.normal(this, "Detected IP address: "+addr);
        addresses.add(addr);
        if(addr.isRealInternetAddress(false, false, false))
          addedValidIP = true;
      }
    }
   
    if((pluginDetectedIPs != null) && (pluginDetectedIPs.length > 0)) {
      for(DetectedIP pluginDetectedIP: pluginDetectedIPs) {
        InetAddress addr = pluginDetectedIP.publicAddress;
        if(addr == null) continue;
        FreenetInetAddress a = new FreenetInetAddress(addr);
        if(!addresses.contains(a)) {
          Logger.normal(this, "Plugin detected IP address: "+a);
          addresses.add(a);
          if(a.isRealInternetAddress(false, false, false))
            addedValidIP = true;
        }
      }
    }
   
    boolean hadAddedValidIP = addedValidIP;
   
    int confidence = 0;
   
    // Try to pick it up from our connections
    if(node.peers != null) {
      PeerNode[] peerList = node.peers.myPeers();
      HashMap<FreenetInetAddress,Integer> countsByPeer = new HashMap<FreenetInetAddress,Integer>();
      // FIXME use a standard mutable int object, we have one somewhere
      for(PeerNode pn: peerList) {
        if(!pn.isConnected()) {
          if(logDEBUG) Logger.minor(this, "Not connected");
          continue;
        }
        if(!pn.isRealConnection()) {
          // Only let seed server connections through.
          // We have to trust them anyway.
          if(!(pn instanceof SeedServerPeerNode)) continue;
          if(logMINOR) Logger.minor(this, "Not a real connection and not a seed node: "+pn);
        }
        if(logMINOR) Logger.minor(this, "Maybe a usable connection for IP: "+pn);
        Peer p = pn.getRemoteDetectedPeer();
        if(logMINOR) Logger.minor(this, "Remote detected peer: "+p);
        if(p == null || p.isNull()) continue;
        FreenetInetAddress addr = p.getFreenetAddress();
        if(logMINOR) Logger.minor(this, "Address: "+addr);
        if(addr == null) continue;
        if(!IPUtil.isValidAddress(addr.getAddress(false), false)) {
          if(logMINOR) Logger.minor(this, "Address not valid");
          continue;
        }
        if(logMINOR)
          Logger.minor(this, "Peer "+pn.getPeer()+" thinks we are "+addr);
        if(countsByPeer.containsKey(addr)) {
          countsByPeer.put(addr, countsByPeer.get(addr) + 1);
        } else {
          countsByPeer.put(addr, 1);
        }
      }
      if(countsByPeer.size() == 1) {
                Entry<FreenetInetAddress, Integer> countByPeer = countsByPeer.entrySet().iterator().next();
        FreenetInetAddress addr = countByPeer.getKey();
        confidence = countByPeer.getValue();
        Logger.minor(this, "Everyone agrees we are "+addr);
        if(!addresses.contains(addr)) {
          if(addr.isRealInternetAddress(false, false, false))
            addedValidIP = true;
          addresses.add(addr);
        }
      } else if(countsByPeer.size() > 1) {
        // Take two most popular addresses.
        FreenetInetAddress best = null;
        FreenetInetAddress secondBest = null;
        int bestPopularity = 0;
        int secondBestPopularity = 0;
        for(Map.Entry<FreenetInetAddress,Integer> entry : countsByPeer.entrySet()) {
          FreenetInetAddress cur = entry.getKey();
          int curPop = entry.getValue();
          Logger.minor(this, "Detected peer: "+cur+" popularity "+curPop);
          if(curPop >= bestPopularity) {
            secondBestPopularity = bestPopularity;
            bestPopularity = curPop;
            secondBest = best;
            best = cur;
          }
        }
        if(best != null) {
          boolean hasRealDetectedAddress = false;
          for(InetAddress detectedAddr: detectedAddrs) {
            if(IPUtil.isValidAddress(detectedAddr, false))
              hasRealDetectedAddress = true;
          }
          if((bestPopularity > 1) || !hasRealDetectedAddress) {
             if(!addresses.contains(best)) {
              Logger.minor(this, "Adding best peer "+best+" ("+bestPopularity+ ')');
              addresses.add(best);
              if(best.isRealInternetAddress(false, false, false))
                addedValidIP = true;
            }
             confidence = bestPopularity;
            if((secondBest != null) && (secondBestPopularity > 1)) {
              if(!addresses.contains(secondBest)) {
                Logger.minor(this, "Adding second best peer "+secondBest+" ("+secondBest+ ')');
                addresses.add(secondBest);
                if(secondBest.isRealInternetAddress(false, false, false))
                  addedValidIP = true;
              }
            }
          }
        }
      }
    }
   
    // Add the old address only if we have no choice, or if we only have the word of two peers to go on.
    if((!(hadAddedValidIP || confidence > 2)) && (oldIPAddress != null) && !oldIPAddress.equals(overrideIPAddress)) {
      addresses.add(oldIPAddress);
      // Don't set addedValidIP.
      // There is an excellent chance that this is out of date.
      // So we still want to nag the user, until we have some confirmation.
    }
   
       return addedValidIP;
  }
 
  private String l10n(String key) {
    return NodeL10n.getBase().getString("NodeIPDetector."+key);
  }

  private String l10n(String key, String pattern, String value) {
    return NodeL10n.getBase().getString("NodeIPDetector."+key, pattern, value);
  }

  FreenetInetAddress[] getPrimaryIPAddress(boolean dumpLocal) {
    if(lastIPAddress == null) return detectPrimaryIPAddress(dumpLocal);
    return lastIPAddress;
  }
 
  public boolean hasDirectlyDetectedIP() {
    InetAddress[] addrs = ipDetector.getAddress(node.executor);
    if(addrs == null || addrs.length == 0) return false;
    for(InetAddress addr: addrs) {
      if(IPUtil.isValidAddress(addr, false)) {
        if(logMINOR)
          Logger.minor(this, "Has a directly detected IP: "+addr);
        return true;
      }
    }
    return false;
  }

  /**
   * Process a list of DetectedIP's from the IP detector plugin manager.
   * DetectedIP's can tell us what kind of NAT we are behind as well as our public
   * IP address.
   */
  public void processDetectedIPs(DetectedIP[] list) {
    pluginDetectedIPs = list;
    boolean mtuChanged = false;
    for(DetectedIP pluginDetectedIP: pluginDetectedIPs)
        reportMTU(pluginDetectedIP.mtu, pluginDetectedIP.publicAddress instanceof Inet6Address);
    redetectAddress();
  }

  /**
   * Is called by IPAddressDetector to inform NodeIPDetector about the MTU
   * associated to this interface
   */
        public void reportMTU(int mtu, boolean forIPv6) {
      boolean mtuChanged = false;
      if(forIPv6)
    mtuChanged |= minimumMTUIPv6.report(mtu);
      else 
    mtuChanged |= minimumMTUIPv4.report(mtu);

      if (mtuChanged) node.updateMTU();
        }

  public void redetectAddress() {
    FreenetInetAddress[] newIP = detectPrimaryIPAddress(false);
    NodeIPPortDetector[] detectors;
    synchronized(this) {
      if(Arrays.equals(newIP, lastIP)) return;
      lastIP = newIP;
      detectors = portDetectors;
    }
    for(NodeIPPortDetector detector: detectors)
      detector.update();
    node.writeNodeFile();
  }

  public void setOldIPAddress(FreenetInetAddress freenetAddress) {
    this.oldIPAddress = freenetAddress;
  }

  public int registerConfigs(SubConfig nodeConfig, int sortOrder) {
    // IP address override
    nodeConfig.register("ipAddressOverride", "", sortOrder++, true, false, "NodeIPDectector.ipOverride",
        "NodeIPDectector.ipOverrideLong",
        new StringCallback() {

      @Override
      public String get() {
        if(overrideIPAddressString == null) return "";
        else return overrideIPAddressString;
      }
     
      @Override
      public void set(String val) throws InvalidConfigValueException {
        boolean hadValidAddressOverride = hasValidAddressOverride();
        // FIXME do we need to tell anyone?
        if(val.length() == 0) {
          // Set to null
          overrideIPAddressString = val;
          overrideIPAddress = null;
          lastIPAddress = null;
          redetectAddress();
          return;
        }
        FreenetInetAddress addr;
        try {
          addr = new FreenetInetAddress(val, false, true);
        } catch (HostnameSyntaxException e) {
          throw new InvalidConfigValueException(l10n("unknownHostErrorInIPOverride", "error", "hostname or IP address syntax error"));
        } catch (UnknownHostException e) {
          throw new InvalidConfigValueException(l10n("unknownHostErrorInIPOverride", "error", e.getMessage()));
        }
        // Compare as IPs.
        if(addr.equals(overrideIPAddress)) return;
        overrideIPAddressString = val;
        overrideIPAddress = addr;
        lastIPAddress = null;
        synchronized(this) {
          hasValidAddressOverride = true;
        }
        if(!hadValidAddressOverride) {
          onGetValidAddressOverride();
        }
        redetectAddress();
      }
    });
   
    hasValidAddressOverride = true;
    overrideIPAddressString = nodeConfig.getString("ipAddressOverride");
    if(overrideIPAddressString.length() == 0)
      overrideIPAddress = null;
    else {
      try {
        overrideIPAddress = new FreenetInetAddress(overrideIPAddressString, false, true);
      } catch (HostnameSyntaxException e) {
        synchronized(this) {
          hasValidAddressOverride = false;
        }
        String msg = "Invalid IP override syntax: "+overrideIPAddressString+" in config: "+e.getMessage();
        Logger.error(this, msg);
        System.err.println(msg+" but starting up anyway, ignoring the configured IP override");
        overrideIPAddress = null;
      } catch (UnknownHostException e) {
        // **FIXME** This never happens for this reason with current FreenetInetAddress(String, boolean, boolean) code; perhaps it needs review?
        String msg = "Unknown host: "+overrideIPAddressString+" in config: "+e.getMessage();
        Logger.error(this, msg);
        System.err.println(msg+" but starting up anyway with no IP override");
        overrideIPAddress = null;
      }
    }
   
    // Temporary IP address hint
   
    nodeConfig.register("tempIPAddressHint", "", sortOrder++, true, false, "NodeIPDectector.tempAddressHint", "NodeIPDectector.tempAddressHintLong", new StringCallback() {

      @Override
      public String get() {
        return "";
      }
     
      @Override
      public void set(String val) throws InvalidConfigValueException {
        if(val.length() == 0) {
          return;
        }
        if(overrideIPAddress != null) return;
        try {
          oldIPAddress = new FreenetInetAddress(val, false);
        } catch (UnknownHostException e) {
          throw new InvalidConfigValueException("Unknown host: "+e.getMessage());
        }
        redetectAddress();
      }
    });
   
    String ipHintString = nodeConfig.getString("tempIPAddressHint");
    if(ipHintString.length() > 0) {
      try {
        oldIPAddress = new FreenetInetAddress(ipHintString, false);
      } catch (UnknownHostException e) {
        String msg = "Unknown host: "+ipHintString+" in config: "+e.getMessage();
        Logger.error(this, msg);
        System.err.println(msg);
        oldIPAddress = null;
      }
    }
   
    return sortOrder;
  }

  /** Start all IP detection related processes */
  public void start() {
    boolean haveValidAddressOverride = hasValidAddressOverride();
    if(!haveValidAddressOverride) {
      onNotGetValidAddressOverride();
    }
    node.executor.execute(ipDetector, "IP address re-detector");
    redetectAddress();
    // 60 second delay for inserting ARK to avoid reinserting more than necessary if we don't detect IP on startup.
    // Not a FastRunnable as it can take a while to start the insert
    node.getTicker().queueTimedJob(new Runnable() {
      @Override
      public void run() {
        NodeIPPortDetector[] detectors;
        synchronized(this) {
          detectors = portDetectors;
        }
        for(NodeIPPortDetector detector: detectors)
          detector.startARK();
      }
    }, SECONDS.toMillis(60));
  }

  public void onConnectedPeer() {
    // Run off thread, but at high priority.
    // Initial messages don't need an up to date IP for the node itself, but
    // announcements do. However announcements are not sent instantly.
    node.executor.execute(new PrioRunnable() {

      @Override
      public void run() {
        ipDetectorManager.maybeRun();
      }

      @Override
      public int getPriority() {
        return NativeThread.HIGH_PRIORITY;
      }
     
    });
  }

  public void registerIPDetectorPlugin(FredPluginIPDetector detector) {
    ipDetectorManager.registerDetectorPlugin(detector);
  }

  public void unregisterIPDetectorPlugin(FredPluginIPDetector detector) {
    ipDetectorManager.unregisterDetectorPlugin(detector);
  }
 
  public synchronized boolean isDetecting() {
    return !(hasDetectedPM && hasDetectedIAD);
  }

  void hasDetectedPM() {
    if(logMINOR)
      Logger.minor(this, "hasDetectedPM() called", new Exception("debug"));
    synchronized(this) {
      hasDetectedPM = true;
    }
  }

  public int getMinimumDetectedMTU(boolean ipv6) {
    return ipv6 ? minimumMTUIPv6.get() : minimumMTUIPv4.get();
  }

  public int getMinimumDetectedMTU() {
    return Math.min(minimumMTUIPv4.get(), minimumMTUIPv6.get());
  }

  public void setMaybeSymmetric() {
    if(ipDetectorManager != null && ipDetectorManager.isEmpty()) {
      if(maybeSymmetricAlert == null) {
        maybeSymmetricAlert = new SimpleUserAlert(true, l10n("maybeSymmetricTitle"),
            l10n("maybeSymmetric"), l10n("maybeSymmetricShort"), UserAlert.ERROR);
      }
      if(node.clientCore != null && node.clientCore.alerts != null)
        node.clientCore.alerts.register(maybeSymmetricAlert);
    } else {
      if(maybeSymmetricAlert != null)
        node.clientCore.alerts.unregister(maybeSymmetricAlert);
    }
  }

  public void registerPortForwardPlugin(FredPluginPortForward forward) {
    ipDetectorManager.registerPortForwardPlugin(forward);
  }

  public void unregisterPortForwardPlugin(FredPluginPortForward forward) {
    ipDetectorManager.unregisterPortForwardPlugin(forward);
  }
 
  //TODO: ugly: deal with multiple instances properly
  public synchronized void registerBandwidthIndicatorPlugin(FredPluginBandwidthIndicator indicator) {
    bandwidthIndicator = indicator;
  }
  public synchronized void unregisterBandwidthIndicatorPlugin(FredPluginBandwidthIndicator indicator) {
    bandwidthIndicator = null;
  }
  public synchronized FredPluginBandwidthIndicator getBandwidthIndicator() {
    return bandwidthIndicator;
  }
  private FredPluginBandwidthIndicator bandwidthIndicator;
 
  boolean hasValidAddressOverride() {
    synchronized(this) {
      return hasValidAddressOverride;
    }
  }
 
  private void onGetValidAddressOverride() {
    node.clientCore.alerts.unregister(invalidAddressOverrideAlert);
  }
 
  private void onNotGetValidAddressOverride() {
    node.clientCore.alerts.register(invalidAddressOverrideAlert);
  }

  public void addConnectionTypeBox(HTMLNode contentNode) {
    ipDetectorManager.addConnectionTypeBox(contentNode);
  }

  public boolean noDetectPlugins() {
    return !ipDetectorManager.hasDetectors();
  }

  public boolean hasJSTUN() {
    return ipDetectorManager.hasJSTUN();
  }
}
TOP

Related Classes of freenet.node.NodeIPDetector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.