From e692e18d44339b111014493b496dc092550c2550 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 30 Mar 2009 16:05:48 +0000 Subject: [PATCH] Peer Selection: - Limit peers to a max % of all tunnels with router.maxTunnelPercentage=nn, default 33 - Add chart to tunnels.jsp to see results --- .../i2p/router/DummyTunnelManagerFacade.java | 2 + .../net/i2p/router/TunnelManagerFacade.java | 4 + .../tunnel/pool/TunnelPeerSelector.java | 1 + .../i2p/router/tunnel/pool/TunnelPool.java | 4 +- .../router/tunnel/pool/TunnelPoolManager.java | 133 ++++++++++++++++++ 5 files changed, 142 insertions(+), 2 deletions(-) diff --git a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java index f7f204857a..aec0390fd3 100644 --- a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java @@ -10,6 +10,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; +import java.util.Set; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -44,6 +45,7 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade { public void setInboundSettings(Hash client, TunnelPoolSettings settings) {} public void setOutboundSettings(Hash client, TunnelPoolSettings settings) {} public int getInboundBuildQueueSize() { return 0; } + public Set<Hash> selectPeersInTooManyTunnels() { return null; } public void renderStatusHTML(Writer out) throws IOException {} public void restart() {} diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java index a6c1c96146..da0482e6ff 100644 --- a/router/java/src/net/i2p/router/TunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java @@ -10,6 +10,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; +import java.util.Set; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -62,6 +63,9 @@ public interface TunnelManagerFacade extends Service { /** count how many inbound tunnel requests we have received but not yet processed */ public int getInboundBuildQueueSize(); + /** @return Set of peers that should not be allowed to be in another tunnel */ + public Set<Hash> selectPeersInTooManyTunnels(); + /** * the client connected (or updated their settings), so make sure we have * the tunnels for them, and whenever necessary, ask them to authorize diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 1e0247c022..92e4545171 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -176,6 +176,7 @@ public abstract class TunnelPeerSelector { Set peers = new HashSet(1); peers.addAll(ctx.profileOrganizer().selectPeersRecentlyRejecting()); + peers.addAll(ctx.tunnelManager().selectPeersInTooManyTunnels()); // if (false && filterUnreachable(ctx, isInbound, isExploratory)) { if (filterUnreachable(ctx, isInbound, isExploratory)) { List caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java index 06a9b49991..699a8be9f1 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -28,7 +28,7 @@ public class TunnelPool { private RouterContext _context; private Log _log; private TunnelPoolSettings _settings; - private ArrayList _tunnels; + private ArrayList<TunnelInfo> _tunnels; private TunnelPeerSelector _peerSelector; private TunnelPoolManager _manager; private boolean _alive; @@ -227,7 +227,7 @@ public class TunnelPool { * * @return list of TunnelInfo objects */ - public List listTunnels() { + public List<TunnelInfo> listTunnels() { synchronized (_tunnels) { return new ArrayList(_tunnels); } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java index 58637ce63a..acbb9345db 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -6,9 +6,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.DataHelper; import net.i2p.data.Destination; @@ -506,6 +509,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { out.write("</table>\n"); out.write("Inactive participating tunnels: " + inactive + "<br />\n"); out.write("Lifetime bandwidth usage: " + DataHelper.formatSize(processed*1024) + "B<br />\n"); + renderPeers(out); } class TunnelComparator implements Comparator { @@ -579,6 +583,135 @@ public class TunnelPoolManager implements TunnelManagerFacade { DataHelper.formatSize(processedOut*1024) + "B out<br />"); } + private void renderPeers(Writer out) throws IOException { + // count up the peers in the local pools + HashCounter lc = new HashCounter(); + int tunnelCount = countTunnelsPerPeer(lc); + + // count up the peers in the participating tunnels + HashCounter pc = new HashCounter(); + int partCount = countParticipatingPerPeer(pc); + + Set<Hash> peers = new HashSet(lc.hashes()); + peers.addAll(pc.hashes()); + List<Hash> peerList = new ArrayList(peers); + Collections.sort(peerList, new HashComparator()); + + out.write("<h2><a name=\"peers\">Tunnel Counts By Peer</a>:</h2>\n"); + out.write("<table border=\"1\"><tr><td><b>Peer</b></td><td><b>Expl. + Client</b></td><td><b>% of total</b></td><td><b>Part. from + to</b></td><td><b>% of total</b></td></tr>\n"); + for (Hash h : peerList) { + out.write("<tr><td>"); + out.write(netDbLink(h)); + out.write("<td align=\"right\">" + lc.count(h)); + out.write("<td align=\"right\">"); + if (tunnelCount > 0) + out.write("" + (lc.count(h) * 100 / tunnelCount)); + else + out.write('0'); + out.write("<td align=\"right\">" + pc.count(h)); + out.write("<td align=\"right\">"); + if (partCount > 0) + out.write("" + (pc.count(h) * 100 / partCount)); + else + out.write('0'); + out.write('\n'); + } + out.write("<tr><td>Tunnels<td align=\"right\">" + tunnelCount); + out.write("<td> <td align=\"right\">" + partCount); + out.write("<td> </table>\n"); + } + + /** @return total number of non-fallback expl. + client tunnels */ + private int countTunnelsPerPeer(HashCounter lc) { + List<TunnelPool> pools = new ArrayList(); + listPools(pools); + int tunnelCount = 0; + for (TunnelPool tp : pools) { + for (TunnelInfo info : tp.listTunnels()) { + if (info.getLength() > 1) { + tunnelCount++; + for (int j = 0; j < info.getLength(); j++) { + Hash peer = info.getPeer(j); + if (!_context.routerHash().equals(peer)) + lc.increment(peer); + } + } + } + } + return tunnelCount; + } + + private static final int DEFAULT_MAX_PCT_TUNNELS = 33; + /** + * For reliability reasons, don't allow a peer in more than x% of + * client and exploratory tunnels. + * + * This also will prevent a single huge-capacity (or malicious) peer from + * taking all the tunnels in the network (although it would be nice to limit + * the % of total network tunnels to 10% or so, but that appears to be + * too low to set as a default here... much lower than 33% will push client + * tunnels out of the fast tier into high cap or beyond...) + * + * Possible improvement - restrict based on count per IP, or IP block, + * to slightly increase costs of collusion + * + * @return Set of peers that should not be allowed in another tunnel + */ + public Set<Hash> selectPeersInTooManyTunnels() { + HashCounter lc = new HashCounter(); + int tunnelCount = countTunnelsPerPeer(lc); + Set<Hash> rv = new HashSet(); + if (tunnelCount >= 4 && _context.router().getUptime() > 10*60*1000) { + int max = _context.getProperty("router.maxTunnelPercentage", DEFAULT_MAX_PCT_TUNNELS); + for (Hash h : lc.hashes()) { + if (lc.count(h) > 0 && (lc.count(h) + 1) * 100 / (tunnelCount + 1) > max) + rv.add(h); + } + } + return rv; + } + + /** @return total number of part. tunnels */ + private int countParticipatingPerPeer(HashCounter pc) { + List<HopConfig> participating = _context.tunnelDispatcher().listParticipatingTunnels(); + for (HopConfig cfg : participating) { + Hash from = cfg.getReceiveFrom(); + if (from != null) + pc.increment(from); + Hash to = cfg.getSendTo(); + if (to != null) + pc.increment(to); + } + return participating.size(); + } + + class HashComparator implements Comparator { + public int compare(Object l, Object r) { + return ((Hash)l).toBase64().compareTo(((Hash)r).toBase64()); + } + } + + private static class HashCounter { + private ConcurrentHashMap<Hash, Integer> _map; + public HashCounter() { + _map = new ConcurrentHashMap(); + } + public void increment(Hash h) { + Integer i = _map.putIfAbsent(h, Integer.valueOf(1)); + if (i != null) + _map.put(h, Integer.valueOf(i.intValue() + 1)); + } + public int count(Hash h) { + Integer i = _map.get(h); + if (i != null) + return i.intValue(); + return 0; + } + public Set<Hash> hashes() { + return _map.keySet(); + } + } + private String getCapacity(Hash peer) { RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) { -- GitLab