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