- Switch to real kad with lib from i2p.zzz.kademlia (not checked in yet)

- Bootstrap only once in explore thread
- Add exploring to explore thread
- Don't store default DHT setting in config file, so we can switch default to true later
- Add new enforce-protocol streaming config, sorry locks out < 0.7.1 peers
- Log tweaks
This commit is contained in:
zzz
2012-06-22 15:12:43 +00:00
parent f8e470c7f4
commit 8522779df1
6 changed files with 76 additions and 49 deletions

View File

@ -240,6 +240,8 @@ public class I2PSnarkUtil {
opts.setProperty("i2p.streaming.maxTotalConnsPerMinute", "8");
if (opts.getProperty("i2p.streaming.maxConnsPerHour") == null)
opts.setProperty("i2p.streaming.maxConnsPerHour", "20");
if (opts.getProperty("i2p.streaming.enforceProtocol") == null)
opts.setProperty("i2p.streaming.enforceProtocol", "true");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
_connecting = false;
}

View File

@ -289,8 +289,9 @@ public class SnarkManager implements Snark.CompleteListener {
_config.setProperty(PROP_STARTUP_DELAY, Integer.toString(DEFAULT_STARTUP_DELAY));
if (!_config.containsKey(PROP_THEME))
_config.setProperty(PROP_THEME, DEFAULT_THEME);
if (!_config.containsKey(PROP_USE_DHT))
_config.setProperty(PROP_USE_DHT, Boolean.toString(I2PSnarkUtil.DEFAULT_USE_DHT));
// no, so we can switch default to true later
//if (!_config.containsKey(PROP_USE_DHT))
// _config.setProperty(PROP_USE_DHT, Boolean.toString(I2PSnarkUtil.DEFAULT_USE_DHT));
updateConfig();
}
/**
@ -365,7 +366,9 @@ public class SnarkManager implements Snark.CompleteListener {
String useOT = _config.getProperty(PROP_USE_OPENTRACKERS);
boolean bOT = useOT == null || Boolean.valueOf(useOT).booleanValue();
_util.setUseOpenTrackers(bOT);
_util.setUseDHT(Boolean.valueOf(_config.getProperty(PROP_USE_DHT)).booleanValue());
// careful, so we can switch default to true later
_util.setUseDHT(Boolean.valueOf(_config.getProperty(PROP_USE_DHT,
Boolean.toString(I2PSnarkUtil.DEFAULT_USE_DHT))).booleanValue());
getDataDir().mkdirs();
initTrackerMap();
}

View File

@ -15,15 +15,15 @@ import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA1Hash;
import net.i2p.data.DataHelper;
import net.i2p.kademlia.KBucketSet;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* All the nodes we know about, stored as a mapping from
* node ID to a Destination and Port.
* Also uses the keySet as a subsitute for kbuckets.
*
* Swap this out for a real DHT later.
* And a real Kademlia routing table, which stores node IDs only.
*
* @since 0.8.4
* @author zzz
@ -34,6 +34,7 @@ class DHTNodes {
private long _expireTime;
private final Log _log;
private final ConcurrentHashMap<NID, NodeInfo> _nodeMap;
private final KBucketSet<NID> _kad;
private volatile boolean _isRunning;
/** stagger with other cleaners */
@ -43,11 +44,12 @@ class DHTNodes {
private static final long DELTA_EXPIRE_TIME = 7*60*1000;
private static final int MAX_PEERS = 999;
public DHTNodes(I2PAppContext ctx) {
public DHTNodes(I2PAppContext ctx, NID me) {
_context = ctx;
_expireTime = MAX_EXPIRE_TIME;
_log = _context.logManager().getLog(DHTNodes.class);
_nodeMap = new ConcurrentHashMap();
_kad = new KBucketSet(ctx, me, 8, 1);
}
public void start() {
@ -67,6 +69,7 @@ class DHTNodes {
}
public void clear() {
_kad.clear();
_nodeMap.clear();
}
@ -75,13 +78,15 @@ class DHTNodes {
}
/**
* @return the old value if present, else nInfo
* @return the old value if present, else null
*/
public NodeInfo putIfAbsent(NodeInfo nInfo) {
_kad.add(nInfo.getNID());
return _nodeMap.putIfAbsent(nInfo.getNID(), nInfo);
}
public NodeInfo remove(NID nid) {
_kad.remove(nid);
return _nodeMap.remove(nid);
}
@ -92,36 +97,31 @@ class DHTNodes {
// end ConcurrentHashMap methods
/**
* Fake DHT
* DHT
* @param sha1 either a InfoHash or a NID
*/
List<NodeInfo> findClosest(SHA1Hash h, int numWant) {
// sort the whole thing
Set<NID> all = new TreeSet(new SHA1Comparator(h));
all.addAll(_nodeMap.keySet());
int sz = all.size();
int max = Math.min(numWant, sz);
// return the first ones
List<NodeInfo> rv = new ArrayList(max);
int count = 0;
for (NID nid : all) {
if (count++ >= max)
break;
NodeInfo nInfo = get(nid);
if (nInfo == null)
continue;
rv.add(nInfo);
public List<NodeInfo> findClosest(SHA1Hash h, int numWant) {
NID key;
if (h instanceof NID)
key = (NID) h;
else
key = new NID(h.getData());
List<NID> keys = _kad.getClosest(key, numWant);
List<NodeInfo> rv = new ArrayList(keys.size());
for (NID nid : keys) {
NodeInfo ninfo = _nodeMap.get(nid);
if (ninfo != null)
rv.add(ninfo);
}
return rv;
}
/**** used CHM methods to be replaced:
public Collection<NodeInfo> values() {}
public NodeInfo get(NID nID) {}
public NodeInfo putIfAbssent(NID nID, NodeInfo nInfo) {}
public int size() {}
****/
/**
* DHT - get random keys to explore
*/
public List<NID> getExploreKeys() {
return _kad.getExploreKeys(15*60*1000);
}
/** */
private class Cleaner extends SimpleTimer2.TimedEvent {
@ -137,10 +137,12 @@ class DHTNodes {
int peerCount = 0;
for (Iterator<NodeInfo> iter = DHTNodes.this.values().iterator(); iter.hasNext(); ) {
NodeInfo peer = iter.next();
if (peer.lastSeen() < now - _expireTime)
if (peer.lastSeen() < now - _expireTime) {
iter.remove();
else
_kad.remove(peer.getNID());
} else {
peerCount++;
}
}
if (peerCount > MAX_PEERS)

View File

@ -110,6 +110,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
private final int _qPort;
private final File _dhtFile;
private volatile boolean _isRunning;
private volatile boolean _hasBootstrapped;
/** all-zero NID used for pings */
public static final NID FAKE_NID = new NID(new byte[NID.HASH_LENGTH]);
@ -147,8 +148,6 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
_log = ctx.logManager().getLog(KRPC.class);
_tracker = new DHTTracker(ctx);
// in place of a DHT, store everybody we hear from for now
_knownNodes = new DHTNodes(ctx);
_sentQueries = new ConcurrentHashMap();
_outgoingTokens = new ConcurrentHashMap();
_incomingTokens = new ConcurrentHashMap();
@ -168,6 +167,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
}
_myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
_dhtFile = new File(ctx.getConfigDir(), DHT_FILE);
_knownNodes = new DHTNodes(ctx, _myNID);
session.addMuxedSessionListener(this, I2PSession.PROTO_DATAGRAM_RAW, _rPort);
session.addMuxedSessionListener(this, I2PSession.PROTO_DATAGRAM, _qPort);
@ -206,25 +206,24 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* Blocking!
* This is almost the same as getPeers()
*
* @param target the key we are searching for
* @param maxNodes how many to contact
* @param maxWait how long to wait for each to reply (not total) must be > 0
* @param parallel how many outstanding at once (unimplemented, always 1)
*/
public void explore(int maxNodes, long maxWait, int parallel) {
// Initial set to try, will get added to as we go
NID myNID = _myNodeInfo.getNID();
List<NodeInfo> nodes = _knownNodes.findClosest(myNID, maxNodes);
private void explore(NID target, int maxNodes, long maxWait, int parallel) {
List<NodeInfo> nodes = _knownNodes.findClosest(target, maxNodes);
if (nodes.isEmpty()) {
if (_log.shouldLog(Log.WARN))
_log.info("DHT is empty, cannot explore");
return;
}
SortedSet<NodeInfo> toTry = new TreeSet(new NodeInfoComparator(myNID));
SortedSet<NodeInfo> toTry = new TreeSet(new NodeInfoComparator(target));
toTry.addAll(nodes);
Set<NodeInfo> tried = new HashSet();
if (_log.shouldLog(Log.INFO))
_log.info("Starting explore");
_log.info("Starting explore of " + target);
for (int i = 0; i < maxNodes; i++) {
if (!_isRunning)
break;
@ -237,8 +236,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
toTry.remove(nInfo);
tried.add(nInfo);
// this isn't going to work, he will just return our own?
ReplyWaiter waiter = sendFindNode(nInfo, _myNID);
ReplyWaiter waiter = sendFindNode(nInfo, target);
if (waiter == null)
continue;
synchronized(waiter) {
@ -266,7 +264,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
}
}
if (_log.shouldLog(Log.INFO))
_log.info("Finished explore");
_log.info("Finished explore of " + target);
}
/**
@ -1441,7 +1439,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
public void timeReached() {
if (!_isRunning)
return;
(new I2PAppThread(new ExplorerThread(), "DHT Explore", true)).start();
if (_knownNodes.size() > 0)
(new I2PAppThread(new ExplorerThread(), "DHT Explore", true)).start();
else
schedule(60*1000);
}
}
@ -1453,10 +1454,24 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
public void run() {
if (!_isRunning)
return;
explore(8, 60*1000, 1);
// refresh the buckets here too
if (!_hasBootstrapped) {
if (_log.shouldLog(Log.INFO))
_log.info("Bootstrap start size: " + _knownNodes.size());
explore(_myNID, 8, 60*1000, 1);
if (_log.shouldLog(Log.INFO))
_log.info("Bootstrap done size: " + _knownNodes.size());
_hasBootstrapped = true;
}
if (!_isRunning)
return;
if (_log.shouldLog(Log.INFO))
_log.info("Explore start size: " + _knownNodes.size());
List<NID> keys = _knownNodes.getExploreKeys();
for (NID nid : keys) {
explore(nid, 8, 60*1000, 1);
}
if (_log.shouldLog(Log.INFO))
_log.info("Explore done size: " + _knownNodes.size());
new Explorer(EXPLORE_TIME);
}
}

View File

@ -8,17 +8,22 @@ import net.i2p.util.Clock;
/**
* A 20-byte peer ID, used as a Map key in lots of places.
* Must be public for constructor in KBucketSet.generateRandomKey()
*
* @since 0.8.4
* @author zzz
*/
class NID extends SHA1Hash {
public class NID extends SHA1Hash {
private long lastSeen;
private int fails;
private static final int MAX_FAILS = 3;
public NID() {
super(null);
}
public NID(byte[] data) {
super(data);
}

View File

@ -225,7 +225,7 @@ class NodeInfo extends SimpleDataStructure {
@Override
public String toString() {
return "NodeInfo: " + nID + ' ' + hash + " port: " + port;
return "NodeInfo: " + nID + ' ' + hash + " port: " + port + (dest != null ? " known dest" : " null dest");
}
/**