Author: robert
Date: 2008-02-06 20:04:55 +0000 (Wed, 06 Feb 2008)
New Revision: 17609
Added:
trunk/freenet/src/freenet/node/NetworkIDManager.java
Modified:
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/NodeDispatcher.java
Log:
initial SecretPing implementation (for discussion; untested & disabled)
Added: trunk/freenet/src/freenet/node/NetworkIDManager.java
===================================================================
--- trunk/freenet/src/freenet/node/NetworkIDManager.java
(rev 0)
+++ trunk/freenet/src/freenet/node/NetworkIDManager.java 2008-02-06
20:04:55 UTC (rev 17609)
@@ -0,0 +1,223 @@
+
+package freenet.node;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+import freenet.io.comm.DMT;
+import freenet.io.comm.DisconnectedException;
+import freenet.io.comm.Message;
+import freenet.io.comm.MessageFilter;
+import freenet.io.comm.NotConnectedException;
+
+import freenet.support.Logger;
+
+/**
+ * Handles the processing of challenge/response pings as well as the storage
of the secrets pertaining thereto.
+ * It may (eventually) also handle the separation of peers into network peer
groups.
+ * @author robert
+ * @created 2008-02-06
+ */
+class NetworkIDManager {
+ private static boolean disableSecretPings=true;
+
+ private static final int SECRETPONG_TIMEOUT=10000;
+ private final boolean logMINOR;
+
+ //Atomic: Locking for both via secretsByPeer
+ private final HashMap secretsByPeer=new HashMap();
+ private final HashMap secretsByUID=new HashMap();
+
+ private final Node node;
+
+ NetworkIDManager(Node node) {
+ this.node=node;
+ this.logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ }
+
+ /**
+ * Stores the secret&uid contained in the message associated with the
peer it comes from.
+ * "FNPStoreSecret" messages are *never* forwarded, they are only
between peers as an alert
+ * that they may be asked for the secret from a third party.
+ */
+ public boolean handleStoreSecret(Message m) {
+ PeerNode pn=(PeerNode)m.getSource();
+ long uid = m.getLong(DMT.UID);
+ long secret = m.getLong(DMT.SECRET);
+ StoredSecret s=new StoredSecret(pn, uid, secret);
+ Logger.error(this, "Storing secret: "+s);
+ addOrReplaceSecret(s);
+ return true;
+ }
+
+ public boolean handleSecretPing(Message m) {
+ try {
+ return _handleSecretPing(m);
+ } catch (NotConnectedException e) {
+ Logger.normal(this, "secretPing/not connected: "+e);
+ return false;
+ }
+ }
+
+ /*
+ @throws NotConnectedException if the *source* goes away
+ */
+ private boolean _handleSecretPing(Message m) throws
NotConnectedException {
+ PeerNode source=(PeerNode)m.getSource();
+ long uid = m.getLong(DMT.UID);
+ short htl = m.getShort(DMT.HTL);
+ short dawnHtl=m.getShort(DMT.DAWN_HTL);
+ int counter=m.getInt(DMT.COUNTER);
+
+ if (disableSecretPings || node.recentlyCompleted(uid)) {
+ source.sendAsync(DMT.createFNPRejectedLoop(uid), null,
0, null);
+ } else {
+ StoredSecret match;
+ //Yes, I know... it looks really weird sync.ing on a
separate map...
+ synchronized (secretsByPeer) {
+ match=(StoredSecret)secretsByUID.get(new
Long(uid));
+ }
+ if (match!=null) {
+ //This is the node that the ping intends to
reach, we will *not* forward it; but we might not respond positively either.
+ //don't set the completed flag, we might reject
it from one peer (too short a path) and accept it from another.
+ if (htl > dawnHtl) {
+
source.sendAsync(DMT.createFNPRejectedLoop(uid), null, 0, null);
+ } else {
+ Logger.error(this, "Responding to
"+source+" with "+match+" from "+match.peer);
+
source.sendAsync(match.getSecretPong(counter+1), null, 0, null);
+ }
+ } else {
+ //Set the completed flag immediately for
determining reject loops rather than locking the uid.
+ node.completed(uid);
+
+ //Not a local match... forward
+ double target=m.getDouble(DMT.TARGET_LOCATION);
+ HashSet routedTo=new HashSet();
+ HashSet notIgnored=new HashSet();
+ while (true) {
+ PeerNode next;
+
+ if (htl > dawnHtl &&
routedTo.isEmpty()) {
+
next=node.peers.getRandomPeer(source);
+ } else {
+
next=node.peers.closerPeer(source, routedTo, notIgnored, target, true,
node.isAdvancedModeEnabled(), -1, null);
+ }
+
+ if (next==null) {
+ //would be rnf... but this is a
more exhaustive and lightweight search I suppose.
+
source.sendAsync(DMT.createFNPRejectedLoop(uid), null, 0, null);
+ break;
+ }
+
+ htl=next.decrementHTL(htl);
+
+ if (htl<=0) {
+ //would be dnf if we were
looking for data.
+
source.sendAsync(DMT.createFNPRejectedLoop(uid), null, 0, null);
+ break;
+ }
+
+ if (!source.isConnected()) {
+ throw new
NotConnectedException("source gone away while forwarding");
+ }
+
+ counter++;
+ routedTo.add(next);
+ try {
+
next.sendAsync(DMT.createFNPSecretPing(uid, target, htl, dawnHtl, counter),
null, 0, null);
+ } catch (NotConnectedException e) {
+ Logger.normal(this, next+"
disconnected before secret-ping-forward");
+ continue;
+ }
+
+ //wait for a reject or pong
+ MessageFilter mfPong =
MessageFilter.create().setSource(next).setField(DMT.UID,
uid).setTimeout(SECRETPONG_TIMEOUT).setType(DMT.FNPSecretPong);
+ MessageFilter mfRejectLoop =
MessageFilter.create().setSource(next).setField(DMT.UID,
uid).setTimeout(SECRETPONG_TIMEOUT).setType(DMT.FNPRejectedLoop);
+ Message msg;
+
+ try {
+ msg =
node.usm.waitFor(mfPong.or(mfRejectLoop), null);
+ } catch (DisconnectedException e) {
+ Logger.normal(this, next+"
disconnected while waiting for a secret-pong");
+ continue;
+ }
+
+ if (msg==null) {
+ Logger.error(this, "fatal
timeout in waiting for secretpong from "+next);
+ //backoff?
+ break;
+ }
+
+ if (msg.getSpec() == DMT.FNPSecretPong)
{
+ int
suppliedCounter=msg.getInt(DMT.COUNTER);
+ if (suppliedCounter>counter)
+ counter=suppliedCounter;
+ long
secret=msg.getLong(DMT.SECRET);
+ Logger.error(this, node+"
forwarding apparently-successful secretpong response: "+counter+"/"+secret+"
from "+next+" to "+source);
+
source.sendAsync(DMT.createFNPSecretPong(uid, counter, secret), null, 0, null);
+ break;
+ }
+
+ if (msg.getSpec() ==
DMT.FNPRejectedLoop) {
+ if (logMINOR)
Logger.minor(this, "secret ping (reject/loop): "+source+" -> "+next);
+ continue;
+ }
+
+ Logger.error(this, "unexpected message
type: "+msg);
+ break;
+ }
+ }
+ //unlockUID()
+ }
+ return true;
+ }
+
+ //FIXME: This needs to be wired in.
+ public void onDisconnect(PeerNode pn) {
+ synchronized (secretsByPeer) {
+ StoredSecret s=(StoredSecret)secretsByPeer.get(pn);
+ if (s!=null) {
+ //???: Might it still be valid to respond to
secret pings when the neighbor requesting it has disconnected? (super-secret
ping?)
+ Logger.error(this, "Removing on disconnect:
"+s);
+ removeSecret(s);
+ }
+ }
+ }
+
+ private void addOrReplaceSecret(StoredSecret s) {
+ synchronized (secretsByPeer) {
+ StoredSecret
prev=(StoredSecret)secretsByPeer.get(s.peer);
+ if (prev!=null) {
+ Logger.normal(this, "Removing on replacement:
"+s);
+ removeSecret(prev);
+ }
+ //Need to remember by peer (so we can remove it on
disconnect)
+ //Need to remember by uid (so we can respond quickly to
arbitrary requests).
+ secretsByPeer.put(s.peer, s);
+ secretsByUID.put(new Long(s.uid), s);
+ }
+ }
+
+ private void removeSecret(StoredSecret s) {
+ //synchronized (secretsByPeer) in calling functions
+ secretsByPeer.remove(s);
+ secretsByUID.remove(s);
+ }
+
+ private static final class StoredSecret {
+ PeerNode peer;
+ long uid;
+ long secret;
+ StoredSecret(PeerNode peer, long uid, long secret) {
+ this.peer=peer;
+ this.uid=uid;
+ this.secret=secret;
+ }
+ public String toString() {
+ return "Secret("+uid+"/"+secret+")";
+ }
+ Message getSecretPong(int counter) {
+ return DMT.createFNPSecretPong(uid, counter, secret);
+ }
+ }
+}
\ No newline at end of file
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2008-02-06 19:43:39 UTC (rev
17608)
+++ trunk/freenet/src/freenet/node/Node.java 2008-02-06 20:04:55 UTC (rev
17609)
@@ -189,6 +189,7 @@
/** Stats */
public final NodeStats nodeStats;
+ public final NetworkIDManager netid;
/** Config object for the whole node. */
public final PersistentConfig config;
@@ -1445,6 +1446,8 @@
clientCore = new NodeClientCore(this, config, nodeConfig,
nodeDir, getDarknetPortNumber(), sortOrder, oldThrottleFS == null ? null :
oldThrottleFS.subset("RequestStarters"), oldConfig, fproxyConfig, toadlets);
+ netid = new NetworkIDManager(this);
+
nodeConfig.register("disableHangCheckers", false, sortOrder++,
true, false, "Node.disableHangCheckers", "Node.disableHangCheckersLong", new
BooleanCallback() {
public boolean get() {
Modified: trunk/freenet/src/freenet/node/NodeDispatcher.java
===================================================================
--- trunk/freenet/src/freenet/node/NodeDispatcher.java 2008-02-06 19:43:39 UTC
(rev 17608)
+++ trunk/freenet/src/freenet/node/NodeDispatcher.java 2008-02-06 20:04:55 UTC
(rev 17609)
@@ -74,6 +74,10 @@
// Ignore
}
return true;
+ } else if (spec == DMT.FNPStoreSecret) {
+ return node.netid.handleStoreSecret(m);
+ } else if(spec == DMT.FNPSecretPing) {
+ return node.netid.handleSecretPing(m);
} else if(spec == DMT.FNPDetectedIPAddress) {
Peer p = (Peer) m.getObject(DMT.EXTERNAL_ADDRESS);
source.setRemoteDetectedPeer(p);
@@ -441,6 +445,7 @@
}
}
+ //FIXME: PLEASE! When are these contexts ever cleaned up!!!
Memory-leak-per-ping!
final Hashtable routedContexts = new Hashtable();
static class RoutedContext {