diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml index 02eee0aabc36ef863fdd4fd561ac646824761dd4..9689fda06823a17019d39cd05c3488b19a433d70 100644 --- a/apps/i2psnark/java/build.xml +++ b/apps/i2psnark/java/build.xml @@ -18,7 +18,6 @@ <!-- Depend on classes instead of jars where available --> <classpath> <pathelement location="../../../core/java/build/obj" /> - <pathelement location="../../../router/java/build/obj" /> <pathelement location="../../ministreaming/java/build/obj" /> <pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" /> <pathelement location="../../jetty/jettylib/javax.servlet.jar" /> @@ -32,7 +31,7 @@ srcdir="./src" debug="true" deprecation="on" source="1.5" target="1.5" destdir="./build/obj" - classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" /> + classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" /> </target> <target name="jar" depends="builddep, compile"> <jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class"> diff --git a/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java new file mode 100644 index 0000000000000000000000000000000000000000..eb157cb5e4426bb87f5538fbe92fee86175724da --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/BWLimits.java @@ -0,0 +1,44 @@ +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + */ +package org.klomp.snark; + +import java.util.Arrays; +import java.util.Properties; + +import net.i2p.I2PAppContext; +import net.i2p.client.I2PSessionException; +import net.i2p.client.I2PClient; +import net.i2p.client.I2PSession; +import net.i2p.client.I2PSimpleClient; + +/** + * Connect via I2CP and ask the router the bandwidth limits. + * + * The call is blocking and returns null on failure. + * Timeout is set to 5 seconds in I2PSimpleSession but it should be much faster. + * + * @author zzz + */ +class BWLimits { + + public static int[] getBWLimits(String host, int port) { + int[] rv = null; + try { + I2PClient client = new I2PSimpleClient(); + Properties opts = new Properties(); + opts.put(I2PClient.PROP_TCP_HOST, host); + opts.put(I2PClient.PROP_TCP_PORT, "" + port); + I2PSession session = client.createSession(null, opts); + session.connect(); + rv = session.bandwidthLimits(); + session.destroySession(); + } catch (I2PSessionException ise) {} + return rv; + } + + public static void main(String args[]) { + System.out.println(Arrays.toString(getBWLimits("127.0.0.1", 7654))); + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index e124955cfc3bc551207bc5291e79f2c25ecdb71e..53c42dfadf30b51dfbd50bfc66e6140480267b99 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -36,7 +36,6 @@ import java.util.Timer; import java.util.TimerTask; import net.i2p.I2PAppContext; -import net.i2p.router.client.ClientManagerFacadeImpl; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.data.Destination; import net.i2p.util.I2PThread; @@ -261,9 +260,9 @@ public class Snark public Snark(I2PAppContext ctx, Properties opts, String torrent, StorageListener slistener, boolean start, String rootDir) { this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir); - String host = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST); + String host = opts.getProperty("i2cp.hostname"); int port = 0; - String s = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT); + String s = opts.getProperty("i2cp.port"); if (s != null) { try { port = Integer.parseInt(s); diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 54367af1ac90658eceaaa1dd3a155b2bfef9234f..01a93a58c12fae63ecff4158415105eda292130f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -18,7 +18,6 @@ import java.util.TreeMap; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.DataHelper; -import net.i2p.router.RouterContext; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; @@ -147,22 +146,21 @@ public class SnarkManager implements Snark.CompleteListener { _config.setProperty(PROP_EEP_PORT, "4444"); if (!_config.containsKey(PROP_UPLOADERS_TOTAL)) _config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS); - if (!_config.containsKey(PROP_UPBW_MAX)) { - try { - if (_context instanceof RouterContext) - _config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 2)); - else - _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW); - } catch (NoClassDefFoundError ncdfe) { - _config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW); - } - } if (!_config.containsKey(PROP_DIR)) _config.setProperty(PROP_DIR, "i2psnark"); if (!_config.containsKey(PROP_AUTO_START)) _config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START); updateConfig(); } + + /** call from DirMonitor since loadConfig() is called before router I2CP is up */ + private void getBWLimit() { + if (!_config.containsKey(PROP_UPBW_MAX)) { + int[] limits = BWLimits.getBWLimits(_util.getI2CPHost(), _util.getI2CPPort()); + if (limits != null && limits[1] > 0) + _util.setMaxUpBW(limits[1]); + } + } private void updateConfig() { String i2cpHost = _config.getProperty(PROP_I2CP_HOST); @@ -619,6 +617,9 @@ public class SnarkManager implements Snark.CompleteListener { _messages.remove(0); } + // here because we need to delay until I2CP is up + // although the user will see the default until then + getBWLimit(); while (true) { File dir = getDataDir(); _log.debug("Directory Monitor loop over " + dir.getAbsolutePath()); diff --git a/core/java/src/net/i2p/client/BWLimitsMessageHandler.java b/core/java/src/net/i2p/client/BWLimitsMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..b47eaa6c8f3ceaa4359c5fb1c76ab4ff44989434 --- /dev/null +++ b/core/java/src/net/i2p/client/BWLimitsMessageHandler.java @@ -0,0 +1,25 @@ +package net.i2p.client; + +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + */ + +import net.i2p.I2PAppContext; +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.data.i2cp.BandwidthLimitsMessage; + +/** + * Handle I2CP BW replies from the router + */ +class BWLimitsMessageHandler extends HandlerImpl { + public BWLimitsMessageHandler(I2PAppContext ctx) { + super(ctx, BandwidthLimitsMessage.MESSAGE_TYPE); + } + + public void handleMessage(I2CPMessage message, I2PSessionImpl session) { + _log.debug("Handle message " + message); + BandwidthLimitsMessage msg = (BandwidthLimitsMessage) message; + ((I2PSimpleSession)session).bwReceived(msg.getLimits()); + } +} diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java index 1776af5c0f91200ccfcfbcbb20ec0101fb8f821e..1998dad55a737a016d558e456ae2f632eff13e6c 100644 --- a/core/java/src/net/i2p/client/I2PSession.java +++ b/core/java/src/net/i2p/client/I2PSession.java @@ -143,6 +143,11 @@ public interface I2PSession { */ public Destination lookupDest(Hash h) throws I2PSessionException; + /** + * Get the current bandwidth limits + */ + public int[] bandwidthLimits() throws I2PSessionException; + /** See I2PSessionMuxedImpl for details */ public void addSessionListener(I2PSessionListener lsnr, int proto, int port); /** See I2PSessionMuxedImpl for details */ diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 0e13f2c56308c82a587081bec7cc0b7e23e178b7..5b7603fdd605be34d10c3f3628fb1270175f6774 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -656,6 +656,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa return null; } + public int[] bandwidthLimits() throws I2PSessionException { + return null; + } + protected void updateActivity() { _lastActivity = _context.clock().now(); if (_isReduced) { diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index fcfafe7672885b09bc532febc0618506c4aa4bfd..b417bd7f7abcf9b19af31ab16b2abab70ac75296 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -17,25 +17,32 @@ import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.DestReplyMessage; +import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.util.I2PThread; import net.i2p.util.Log; /** - * Create a new session for doing naming queries only. Do not create a Destination. + * Create a new session for doing naming and bandwidth queries only. Do not create a Destination. * Don't create a producer. Do not send/receive messages to other Destinations. * Cannot handle multiple simultaneous queries atm. * Could be expanded to ask the router other things. + * + * @author zzz */ class I2PSimpleSession extends I2PSessionImpl2 { private boolean _destReceived; private Object _destReceivedLock; private Destination _destination; + private boolean _bwReceived; + private Object _bwReceivedLock; + private int[] _bwLimits; /** - * Create a new session for doing naming queries only. Do not create a destination. + * Create a new session for doing naming and bandwidth queries only. Do not create a destination. * * @throws I2PSessionException if there is a problem */ @@ -94,6 +101,14 @@ class I2PSimpleSession extends I2PSessionImpl2 { } } + void bwReceived(int[] i) { + _bwReceived = true; + _bwLimits = i; + synchronized (_bwReceivedLock) { + _bwReceivedLock.notifyAll(); + } + } + public Destination lookupDest(Hash h) throws I2PSessionException { if (_closed) return null; @@ -110,14 +125,31 @@ class I2PSimpleSession extends I2PSessionImpl2 { return _destination; } + public int[] bandwidthLimits() throws I2PSessionException { + if (_closed) + return null; + _bwReceivedLock = new Object(); + sendMessage(new GetBandwidthLimitsMessage()); + for (int i = 0; i < 5 && !_bwReceived; i++) { + try { + synchronized (_bwReceivedLock) { + _bwReceivedLock.wait(1000); + } + } catch (InterruptedException ie) {} + } + _bwReceived = false; + return _bwLimits; + } + /** * Only map message handlers that we will use */ class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap { public SimpleMessageHandlerMap(I2PAppContext context) { - int highest = DestReplyMessage.MESSAGE_TYPE; + int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE); _handlers = new I2CPMessageHandler[highest+1]; _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); + _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); } } } diff --git a/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java b/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..2f8d567869d70abf06bbb9690967d78e33c9affc --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/BandwidthLimitsMessage.java @@ -0,0 +1,101 @@ +package net.i2p.data.i2cp; + +/* + * public domain + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; + +/** + * Tell the other side the limits + * + * @author zzz + */ +public class BandwidthLimitsMessage extends I2CPMessageImpl { + private final static Log _log = new Log(BandwidthLimitsMessage.class); + public final static int MESSAGE_TYPE = 23; + private static final int LIMITS = 16; + private int[] data; + + public BandwidthLimitsMessage() { + super(); + data = new int[LIMITS]; + } + + /** + * Let's define it this way. + * Leave some extra. This is only local and rarely sent so we don't care about waste. + * + * 0) Client inbound limit (KBps) + * 1) Client outbound limit (KBps) + * 2) Router inbound limit (KBps) + * 3) Router inbound burst limit (KBps) + * 4) Router outbound limit (KBps) + * 5) Router outbound burst limit (KBps) + * 6) Router burst time (seconds) + * 7-15) undefined + */ + public BandwidthLimitsMessage(int in, int out) { + this(); + data[0] = in; + data[1] = out; + } + + public int[] getLimits() { + return data; + } + + @Override + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + for (int i = 0; i < LIMITS; i++) { + data[i] = (int) DataHelper.readLong(in, 4); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Unable to load the message data", dfe); + } + } + + @Override + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(64); + try { + for (int i = 0; i < LIMITS; i++) { + DataHelper.writeLong(os, 4, data[i]); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error writing out the message data", dfe); + } + return os.toByteArray(); + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public boolean equals(Object object) { + if ((object != null) && (object instanceof BandwidthLimitsMessage)) { + BandwidthLimitsMessage msg = (BandwidthLimitsMessage) object; + return DataHelper.eq(data, msg.getLimits()); + } + return false; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[BandwidthLimitsMessage"); + buf.append("\n\tIn: ").append(data[0]); + buf.append("\n\tOut: ").append(data[1]); + buf.append("]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java b/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..a47f883d78a200a776e0d313d05d5fd4bd178c99 --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/GetBandwidthLimitsMessage.java @@ -0,0 +1,56 @@ +package net.i2p.data.i2cp; + +/* + * public domain + * + */ + +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.util.Log; + +/** + * Request the router tells us the current bw limits + * + * @author zzz + */ +public class GetBandwidthLimitsMessage extends I2CPMessageImpl { + private final static Log _log = new Log(GetBandwidthLimitsMessage.class); + public final static int MESSAGE_TYPE = 8; + + public GetBandwidthLimitsMessage() { + super(); + } + + @Override + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + // noop + } + + @Override + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + byte rv[] = new byte[0]; + return rv; + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public boolean equals(Object object) { + if ((object != null) && (object instanceof GetBandwidthLimitsMessage)) { + return true; + } + + return false; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[GetBandwidthLimitsMessage]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java index 61d86505344d1c201d24597edb35e98d09c07c8c..04da546f782f2bece1287062f972b8930f27598f 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -94,6 +94,10 @@ public class I2CPMessageHandler { return new DestLookupMessage(); case DestReplyMessage.MESSAGE_TYPE: return new DestReplyMessage(); + case GetBandwidthLimitsMessage.MESSAGE_TYPE: + return new GetBandwidthLimitsMessage(); + case BandwidthLimitsMessage.MESSAGE_TYPE: + return new BandwidthLimitsMessage(); default: throw new I2CPMessageException("The type " + type + " is an unknown I2CP message"); } diff --git a/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java b/router/java/src/net/i2p/router/DummyTunnelManagerFacade.java index f7f204857a1765913ff87dae454d857910f09664..aec0390fd3d78b2401136aaa536c728a0c67fe84 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 a6c1c961466ff8953a1202cb9a8038609927329c..da0482e6ffd4072eac1416e95e3397cf0222fd20 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/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 0dcc8187091d85010525d1070547637285cd9dbd..3b4b1a6bed38fa27ce4434f5691256e96f902ce5 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -11,10 +11,12 @@ package net.i2p.router.client; import java.util.Properties; import net.i2p.data.Payload; +import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.CreateLeaseSetMessage; import net.i2p.data.i2cp.CreateSessionMessage; import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.DestroySessionMessage; +import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetDateMessage; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; @@ -93,6 +95,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi case ReconfigureSessionMessage.MESSAGE_TYPE: handleReconfigureSession(reader, (ReconfigureSessionMessage)message); break; + case GetBandwidthLimitsMessage.MESSAGE_TYPE: + handleGetBWLimits(reader, (GetBandwidthLimitsMessage)message); + break; default: if (_log.shouldLog(Log.ERROR)) _log.error("Unhandled I2CP type received: " + message.getType()); @@ -274,6 +279,24 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } } + /** + * Divide router limit by 1.75 for overhead. + * This could someday give a different answer to each client. + * But it's not enforced anywhere. + */ + private void handleGetBWLimits(I2CPMessageReader reader, GetBandwidthLimitsMessage message) { + if (_log.shouldLog(Log.INFO)) + _log.info("Got BW Limits request"); + int in = _context.bandwidthLimiter().getInboundKBytesPerSecond() * 4 / 7; + int out = _context.bandwidthLimiter().getOutboundKBytesPerSecond() * 4 / 7; + BandwidthLimitsMessage msg = new BandwidthLimitsMessage(in, out); + try { + _runner.doSend(msg); + } catch (I2CPMessageException ime) { + _log.error("Error writing out the session status message", ime); + } + } + // this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME private final static int MAX_SESSION_ID = 32767; 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 1e0247c02227d61269e076ed50f4665980bd530c..92e4545171cac325a29e91147e752ceffed3e72a 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 06a9b499913f2890f90f10dec628e047417d8f7d..699a8be9f1468e57b09e33c235d589ff35247333 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 58637ce63a1c50d981f1b9862548e1572cf47c01..acbb9345db9795ff85d8751f30e7ef42eed18b70 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) {