diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java index ac53f38b6..2bb9feb5f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java @@ -28,6 +28,8 @@ import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.Log; +import org.klomp.snark.dht.DHT; + /** * TimerTask that checks for good/bad up/downloader. Works together * with the PeerCoordinator to select which Peers get (un)choked. @@ -74,6 +76,7 @@ class PeerCheckerTask implements Runnable List removed = new ArrayList(); int uploadLimit = coordinator.allowedUploaders(); boolean overBWLimit = coordinator.overUpBWLimit(); + DHT dht = _util.getDHT(); for (Peer peer : peerList) { // Remove dying peers @@ -218,8 +221,8 @@ class PeerCheckerTask implements Runnable if (coordinator.getNeededLength() > 0 || !peer.isCompleted()) peer.keepAlive(); // announce them to local tracker (TrackerClient does this too) - if (_util.getDHT() != null && (_runCount % 5) == 0) { - _util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash()); + if (dht != null && (_runCount % 5) == 0) { + dht.announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash()); } } @@ -267,8 +270,8 @@ class PeerCheckerTask implements Runnable } // announce ourselves to local tracker (TrackerClient does this too) - if (_util.getDHT() != null && (_runCount % 16) == 0) { - _util.getDHT().announce(coordinator.getInfoHash()); + if (dht != null && (_runCount % 16) == 0) { + dht.announce(coordinator.getInfoHash()); } } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 459486831..25fa0ffa0 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -577,6 +577,8 @@ public class SnarkManager implements Snark.CompleteListener { addMessage(_("Enabled DHT.")); else addMessage(_("Disabled DHT.")); + if (_util.connected()) + addMessage(_("DHT change requires tunnel shutdown and reopen")); _util.setUseDHT(useDHT); changed = true; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index ad26660ee..ccb028134 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -43,6 +43,8 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; +import org.klomp.snark.dht.DHT; + /** * Informs metainfo tracker of events and gets new peers for peer * coordinator. @@ -323,8 +325,9 @@ public class TrackerClient implements Runnable { } // Local DHT tracker announce - if (_util.getDHT() != null) - _util.getDHT().announce(snark.getInfoHash()); + DHT dht = _util.getDHT(); + if (dht != null) + dht.announce(snark.getInfoHash()); long uploaded = coordinator.getUploaded(); long downloaded = coordinator.getDownloaded(); @@ -372,9 +375,10 @@ public class TrackerClient implements Runnable { snark.setTrackerSeenPeers(tr.seenPeers); // pass everybody over to our tracker - if (_util.getDHT() != null) { + dht = _util.getDHT(); + if (dht != null) { for (Peer peer : peers) { - _util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash()); + dht.announce(snark.getInfoHash(), peer.getPeerID().getDestHash()); } } @@ -458,20 +462,21 @@ public class TrackerClient implements Runnable { // Get peers from DHT // FIXME this needs to be in its own thread - if (_util.getDHT() != null && (meta == null || !meta.isPrivate()) && !stop) { + dht = _util.getDHT(); + if (dht != null && (meta == null || !meta.isPrivate()) && !stop) { int numwant; if (event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers()) numwant = 1; else numwant = _util.getMaxConnections(); - List hashes = _util.getDHT().getPeers(snark.getInfoHash(), numwant, 2*60*1000); + List hashes = dht.getPeers(snark.getInfoHash(), numwant, 2*60*1000); if (_log.shouldLog(Log.INFO)) _log.info("Got " + hashes + " from DHT"); // announce ourselves while the token is still good // FIXME this needs to be in its own thread if (!stop) { // announce only to the 1 closest - int good = _util.getDHT().announce(snark.getInfoHash(), 1, 5*60*1000); + int good = dht.announce(snark.getInfoHash(), 1, 5*60*1000); if (_log.shouldLog(Log.INFO)) _log.info("Sent " + good + " good announces to DHT"); } @@ -548,8 +553,9 @@ public class TrackerClient implements Runnable { */ private void unannounce() { // Local DHT tracker unannounce - if (_util.getDHT() != null) - _util.getDHT().unannounce(snark.getInfoHash()); + DHT dht = _util.getDHT(); + if (dht != null) + dht.unannounce(snark.getInfoHash()); int i = 0; for (Tracker tr : trackers) { if (_util.connected() && diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java b/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java index 2e401f060..d5642835b 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java @@ -89,4 +89,9 @@ public interface DHT { * Stop everything. */ public void stop(); + + /** + * Known nodes, not estimated total network size. + */ + public int size(); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java index a224b1416..37724825d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java @@ -158,7 +158,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { _qPort = 2555 + ctx.random().nextInt(61111); _rPort = _qPort + 1; if (SECURE_NID) { - _myNID = NodeInfo.generateNID(session.getMyDestination().calculateHash(), _qPort); + _myNID = NodeInfo.generateNID(session.getMyDestination().calculateHash(), _qPort, _context.random()); _myID = _myNID.getData(); } else { _myID = new byte[NID.HASH_LENGTH]; @@ -176,6 +176,13 @@ public class KRPC implements I2PSessionMuxedListener, DHT { ///////////////// Public methods + /** + * Known nodes, not estimated total network size. + */ + public int size() { + return _knownNodes.size(); + } + /** * @return The UDP query port */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java index f9a919c00..9ed1a2912 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java @@ -9,6 +9,7 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.RandomSource; /* * A Node ID, Hash, and port, and an optional Destination. @@ -139,29 +140,35 @@ class NodeInfo extends SimpleDataStructure { } /** - * Generate a secure NID that matches the Hash and port + * Generate a secure NID that matches the Hash and port. + * Rules: First 4 bytes must match Hash. + * Next 2 bytes must match Hash ^ port. + * Remaining bytes may be random. + * * @throws IllegalArgumentException */ - public static NID generateNID(Hash h, int p) { + public static NID generateNID(Hash h, int p, RandomSource random) { byte[] n = new byte[NID.HASH_LENGTH]; - System.arraycopy(h.getData(), 0, n, 0, NID.HASH_LENGTH); - n[0] ^= (byte) (p >> 8); - n[1] ^= (byte) p; + System.arraycopy(h.getData(), 0, n, 0, 6); + n[4] ^= (byte) (p >> 8); + n[5] ^= (byte) p; + random.nextBytes(n, 6, NID.HASH_LENGTH - 6); return new NID(n); } /** - * Verify the NID matches the Hash - * @throws IllegalArgumentException + * Verify the NID matches the Hash. + * See generateNID() for requirements. + * @throws IllegalArgumentException on mismatch */ private void verify() { if (!KRPC.SECURE_NID) return; byte[] nb = nID.getData(); byte[] hb = hash.getData(); - if ((!DataHelper.eq(nb, 2, hb, 2, NID.HASH_LENGTH - 2)) || - ((nb[0] ^ (port >> 8)) & 0xff) != (hb[0] & 0xff) || - ((nb[1] ^ port) & 0xff) != (hb[1] & 0xff)) + if ((!DataHelper.eq(nb, 0, hb, 0, 4)) || + ((nb[4] ^ (port >> 8)) & 0xff) != (hb[4] & 0xff) || + ((nb[5] ^ port) & 0xff) != (hb[5] & 0xff)) throw new IllegalArgumentException("NID/Hash mismatch"); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 5a7bec05f..3eafa3ecd 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -42,6 +42,7 @@ import org.klomp.snark.SnarkManager; import org.klomp.snark.Storage; import org.klomp.snark.Tracker; import org.klomp.snark.TrackerClient; +import org.klomp.snark.dht.DHT; import org.mortbay.jetty.servlet.DefaultServlet; import org.mortbay.resource.Resource; @@ -470,6 +471,14 @@ public class I2PSnarkServlet extends DefaultServlet { out.write(", "); out.write(DataHelper.formatSize2(stats[5]) + "B, "); out.write(ngettext("1 connected peer", "{0} connected peers", (int) stats[4])); + DHT dht = _manager.util().getDHT(); + if (dht != null) { + int dhts = dht.size(); + if (dhts > 0) { + out.write(", "); + out.write(ngettext("1 DHT peer", "{0} DHT peers", dhts)); + } + } out.write("\n"); if (_manager.util().connected()) { out.write(" " + formatSize(stats[0]) + "\n" +