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>&nbsp;<td align=\"right\">" + partCount);
+        out.write("<td>&nbsp;</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