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"); } }