Package freenet.node

Source Code of freenet.node.IPDetectorPluginManager$MyUserAlert

package freenet.node;

import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;

import java.net.InetAddress;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import freenet.clients.http.ConnectivityToadlet;
import freenet.clients.http.ExternalLinkToadlet;
import freenet.io.AddressTracker;
import freenet.io.AddressTracker.Status;
import freenet.io.comm.FreenetInetAddress;
import freenet.io.comm.Peer;
import freenet.l10n.NodeL10n;
import freenet.node.useralerts.AbstractUserAlert;
import freenet.node.useralerts.ProxyUserAlert;
import freenet.node.useralerts.SimpleUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.pluginmanager.DetectedIP;
import freenet.pluginmanager.ForwardPort;
import freenet.pluginmanager.ForwardPortCallback;
import freenet.pluginmanager.ForwardPortStatus;
import freenet.pluginmanager.FredPlugin;
import freenet.pluginmanager.FredPluginIPDetector;
import freenet.pluginmanager.FredPluginPortForward;
import freenet.support.HTMLEncoder;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.transport.ip.IPUtil;

/**
* Tracks all known IP address detection plugins, and runs them when appropriate.
* Normally there would only be one, but sometimes there may be more than one.
*/
public class IPDetectorPluginManager implements ForwardPortCallback {
 
  public class PortForwardAlert extends AbstractUserAlert {

    private int[] portsNotForwarded;
   
    private short maxPriorityShown;
    private int maxPortsLength;
   
    private boolean valid;
   
    @Override
    public String anchor() {
      return "port-forward:"+super.hashCode();
    }

    @Override
    public String dismissButtonText() {
      return NodeL10n.getBase().getString("UserAlert.hide");
    }

    @Override
    public HTMLNode getHTMLText() {
      HTMLNode div = new HTMLNode("div");
      String url = ExternalLinkToadlet.escape(HTMLEncoder.encode(l10n("portForwardHelpURL")));
      boolean maybeForwarded = true;
      for(int portNotForwarded: portsNotForwarded) {
        if(portNotForwarded < 0) maybeForwarded = false;
      }
      String keySuffix = maybeForwarded ? "MaybeForwarded" : "NotForwarded";
      if(portsNotForwarded.length == 1) {
        NodeL10n.getBase().addL10nSubstitution(div, "IPDetectorPluginManager.forwardPort"+keySuffix,
                new String[] { "port", "link" },
                new HTMLNode[] { HTMLNode.text(Math.abs(portsNotForwarded[0])), HTMLNode.link(url) });
      } else if(portsNotForwarded.length == 2) {
        NodeL10n.getBase().addL10nSubstitution(div, "IPDetectorPluginManager.forwardTwoPorts"+keySuffix,
                new String[] { "port1", "port2", "link", "connectivity" },
                new HTMLNode[] { HTMLNode.text(Math.abs(portsNotForwarded[0])),
                        HTMLNode.text(Math.abs(portsNotForwarded[1])),
                        HTMLNode.link(url),
                        HTMLNode.link(ConnectivityToadlet.PATH) });
      } else {
        Logger.error(this, "Unknown number of ports to forward: "+portsNotForwarded.length);
      }
      if(innerGetPriorityClass() == UserAlert.ERROR) {
        div.addChild("#", " " + l10n("symmetricPS"));
      }
      return div;
    }

    @Override
    public short getPriorityClass() {
      return innerGetPriorityClass();
    }
   
    public short innerGetPriorityClass() {
      if(connectionType == DetectedIP.SYMMETRIC_NAT || connectionType == DetectedIP.SYMMETRIC_UDP_FIREWALL)
        // Only able to connect to directly connected / full cone nodes.
        return UserAlert.ERROR;
      if(portsNotForwarded != null) {
        for(int portNotForwarded: portsNotForwarded)
          if(portNotForwarded < 0) return UserAlert.ERROR;
      }
      return UserAlert.MINOR;
    }

    @Override
    public String getShortText() {
      String prefix = innerGetPriorityClass() == UserAlert.ERROR ?
          l10n("seriousConnectionProblems") : l10n("connectionProblems");
      prefix += " ";
      boolean maybeForwarded = true;
      for(int portNotForwarded: portsNotForwarded) {
        if(portNotForwarded < 0) maybeForwarded = false;
      }
      String keySuffix = maybeForwarded ? "MaybeForwarded" : "NotForwarded";
      if(portsNotForwarded.length == 1) {
        return prefix + l10n("forwardPortShort"+keySuffix, "port", Integer.toString(Math.abs(portsNotForwarded[0])));
      } else if(portsNotForwarded.length == 2) {
        return prefix + l10n("forwardTwoPortsShort"+keySuffix, new String[] { "port1", "port2" },
            new String[] { Integer.toString(Math.abs(portsNotForwarded[0])), Integer.toString(Math.abs(portsNotForwarded[1])) });
      } else {
        Logger.error(this, "Unknown number of ports to forward: "+portsNotForwarded.length);
        return "";
      }
    }

    @Override
    public String getText() {
      String url = l10n("portForwardHelpURL");
      boolean maybeForwarded = true;
      for(int portNotForwarded: portsNotForwarded) {
        if(portNotForwarded < 0) maybeForwarded = false;
      }
      String keySuffix = maybeForwarded ? "MaybeForwarded" : "NotForwarded";
      if(portsNotForwarded.length == 1) {
        return l10n("forwardPort"+keySuffix, new String[] { "port", "link", "/link" },
            new String[] { Integer.toString(Math.abs(portsNotForwarded[0])), "", " ("+url+")" });
      } else if(portsNotForwarded.length == 2) {
        return l10n("forwardTwoPorts"+keySuffix, new String[] { "port1", "port2", "link", "/link" },
            new String[] { Integer.toString(Math.abs(portsNotForwarded[0])), Integer.toString(Math.abs(portsNotForwarded[1])), "", " ("+url+")" });
      } else {
        Logger.error(this, "Unknown number of ports to forward: "+portsNotForwarded.length);
        return "";
      }
    }

    @Override
    public String getTitle() {
      return getShortText();
    }

    @Override
    public boolean isValid() {
      portsNotForwarded = getUDPPortsNotForwarded();
      if(portsNotForwarded.length > maxPortsLength) {
        valid = true;
        maxPortsLength = portsNotForwarded.length;
      }
      short prio = innerGetPriorityClass();
      if(prio < maxPriorityShown) {
        valid = true;
        maxPriorityShown = prio;
      }
      if(portsNotForwarded.length == 0) return false;
      return valid;
    }

    @Override
    public void isValid(boolean validity) {
      valid = validity;
    }

    @Override
    public void onDismiss() {
      valid = false;
    }

    @Override
    public boolean shouldUnregisterOnDismiss() {
      return false;
    }

    @Override
    public boolean userCanDismiss() {
      return true;
    }

    @Override
    public boolean isEventNotification() {
      return false;
    }
   
  }
 
  public class MyUserAlert extends AbstractUserAlert {

    final boolean suggestPortForward;
    private int[] portsNotForwarded;
   
    public MyUserAlert(String title, String text, boolean suggestPortForward, short code) {
      super(false, title, text, title, null, code, true, NodeL10n.getBase().getString("UserAlert.hide"), false, null);
      this.suggestPortForward = suggestPortForward;
      portsNotForwarded = new int[] { };
    }

    @Override
    public HTMLNode getHTMLText() {
      HTMLNode div = new HTMLNode("div");
      div.addChild("#", super.getText());
      if(suggestPortForward) {
        if(portsNotForwarded.length == 1) {
          NodeL10n.getBase().addL10nSubstitution(div,
                  "IPDetectorPluginManager.suggestForwardPortWithLink",
                  new String[] { "link", "port" },
                  new HTMLNode[] { HTMLNode.link(ExternalLinkToadlet.escape(
                          "http://wiki.freenetproject.org/FirewallAndRouterIssues")),
                          HTMLNode.text(portsNotForwarded[0])});
        } else {
          NodeL10n.getBase().addL10nSubstitution(div,
                  "IPDetectorPluginManager.suggestForwardTwoPortsWithLink",
                  new String[] { "link", "port1", "port2" },
                  new HTMLNode[] { HTMLNode.link(ExternalLinkToadlet.escape(
                          "http://wiki.freenetproject.org/FirewallAndRouterIssues")),
                          HTMLNode.text(portsNotForwarded[0]),
                          HTMLNode.text(portsNotForwarded[1]) });
        }
      }
      return div;
    }

    @Override
    public String getText() {
      if(!suggestPortForward) return super.getText();
      StringBuilder sb = new StringBuilder();
      sb.append(super.getText());
      if(portsNotForwarded.length == 1) {
        sb.append(l10n("suggestForwardPort", "port", Integer.toString(Math.abs(portsNotForwarded[0]))));
      } else if(portsNotForwarded.length >= 2) {
        sb.append(l10n("suggestForwardTwoPorts", new String[] { "port1", "port2" },
            new String[] { Integer.toString(Math.abs(portsNotForwarded[0])), Integer.toString(Math.abs(portsNotForwarded[1])) }));
        if(portsNotForwarded.length > 2)
          Logger.error(this, "Not able to tell user about more than 2 ports to forward! ("+portsNotForwarded.length+")");
      }
     
      return sb.toString();
    }

    @Override
    public void isValid(boolean validity) {
      valid = validity;
    }

    @Override
    public boolean isValid() {
      portsNotForwarded = getUDPPortsNotForwarded();
      return valid && (portsNotForwarded.length > 0);
    }
   
    @Override
    public void onDismiss() {
      valid = false;
    }
   
    @Override
    public boolean userCanDismiss() {
      return false;
    }

  }

  private static boolean logMINOR;
  private static boolean logDEBUG;
  static {
    Logger.registerClass(IPDetectorPluginManager.class);
  }
  private final NodeIPDetector detector;
  private final Node node;
  FredPluginIPDetector[] plugins;
  FredPluginPortForward[] portForwardPlugins;
  private final MyUserAlert noConnectionAlert;
  private final MyUserAlert symmetricAlert;
  private final MyUserAlert portRestrictedAlert;
  private final MyUserAlert restrictedAlert;
  private short connectionType;
  private ProxyUserAlert proxyAlert;
  private final PortForwardAlert portForwardAlert;
  private boolean started;
 
  IPDetectorPluginManager(Node node, NodeIPDetector detector) {
    plugins = new FredPluginIPDetector[0];
    portForwardPlugins = new FredPluginPortForward[0];
    this.node = node;
    this.detector = detector;
    noConnectionAlert = new MyUserAlert( l10n("noConnectivityTitle"), l10n("noConnectivity"),
        true, UserAlert.ERROR);
    symmetricAlert = new MyUserAlert(l10n("symmetricTitle"), l10n("symmetric"),
        true, UserAlert.ERROR);
    portRestrictedAlert = new MyUserAlert(l10n("portRestrictedTitle"), l10n("portRestricted"),
        true, UserAlert.WARNING);
    restrictedAlert = new MyUserAlert(l10n("restrictedTitle"), l10n("restricted"),
        false, UserAlert.MINOR);
    portForwardAlert = new PortForwardAlert();
  }

  /**
   * Return all the ports that we have reason to believe are not forwarded. E.g. for the user-alert, which only
   * shows if what we return is of nonzero length.
   */
  public int[] getUDPPortsNotForwarded() {
    OpennetManager om = node.getOpennet();
    Status darknetStatus = (node.peers.anyDarknetPeers() ? node.darknetCrypto.getDetectedConnectivityStatus() : AddressTracker.Status.DONT_KNOW);
    Status opennetStatus = om == null ? Status.DONT_KNOW : om.crypto.getDetectedConnectivityStatus();
    if(om == null || opennetStatus.ordinal() >= AddressTracker.Status.DONT_KNOW.ordinal()) {
      if(darknetStatus.ordinal() >= AddressTracker.Status.DONT_KNOW.ordinal()) {
        return new int[] { };
      } else {
        return new int[] { (darknetStatus.ordinal() < AddressTracker.Status.MAYBE_NATED.ordinal() ? -1 : 1) * node.getDarknetPortNumber() };
      }
    } else {
      if(darknetStatus.ordinal() >= AddressTracker.Status.DONT_KNOW.ordinal()) {
        return new int[] { (opennetStatus.ordinal() < AddressTracker.Status.MAYBE_NATED.ordinal() ? -1 : 1 ) * om.crypto.portNumber };
      } else {
        return new int[] { ((darknetStatus.ordinal() < AddressTracker.Status.MAYBE_NATED.ordinal()) ? -1 : 1 ) * node.getDarknetPortNumber(),
            (opennetStatus.ordinal() < AddressTracker.Status.MAYBE_NATED.ordinal() ? -1 : 1 ) * om.crypto.portNumber };
      }
    }
  }

  private String l10n(String key) {
    return NodeL10n.getBase().getString("IPDetectorPluginManager."+key);
  }

  public String l10n(String key, String pattern, String value) {
    return NodeL10n.getBase().getString("IPDetectorPluginManager."+key, new String[] { pattern }, new String[] { value });
  }

  public String l10n(String key, String[] patterns, String[] values) {
    return NodeL10n.getBase().getString("IPDetectorPluginManager."+key, patterns, values);
  }

  /** Start the detector plugin manager. This includes running the plugin, if there
   * is one, and if it is necessary to do so. */
  void start() {
    // Cannot be initialized until UserAlertManager has been created.
    proxyAlert = new ProxyUserAlert(node.clientCore.alerts, false);
    node.clientCore.alerts.register(portForwardAlert);
    started = true;
    tryMaybeRun();
  }
 
  /**
   * Start the plugin detection, if necessary. Either way, schedule another attempt in
   * 1 minute's time.
   */
  private void tryMaybeRun() {
    try {
      maybeRun();
    } catch (Throwable t) {
      Logger.error(this, "Caught "+t, t);
    }
    node.getTicker().queueTimedJob(new Runnable() {
      @Override
      public void run() {
        freenet.support.Logger.OSThread.logPID(this);
        tryMaybeRun();
      }
    }, MINUTES.toMillis(1));
  }

  /**
   * Register a plugin.
   */
  public void registerDetectorPlugin(FredPluginIPDetector d) {
    if(d == null) throw new NullPointerException();
    synchronized(this) {
      lastDetectAttemptEndedTime = -1;
      plugins = Arrays.copyOf(plugins, plugins.length+1);
      plugins[plugins.length-1] = d;
    }
    if(logMINOR) Logger.minor(this, "Registering a new plugin : " + d);
    maybeRun();
  }

  /**
   * Remove a plugin.
   */
  public void unregisterDetectorPlugin(FredPluginIPDetector d) {
    DetectorRunner runningDetector;
    synchronized(this) {
      int count = 0;
      for(FredPluginIPDetector plugin: plugins) {
        if(plugin == d) count++;
      }
      if(count == 0) return;
      FredPluginIPDetector[] newPlugins = new FredPluginIPDetector[plugins.length - count];
      int x = 0;
      for(FredPluginIPDetector plugin: plugins) {
        if(plugin != d) newPlugins[x++] = plugin;
      }
      plugins = newPlugins;
      // Will be removed when returns in the DetectorRunner
      runningDetector = runners.get(d);
    }
                if(runningDetector != null)
                    runningDetector.kill();
  }

 
  /* When should we run an IP address detection? This is for things like STUN, so
   * there may conceivably be some exposure or risk, or limited resources, so not
   * all the time.
   *
   * If we don't get a real IP address from a detection, we should not run another
   * one for 5 minutes. This indicated that we were not on the internet *at all*.
   *
   * If we have a directly detected IP, and:
   * - We have no peers older than 30 minutes OR
   * - We have successfully connected to two different peers with different real
   *   internet addresses to us since startup
   *
   * Then we should not run a detection. (However, we don't entirely exclude it
   * because we may be behind a firewall).
   *
   * If we have no peers, and we haven't run a detection in the last 6 hours (don't
   * save this time over startups), we should run a detection.
   *
   * Otherwise, we have peers, and if we have run a detection in the last hour we
   * should not run another one.
   *
   * If we have one or two connected peers, both of which report the same IP
   * address, and we have other nodes which have been connected recently, and this
   * state has persisted for 2 minutes, we should run a detection.
   * (To protect against bogus IP address reports)
   *
   * If we have no connected peers with real internet addresses, and this state has
   * persisted for 2 minutes, and we have disconnected peers, then we should run a
   * detection. (every hour that we are down)
   * (To detect new IP address)
   */
 
  private HashMap<FredPluginIPDetector,DetectorRunner> runners = new HashMap<FredPluginIPDetector,DetectorRunner>();
  private HashSet<FredPluginIPDetector> failedRunners = new HashSet<FredPluginIPDetector>();
  private long lastDetectAttemptEndedTime;
  private long firstTimeUrgent;
 
  /**
   * Do we need to run a plugin?
   */
  public void maybeRun() {
    if(!started) return;
    if(logMINOR) Logger.minor(this, "Maybe running IP detection plugins", new Exception("debug"));
    PeerNode[] peers = node.getPeerNodes();
    PeerNode[] conns = node.getConnectedPeers();
    int peerCount = node.peers.countValidPeers();
    FreenetInetAddress[] nodeAddrs = detector.getPrimaryIPAddress(true);
    long now = System.currentTimeMillis();
    synchronized(this) {
      if(plugins.length == 0) {
        if(logMINOR) Logger.minor(this, "No IP detection plugins");
        detector.hasDetectedPM();
        return;
      }
      if(runners.size() == plugins.length) {
        if(logMINOR) Logger.minor(this, "Already running all IP detection plugins");
        return;
      }
     
      // If detect attempt failed to produce an IP in the last 5 minutes, don't
      // try again yet.
      if(failedRunners.size() == plugins.length) {
        if(now - lastDetectAttemptEndedTime < MINUTES.toMillis(5)) {
          if(logMINOR) Logger.minor(this, "Last detect failed less than 5 minutes ago");
          return;
        } else {
          if(logMINOR) Logger.minor(this, "Last detect failed, redetecting");
          startDetect();
          return;
        }
      }
      if(detector.hasDirectlyDetectedIP()) {
       
        if(!shouldDetectDespiteRealIP(now, conns, nodeAddrs)) return;
       
      }
     
      if(peerCount == 0) {
       
        if(shouldDetectNoPeers(now)) startDetect();
       
      } else {
       
        if(shouldDetectWithPeers(now, peers, conns, nodeAddrs)) startDetect();
       
      }
    }
   
  }

  /**
   * Given that we have no peers, should we run the detection plugins?
   * Algorithm: Run the detection once every 6 hours.
   * @param now The time at the start of the calling method.
   * @return True if we should run a detection.
   */
  private boolean shouldDetectNoPeers(long now) {
    if(now - lastDetectAttemptEndedTime < HOURS.toMillis(6)) {
      // No peers, only try every 6 hours.
      if(logMINOR) Logger.minor(this, "No peers but detected less than 6 hours ago");
      return false;
    } else {
      // Must try once!
      return true;
    }
  }

  /**
   * Given that we have some peers, should we run the detection plugins?
   * @param now The time at the beginning of the calling method.
   * @param peers The node's peers.
   * @param conns The node's connected peers.
   * @return True if we should run a detection.
   */
  private boolean shouldDetectWithPeers(long now, PeerNode[] peers, PeerNode[] conns, FreenetInetAddress[] nodeAddrs) {
   
    boolean detect = false;
   
    // If we have no connections, and several disconnected but enabled
    // peers, then run a detection.
   
    int realConnections = 0;
    int realDisconnected = 0;
    int recentlyConnected = 0;
   
    if(logMINOR) Logger.minor(this, "Checking whether should detect with "+peers.length+" peers and "+conns.length+" conns, counting peers...");
    for(PeerNode p: peers) {
      if(p.isDisabled()) continue;
      // Don't count localhost, LAN addresses.
      Peer peer = p.getPeer();
      if(peer == null) continue;
      FreenetInetAddress a = peer.getFreenetAddress();
      if(a == null) continue; // Not much chance of connecting.
      InetAddress addr = a.getAddress(false);
      if(addr != null) {
        if(!IPUtil.isValidAddress(addr, false)) continue;
      }
      boolean skip = false;
      for(FreenetInetAddress nodeAddr: nodeAddrs) {
        if(a.equals(nodeAddr)) {
          skip = true;
          break;
        }
      }
      if(skip) continue;
      if(p.isConnected())
        realConnections++;
      else {
        realDisconnected++;
        if(now - p.lastReceivedPacketTime() < MINUTES.toMillis(5))
          recentlyConnected++;
      }
    }
   
    // If we have no connections, and several disconnected nodes, we should do a
    // detection soon.
    if(logMINOR) Logger.minor(this, "Real connections: "+realConnections+" disconnected "+realDisconnected);
    if(realConnections == 0 && realDisconnected > 0) {
      if(firstTimeUrgent <= 0)
        firstTimeUrgent = now;
     
      if(detector.oldIPAddress != null && detector.oldIPAddress.isRealInternetAddress(false, false, false)) {
        if(logDEBUG) Logger.debug(this, "Detecting in 2 minutes as have oldIPAddress");
        // Allow 2 minutes to get incoming connections and therefore detect from them.
        // In the meantime, *hopefully* our oldIPAddress is valid.
        // If not, we'll find out in 2 minutes.
        if(now - firstTimeUrgent > MINUTES.toMillis(2)) {
          detect = true;
          firstTimeUrgent = now; // Reset now rather than on next round.
          if(logMINOR) Logger.minor(this, "Detecting now as 2 minutes are up (have oldIPAddress)");
        }
      } else {
        if(logMINOR) Logger.minor(this, "Detecting now (no oldIPAddress)");
        // Detect immediately
        detect = true;
      }
    } else if(realConnections == 0 && realDisconnected == 0) {
      return shouldDetectNoPeers(now);
    } else {
      if(logDEBUG) Logger.minor(this, "Not urgent; conns="+conns.length+", peers="+peers.length);
      firstTimeUrgent = 0;
    }
   
    // If we have no connections, and have lost several connections recently, we should
    // do a detection soon, regardless of the 1 detection per hour throttle.
    if(realConnections == 0 && realDisconnected > 0 && recentlyConnected > 2) {
      if(now - lastDetectAttemptEndedTime > MINUTES.toMillis(6)) {
        return true;
      }
    }
   
    // If it appears to be an SNAT, do a detection at least once to verify that, and to
    // check whether our IP is bogus.
    if(detector.maybeSymmetric && lastDetectAttemptEndedTime <= 0)
      return true;
   
    if(detect) {
      if(now - lastDetectAttemptEndedTime < HOURS.toMillis(1)) {
        // Only try every hour
        if(logMINOR) Logger.minor(this, "Only trying once per hour");
        return false;
      }
     
      return true;
    } else {
      return false;
    }
  }

  /**
   * Should we run the detection plugins despite having a directly detected IP address?
   * @param now The time at the beginning of the calling method.
   * @param peers The node's peers.
   * @param nodeAddrs Our peers' addresses.
   * @return True if we should run a detection.
   */
  private boolean shouldDetectDespiteRealIP(long now, PeerNode[] peers, FreenetInetAddress[] nodeAddrs) {
    // We might still be firewalled?
    // First, check only once per day or startup
    if(now - lastDetectAttemptEndedTime < HOURS.toMillis(12)) {
      if(logMINOR) Logger.minor(this, "Node has directly detected IP and we have checked less than 12 hours ago");
      return false;
    }
   
    if(logMINOR) Logger.minor(this, "Checking whether should detect despite real IP...");
    // Now, if we have two nodes with unique IPs which aren't ours
    // connected, we don't need to detect.
    HashSet<InetAddress> addressesConnected = null;
    boolean hasOldPeers = false;
    for(PeerNode p : peers) {
      if(p.isConnected() || (now - p.lastReceivedPacketTime() < HOURS.toMillis(24))) {
        // Has been connected in the last 24 hours.
        // Unique IP address?
        Peer peer = p.getPeer();
        if(peer != null){
          InetAddress addr = peer.getAddress(false);
          if(p.isConnected() && (addr != null) && IPUtil.isValidAddress(peer.getAddress(), false)) {
            // Connected node, on a real internet IP address.
            // Is it internal?
            boolean internal = false;
            for(FreenetInetAddress nodeAddr: nodeAddrs) {
              if(addr.equals(nodeAddr.getAddress(false))) {
                // Internal
                internal = true;
                break;
              }
            }
            if(!internal) {
              // Real IP address
              if(addressesConnected == null)
                addressesConnected = new HashSet<InetAddress>();
              addressesConnected.add(addr);
              if(addressesConnected.size() > 2) {
                // 3 connected addresses, lets assume we have connectivity.
                if(logMINOR) Logger.minor(this, "Node has directly detected IP and has connected to 3 real IPs");
                return false;
              }
            }
          }
        }
        long l = p.getPeerAddedTime();
        if((l <= 0) || (now - l > MINUTES.toMillis(30))) {
          hasOldPeers = true;
        }
      }
    }
    if(!hasOldPeers) {
      // No peers older than 30 minutes
      if(logMINOR) Logger.minor(this, "Not detecting as less than 30 minutes old");
      return false;
    }
    return true;
  }

  private void startDetect() {
    if(logMINOR) Logger.minor(this, "Detecting...");
    synchronized(this) {
      failedRunners.clear();
      for(FredPluginIPDetector plugin: plugins) {
        if(runners.containsKey(plugin)) continue;
        DetectorRunner d = new DetectorRunner(plugin);
        runners.put(plugin, d);
        node.executor.execute(d, "Plugin detector runner for "+plugin.getClass());
      }
    }
  }

  public class DetectorRunner implements Runnable {
   
    final FredPluginIPDetector plugin;

    public DetectorRunner(FredPluginIPDetector detector) {
      plugin = detector;
    }

    public void kill() {
      node.pluginManager.killPlugin((FredPlugin)plugin, 0);
    }

    @Override
    public void run() {
      freenet.support.Logger.OSThread.logPID(this);
      try {
        realRun();
      } catch (Throwable t) {
        Logger.error(this, "Caught "+t, t);
      }
    }
   
    public void realRun() {
      if(logMINOR) Logger.minor(this, "Running plugin detection");
      try {
        List<DetectedIP> v = new ArrayList<DetectedIP>();
        DetectedIP[] detected = null;
        try {
          detected = plugin.getAddress();
        } catch (Throwable t) {
          Logger.error(this, "Caught "+t, t);
        }
        if(detected != null) {
          for(DetectedIP d: detected)
            v.add(d);
        }
        synchronized(IPDetectorPluginManager.this) {
          lastDetectAttemptEndedTime = System.currentTimeMillis();
          boolean failed = false;
          if(v.isEmpty()) {
            if(logMINOR) Logger.minor(this, "No IPs found");
            failed = true;
          } else {
            failed = true;
            for(DetectedIP ip : v) {
              if(logMINOR) Logger.minor(this, "Detected IP: "+ip+" for "+plugin);
              if(!((ip.publicAddress == null) || !IPUtil.isValidAddress(ip.publicAddress, false))) {
                if(logMINOR) Logger.minor(this, "Address checked out");
                failed = false;
              }
            }
          }
          if(failed) {
            if(logMINOR) Logger.minor(this, "Failed");
            failedRunners.add(plugin);
            return;
          }
        }
       
        // Node does not know about individual interfaces, so just process the lot.
       
        // FIXME if we use the interfaces we should simply take the most popular conclusion for each one.
       
//        // Now tell the node
//        HashMap map = new LinkedHashMap();
//        for(int i=0;i<v.size();i++) {
//          DetectedIP d = (DetectedIP) v.get(i);
//          InetAddress addr = d.publicAddress;
//          if(!map.containsKey(addr)) {
//            map.put(addr, d);
//          } else {
//            DetectedIP oldD = (DetectedIP) map.get(addr);
//            if(!oldD.equals(d)) {
//              if(d.natType != DetectedIP.NOT_SUPPORTED) {
//                if(oldD.natType < d.natType) {
//                  // Higher value = more restrictive.
//                  // Assume the worst.
//                  map.put(addr, d);
//                }
//              }
//            }
//          }
//        }
//        DetectedIP[] list = (DetectedIP[]) map.values().toArray(new DetectedIP[map.size()]);
        DetectedIP[] list = v.toArray(new DetectedIP[v.size()]);
        int countOpen = 0;
        int countFullCone = 0;
        int countRestricted = 0;
        int countPortRestricted = 0;
        int countSymmetric = 0;
        int countClosed = 0;
        for(DetectedIP d: list) {
          Logger.normal(this, "Detected IP: "+d.publicAddress+ " : type "+d.natType);
          System.out.println("Detected IP: "+d.publicAddress+ " : type "+d.natType);
          switch(d.natType) {
          case DetectedIP.FULL_CONE_NAT:
            countFullCone++;
            break;
          case DetectedIP.FULL_INTERNET:
            countOpen++;
            break;
          case DetectedIP.NO_UDP:
            countClosed++;
            break;
          case DetectedIP.NOT_SUPPORTED:
            // Ignore
            break;
          case DetectedIP.RESTRICTED_CONE_NAT:
            countRestricted++;
            break;
          case DetectedIP.PORT_RESTRICTED_NAT:
            countPortRestricted++;
            break;
          case DetectedIP.SYMMETRIC_NAT:
          case DetectedIP.SYMMETRIC_UDP_FIREWALL:
            countSymmetric++;
            break;
          }
        }
       
        if(countClosed > 0 && (countOpen + countFullCone + countRestricted + countPortRestricted + countSymmetric) == 0) {
          proxyAlert.setAlert(noConnectionAlert);
          proxyAlert.isValid(true);
          connectionType = DetectedIP.NO_UDP;
        } else if(countSymmetric > 0 && (countOpen + countFullCone + countRestricted + countPortRestricted == 0)) {
          proxyAlert.setAlert(symmetricAlert);
          proxyAlert.isValid(true);
          connectionType = DetectedIP.SYMMETRIC_NAT;
        } else if(countPortRestricted > 0 && (countOpen + countFullCone + countRestricted == 0)) {
          proxyAlert.setAlert(portRestrictedAlert);
          proxyAlert.isValid(true);
          connectionType = DetectedIP.PORT_RESTRICTED_NAT;
        } else if(countRestricted > 0 && (countOpen + countFullCone == 0)) {
          proxyAlert.setAlert(restrictedAlert);
          proxyAlert.isValid(true);
          connectionType = DetectedIP.RESTRICTED_CONE_NAT;
        } else if(countFullCone > 0 && countOpen == 0) {
          proxyAlert.isValid(false);
          connectionType = DetectedIP.FULL_CONE_NAT;
        } else if(countOpen > 0) {
          proxyAlert.isValid(false);
        }
        detector.processDetectedIPs(list);
        if(connectionType == DetectedIP.NO_UDP) {
          SimpleUserAlert toRegister = null;
          synchronized(this) {
            if(noConnectivityAlert == null)
              noConnectivityAlert = toRegister =
                new SimpleUserAlert(false, l10n("noConnectivityTitle"), l10n("noConnectivity"), l10n("noConnectivityShort"), UserAlert.ERROR);
          }
          if(toRegister != null)
            node.clientCore.alerts.register(toRegister);
        } else {
          UserAlert toKill;
          synchronized(this) {
            toKill = noConnectivityAlert;
            noConnectivityAlert = null;
          }
          if(toKill != null)
            node.clientCore.alerts.unregister(toKill);
        }
      } finally {
        boolean finished;
        synchronized(IPDetectorPluginManager.this) {
          runners.remove(plugin);
          finished = runners.isEmpty();
        }
        if(finished)
          detector.hasDetectedPM();
      }
    }

  }
 
  private SimpleUserAlert noConnectivityAlert;

  public synchronized boolean isEmpty() {
    return plugins.length == 0;
  }

  public void registerPortForwardPlugin(FredPluginPortForward forward) {
    if(forward == null) throw new NullPointerException();
    synchronized(this) {
      portForwardPlugins = Arrays.copyOf(portForwardPlugins, portForwardPlugins.length+1);
      portForwardPlugins[portForwardPlugins.length-1] = forward;
    }
    if(logMINOR) Logger.minor(this, "Registering a new port forward plugin : " + forward);
    forward.onChangePublicPorts(node.getPublicInterfacePorts(), this);
  }

  /**
   * Remove a plugin.
   */
  public void unregisterPortForwardPlugin(FredPluginPortForward forward) {
    synchronized(this) {
      int count = 0;
      for(FredPluginPortForward portForwardPlugin: portForwardPlugins) {
        if(portForwardPlugin == forward) count++;
      }
      if(count == 0) return;
      FredPluginPortForward[] newPlugins = new FredPluginPortForward[portForwardPlugins.length - count];
      int x = 0;
      for(FredPluginPortForward portForwardPlugin: portForwardPlugins) {
        if(portForwardPlugin != forward) newPlugins[x++] = portForwardPlugin;
      }
      portForwardPlugins = newPlugins;
    }
  }

  void notifyPortChange(final Set<ForwardPort> newPorts) {
    FredPluginPortForward[] plugins;
    synchronized(this) {
      plugins = portForwardPlugins;
    }
    for(final FredPluginPortForward plugin: plugins) {
      node.executor.execute(new Runnable() {

        @Override
        public void run() {
          try {
            plugin.onChangePublicPorts(newPorts, IPDetectorPluginManager.this);
          } catch (Throwable t) {
            Logger.error(this, "Changing public ports list on "+plugin+" threw: "+t, t);
          }
        }
       
      }, "Notify "+plugin+" of ports list change");
    }
  }

  @Override
  public void portForwardStatus(Map<ForwardPort, ForwardPortStatus> statuses) {
    Set<ForwardPort> currentPorts = node.getPublicInterfacePorts();
    for(ForwardPort p : currentPorts) {
      ForwardPortStatus status = statuses.get(p);
      if(status == null) continue;
      if(status.status == ForwardPortStatus.DEFINITE_SUCCESS) {
        Logger.normal(this, "Succeeded forwarding "+p.name+" port "+p.portNumber+" for "+p.protocol+" - port forward definitely succeeded "+status.reasonString);
      } else if(status.status == ForwardPortStatus.PROBABLE_SUCCESS) {
        Logger.normal(this, "Probably succeeded forwarding "+p.name+" port "+p.portNumber+" for "+p.protocol+" - port forward probably succeeded "+status.reasonString);
      } else if(status.status == ForwardPortStatus.MAYBE_SUCCESS) {
        Logger.normal(this, "Maybe succeeded forwarding "+p.name+" port "+p.portNumber+" for "+p.protocol+" - port forward may have succeeded but strongly recommend out of band verification "+status.reasonString);
      } else if(status.status == ForwardPortStatus.DEFINITE_FAILURE) {
        Logger.error(this, "Failed forwarding "+p.name+" port "+p.portNumber+" for "+p.protocol+" - port forward definitely failed "+status.reasonString);
      } else if(status.status == ForwardPortStatus.PROBABLE_FAILURE) {
        Logger.error(this, "Probably failed forwarding "+p.name+" port "+p.portNumber+" for "+p.protocol+" - port forward probably failed "+status.reasonString);
      }
      // Not much more we can do / want to do for now
      // FIXME use status.externalPort.
    }
    node.executor.execute(new Runnable() {
      @Override
      public void run() {
        maybeRun();
      }
    }, "Redetect IP after port forward changed");
  }

  public synchronized boolean hasDetectors() {
    return plugins.length > 0;
  }

  public void addConnectionTypeBox(HTMLNode contentNode) {
    if(node.clientCore == null) return;
    if(node.clientCore.alerts == null) return;
    if(proxyAlert == null) {
      Logger.error(this, "start() not called yet?", new Exception("debug"));
      return;
    }
    if(proxyAlert.isValid())
      contentNode.addChild(node.clientCore.alerts.renderAlert(proxyAlert));
  }

  public boolean hasJSTUN() {
    return node.pluginManager.isPluginLoadedOrLoadingOrWantLoad("JSTUN");
  }
 
}
TOP

Related Classes of freenet.node.IPDetectorPluginManager$MyUserAlert

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.