Author: toad
Date: 2006-07-05 21:47:17 +0000 (Wed, 05 Jul 2006)
New Revision: 9472
Added:
trunk/freenet/src/freenet/node/IPDetectorPluginManager.java
Modified:
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/Version.java
trunk/freenet/src/freenet/pluginmanager/DetectedIP.java
Log:
860: Primitive STUN support via the JSTUN plugin (if it is loaded).
Added: trunk/freenet/src/freenet/node/IPDetectorPluginManager.java
===================================================================
--- trunk/freenet/src/freenet/node/IPDetectorPluginManager.java 2006-07-05
20:57:51 UTC (rev 9471)
+++ trunk/freenet/src/freenet/node/IPDetectorPluginManager.java 2006-07-05
21:47:17 UTC (rev 9472)
@@ -0,0 +1,384 @@
+package freenet.node;
+
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+import freenet.io.comm.Peer;
+import freenet.pluginmanager.DetectedIP;
+import freenet.pluginmanager.FredPluginIPDetector;
+import freenet.support.Logger;
+import freenet.transport.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 {
+
+ private final Node node;
+ FredPluginIPDetector[] plugins;
+
+ IPDetectorPluginManager(Node node) {
+ plugins = new FredPluginIPDetector[0];
+ this.node = node;
+ }
+
+ void start() {
+ try {
+ maybeRun();
+ } catch (Throwable t) {
+ Logger.error(this, "Caught "+t, t);
+ }
+ node.ps.queueTimedJob(new Runnable() {
+ public void run() {
+ start();
+ }
+ }, 60*1000);
+
+ }
+
+ /**
+ * Register a plugin.
+ */
+ public void register(FredPluginIPDetector d) {
+ if(d == null) throw new NullPointerException();
+ synchronized(this) {
+ FredPluginIPDetector[] newPlugins = new
FredPluginIPDetector[plugins.length+1];
+ System.arraycopy(plugins, 0, newPlugins, 0,
plugins.length);
+ newPlugins[plugins.length] = d;
+ plugins = newPlugins;
+ }
+ maybeRun();
+ }
+
+ /**
+ * Remove a plugin.
+ */
+ public void remove(FredPluginIPDetector d) {
+ synchronized(this) {
+ int count = 0;
+ for(int i=0;i<plugins.length;i++) {
+ if(plugins[i] == d) count++;
+ }
+ if(count == 0) return;
+ FredPluginIPDetector[] newPlugins = new
FredPluginIPDetector[plugins.length - count];
+ int x = 0;
+ for(int i=0;i<plugins.length;i++) {
+ if(newPlugins[i] != d) newPlugins[x++] =
plugins[i];
+ }
+ plugins = newPlugins;
+ }
+ }
+
+
+ /* 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 DetectorRunner runner;
+ private boolean lastDetectAttemptFailed;
+ private long lastDetectAttemptEndedTime;
+ private long firstTimeMaybeFakePeers;
+ private long firstTimeUrgent;
+
+ /**
+ * Do we need to run a plugin?
+ */
+ public void maybeRun() {
+ Logger.minor(this, "Maybe running IP detection plugins", new
Exception("debug"));
+ PeerNode[] peers = node.getPeerNodes();
+ PeerNode[] conns = node.getDarknetConnections();
+ Peer[] nodeAddrs = node.getPrimaryIPAddress();
+ synchronized(this) {
+ long now = System.currentTimeMillis();
+ if(runner != null) {
+ Logger.minor(this, "Already running IP
detection plugins");
+ return;
+ }
+ // If detect attempt failed to produce an IP in the
last 5 minutes, don't
+ // try again yet.
+ if(lastDetectAttemptFailed) {
+ if(now - lastDetectAttemptEndedTime <
5*60*1000) {
+ Logger.minor(this, "Last detect failed
and more than 5 minutes ago");
+ return;
+ } else {
+ startDetect();
+ }
+ }
+ if(node.hasDirectlyDetectedIP()) {
+ // We might still be firewalled?
+ // First, check only once per day or startup
+ if(now - lastDetectAttemptEndedTime <
24*60*60*1000) {
+ Logger.minor(this, "Node has directly
detected IP and we have checked less than 24 hours ago");
+ return;
+ }
+
+ // Now, if we have two nodes with unique IPs
which aren't ours
+ // connected, we don't need to detect.
+ HashSet addressesConnected = null;
+ for(int i=0;i<peers.length;i++) {
+ PeerNode p = peers[i];
+ if(p.isConnected() || now -
p.lastReceivedPacketTime() < 24*60*60*1000) {
+ // Has been connected in the
last 24 hours.
+ // Unique IP address?
+ Peer peer = p.getPeer();
+ InetAddress addr =
peer.getAddress(false);
+ if(p.isConnected() && peer !=
null && addr != null && IPUtil.checkAddress(peer.getAddress())) {
+ // Connected node, on a
real internet IP address.
+ // Is it internal?
+ boolean internal =
false;
+ for(int
j=0;j<nodeAddrs.length;j++) {
+
if(addr.equals(nodeAddrs[j].getAddress())) {
+ //
Internal
+
internal = true;
+ break;
+ }
+ }
+ if(internal) {
+ // Real IP
address
+
if(addressesConnected == null)
+
addressesConnected = new HashSet();
+
addressesConnected.add(addr);
+
if(addressesConnected.size() > 2) {
+ // 3
connected addresses, lets assume we have connectivity.
+
Logger.minor(this, "Node has directly detected IP and has connected to 3 real
IPs");
+ return;
+ }
+ }
+ }
+ }
+ long l = p.getPeerAddedTime();
+ if(l <= 0 || now - l < 30*60*1000) {
+ // Less than 30 minutes old,
don't run a detection yet as
+ // it is likely we are simply
directly connected. (But we do
+ // want it to work out of the
box if we are not!).
+ Logger.minor(this, "Not
detecting as less than 30 minutes old");
+ return;
+ }
+ }
+ }
+
+ if(peers.length == 0) {
+ if(now - lastDetectAttemptEndedTime <
6*60*60*1000) {
+ // No peers, only try every 6 hours.
+ Logger.minor(this, "No peers but
detected less than 6 hours ago");
+ return;
+ } else {
+ // Must try once!
+ startDetect();
+ return;
+ }
+ } else {
+
+ boolean detect = false;
+
+ // If we have no connections, and several
disconnected but enabled
+ // peers, then run a detection.
+
+ boolean maybeUrgent = false;
+
+ if(conns.length == 0) {
+
+ // No connections.
+ for(int i=0;i<peers.length;i++) {
+ PeerNode p = peers[i];
+ if(!p.isDisabled()) {
+ maybeUrgent = true;
+ Logger.minor(this, "No
connections, but have peers, may detect...");
+ break;
+ }
+ }
+ }
+
+ if(maybeUrgent) {
+ if(firstTimeUrgent <= 0)
+ firstTimeUrgent = now;
+
+ if(now - firstTimeUrgent > 2*60*1000)
+ detect = true;
+
+ } else {
+ Logger.minor(this, "Not urgent;
conns="+conns.length);
+ firstTimeUrgent = 0;
+ }
+
+ // Do the possibly-fake-IPs detection.
+ // If we have one or two peers connected now,
reporting real IPs, and
+ // if there is a locally detected address they
are different to it,
+ // and other peers have been connected, then
maybe we need to redetect
+ // to make sure we're not being spoofed.
+
+ boolean maybeFake = false;
+
+ if(!node.hasDirectlyDetectedIP()) {
+
+ if(conns.length > 0 && conns.length <
3) {
+ // No locally detected IP, only
one or two connections.
+ // Have we had more relatively
recently?
+ int count = 0;
+ long timeref = now;
+ if(firstTimeMaybeFakePeers > 0)
timeref = firstTimeMaybeFakePeers;
+ for(int i=0;i<peers.length;i++)
{
+ PeerNode p = peers[i];
+ if((!p.isConnected())
|| now - p.lastReceivedPacketTime() < 5*60*1000) {
+ // Not
connected now but has been within the past 5 minutes.
+ count++;
+ }
+ }
+ if(count > 2) {
+ maybeFake = true;
+ }
+ }
+ }
+
+ if(maybeFake) {
+ Logger.minor(this, "Possible fake IPs
being fed to us, may detect...");
+ if(firstTimeMaybeFakePeers <= 0)
+ firstTimeMaybeFakePeers = now;
+
+ if((now - firstTimeMaybeFakePeers) >
2*60*1000) {
+ // MaybeFake been true for 2
minutes.
+ detect = true;
+ }
+
+ } else {
+ Logger.minor(this, "Not fake");
+ firstTimeMaybeFakePeers = 0;
+ }
+
+ if(detect) {
+ if(now - lastDetectAttemptEndedTime <
60*60*1000) {
+ // Only try every hour
+ Logger.minor(this, "Only trying
once per hour");
+ return;
+ }
+
+ startDetect();
+ }
+
+ }
+ }
+
+ }
+
+ private void startDetect() {
+ Logger.minor(this, "Detecting...");
+ synchronized(this) {
+ runner = new DetectorRunner();
+ Thread t = new Thread(runner);
+ t.setDaemon(true);
+ t.start();
+ }
+ }
+
+ public class DetectorRunner implements Runnable {
+
+ public void run() {
+ try {
+ realRun();
+ } catch (Throwable t) {
+ Logger.error(this, "Caught "+t, t);
+ }
+ }
+
+ public void realRun() {
+ Logger.minor(this, "Running STUN detection");
+ try {
+ FredPluginIPDetector[] run = plugins;
+ Vector v = new Vector();
+ for(int i=0;i<run.length;i++) {
+ DetectedIP[] detected = null;
+ try {
+ detected = run[i].getAddress();
+ } catch (Throwable t) {
+ Logger.error(this, "Caught "+t, t);
+ }
+ if(detected != null) {
+ for(int j=0;j<detected.length;j++)
+ v.add(detected[j]);
+ }
+ }
+ synchronized(IPDetectorPluginManager.this) {
+ lastDetectAttemptEndedTime =
System.currentTimeMillis();
+ boolean failed = false;
+ if(v.isEmpty()) {
+ Logger.minor(this, "No IPs found");
+ failed = true;
+ } else {
+ failed = true;
+ for(int i=0;i<v.size();i++) {
+ DetectedIP ip = (DetectedIP)
v.get(i);
+ if(!(ip.publicAddress == null
|| !IPUtil.checkAddress(ip.publicAddress))) {
+ Logger.minor(this,
"Address checked out");
+ failed = false;
+ }
+ }
+ }
+ if(failed) {
+ Logger.minor(this, "Failed");
+ lastDetectAttemptFailed = true;
+ return;
+ }
+ }
+ // Now tell the node
+ HashMap map = new HashMap();
+ 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()]);
+ for(int i=0;i<list.length;i++)
+ Logger.minor(this, "Detected IP:
"+list[i].publicAddress+ " : type "+list[i].natType);
+ node.processDetectedIPs(list);
+ } finally {
+ runner = null;
+ }
+ }
+
+ }
+
+}
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2006-07-05 20:57:51 UTC (rev
9471)
+++ trunk/freenet/src/freenet/node/Node.java 2006-07-05 21:47:17 UTC (rev
9472)
@@ -99,6 +99,7 @@
import freenet.node.useralerts.N2NTMUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.node.useralerts.UserAlertManager;
+import freenet.pluginmanager.DetectedIP;
import freenet.pluginmanager.FredPluginIPDetector;
import freenet.pluginmanager.PluginManager;
import freenet.store.BerkeleyDBFreenetStore;
@@ -479,6 +480,8 @@
private final HashMap insertSenders;
/** IP address detector */
private final IPAddressDetector ipDetector;
+ /** Plugin manager for plugin IP address detectors e.g. STUN */
+ private final IPDetectorPluginManager ipDetectorManager;
/** My crypto group */
private DSAGroup myCryptoGroup;
/** My private key */
@@ -976,6 +979,7 @@
startupTime = System.currentTimeMillis();
throttleWindow = new ThrottleWindowManager(2.0);
alerts = new UserAlertManager();
+ ipDetectorManager = new IPDetectorPluginManager(this);
nodeNameUserAlert = new MeaningfulNodeNameUserAlert();
recentlyCompletedIDs = new LRUQueue();
this.config = config;
@@ -1577,6 +1581,7 @@
ps.start();
usm.start();
myMemoryChecker.start();
+ ipDetectorManager.start();
if(isUsingWrapper()) {
Logger.normal(this, "Using wrapper correctly:
"+nodeStarter);
@@ -2242,6 +2247,8 @@
FreenetInetAddress overrideIPAddress;
/** IP address from last time */
FreenetInetAddress oldIPAddress;
+ /** Detected IP's and their NAT status from plugins */
+ DetectedIP[] pluginDetectedIPs;
/** Last detected IP address */
Peer[] lastIPAddress;
@@ -2263,6 +2270,15 @@
if(!addresses.contains(a))
addresses.add(a);
}
+ if(pluginDetectedIPs != null && pluginDetectedIPs.length > 0) {
+ for(int i=0;i<pluginDetectedIPs.length;i++) {
+ InetAddress addr =
pluginDetectedIPs[i].publicAddress;
+ if(addr == null) continue;
+ Peer a = new Peer(new FreenetInetAddress(addr),
portNumber);
+ if(!addresses.contains(a))
+ addresses.add(a);
+ }
+ }
if(detectedAddr == null && oldIPAddress != null &&
!oldIPAddress.equals(overrideIPAddress))
addresses.add(new Peer(oldIPAddress, portNumber));
// Try to pick it up from our connections
@@ -3049,6 +3065,7 @@
Logger.minor(this, "onConnectedPeer()");
if(arkPutter != null)
arkPutter.onConnectedPeer();
+ ipDetectorManager.maybeRun();
}
public String getBindTo(){
@@ -3389,9 +3406,8 @@
}
public void registerIPDetectorPlugin(FredPluginIPDetector detector) {
- // FIXME do something
- // TODO Auto-generated method stub
- }
+ ipDetectorManager.register(detector);
+ } // FIXME what about unloading?
/**
* Return a peer of the node given its ip and port, name or identity,
as a String
@@ -3418,4 +3434,21 @@
public boolean isHasStarted() {
return hasStarted;
}
+
+ public boolean hasDirectlyDetectedIP() {
+ return (ipDetector.getAddress() != null);
+ }
+
+ /**
+ * Process a list of DetectedIP's from the IP detector plugin manager.
+ * DetectedIP's can tell us what kind of NAT we are behind as well as
our public
+ * IP address.
+ */
+ public void processDetectedIPs(DetectedIP[] list) {
+ Vector fullAccess = new Vector();
+ Vector fullCone = new Vector();
+ pluginDetectedIPs = list;
+ redetectAddress();
+ shouldInsertARK();
+ }
}
Modified: trunk/freenet/src/freenet/node/Version.java
===================================================================
--- trunk/freenet/src/freenet/node/Version.java 2006-07-05 20:57:51 UTC (rev
9471)
+++ trunk/freenet/src/freenet/node/Version.java 2006-07-05 21:47:17 UTC (rev
9472)
@@ -18,7 +18,7 @@
public static final String protocolVersion = "1.0";
/** The build number of the current revision */
- private static final int buildNumber = 859;
+ private static final int buildNumber = 860;
/** Oldest build of Fred we will talk to */
private static final int oldLastGoodBuild = 839;
Modified: trunk/freenet/src/freenet/pluginmanager/DetectedIP.java
===================================================================
--- trunk/freenet/src/freenet/pluginmanager/DetectedIP.java 2006-07-05
20:57:51 UTC (rev 9471)
+++ trunk/freenet/src/freenet/pluginmanager/DetectedIP.java 2006-07-05
21:47:17 UTC (rev 9472)
@@ -16,9 +16,7 @@
public final short natType;
// Constants
/** The plugin does not support detecting the NAT type. */
- public static final short NOT_SUPPORTED = 0;
- /** No UDP connectivity at all */
- public static final short NO_UDP = 1;
+ public static final short NOT_SUPPORTED = 1;
/** Full internet access! */
public static final short FULL_INTERNET = 2;
/** Full cone NAT. Once we have sent a packet out on a port, any node
anywhere can send us
@@ -35,10 +33,23 @@
public static final short SYMMETRIC_NAT = 6;
/** Symmetric UDP firewall. We are not NATed, but the firewall behaves
as if we were. */
public static final short SYMMETRIC_UDP_FIREWALL = 7;
+ /** No UDP connectivity at all */
+ public static final short NO_UDP = 8;
public DetectedIP(InetAddress addr, short type) {
this.publicAddress = addr;
this.natType = type;
}
+ public boolean equals(Object o) {
+ if(!(o instanceof DetectedIP)) {
+ return false;
+ }
+ DetectedIP d = (DetectedIP)o;
+ return (d.natType == natType &&
d.publicAddress.equals(publicAddress));
+ }
+
+ public int hashCode() {
+ return publicAddress.hashCode() ^ natType;
+ }
}