diff --git a/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java b/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java index 3b8b09db6c84919a4c6b269e09c765c5f4963be5..72ca6e574c4bafbbd9878fa4588fda1a0ae88cc5 100644 --- a/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java +++ b/apps/i2psnark/java/src/net/i2p/kademlia/KBucketSet.java @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,6 +23,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.LHMCache; import net.i2p.util.Log; /** @@ -658,7 +658,7 @@ public class KBucketSet<T extends SimpleDataStructure> { public Range(T us, int bValue) { _bValue = bValue; _bigUs = new BigInteger(1, us.getData()); - _distanceCache = new LHM(256); + _distanceCache = new LHMCache(256); } /** @return 0 to max-1 or -1 for us */ @@ -697,20 +697,6 @@ public class KBucketSet<T extends SimpleDataStructure> { } } - private static class LHM<K, V> extends LinkedHashMap<K, V> { - private final int _max; - - public LHM(int max) { - super(max, 0.75f, true); - _max = max; - } - - @Override - protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { - return size() > _max; - } - } - /** * For Collections.binarySearch. * getRangeBegin == getRangeEnd. 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 bdf0891fc3ffea628aa1a51a6924de05661032dd..6dd9572d63354b6dd7fb4283316e41d7c29e537c 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java @@ -38,6 +38,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.ConcurrentHashSet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; @@ -97,6 +98,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT { private final ConcurrentHashMap<Token, NodeInfo> _outgoingTokens; /** index to incoming opaque tokens, received in a peers or nodes reply */ private final ConcurrentHashMap<NID, Token> _incomingTokens; + /** recently unreachable, with lastSeen() as the added-to-blacklist time */ + private final Set<NID> _blacklist; /** hook to inject and receive datagrams */ private final I2PSession _session; @@ -147,6 +150,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { /** stagger with other cleaners */ private static final long CLEAN_TIME = 63*1000; private static final long EXPLORE_TIME = 877*1000; + private static final long BLACKLIST_CLEAN_TIME = 17*60*1000; private static final String DHT_FILE = "i2psnark.dht.dat"; private static final int SEND_CRYPTO_TAGS = 8; @@ -161,6 +165,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { _sentQueries = new ConcurrentHashMap(); _outgoingTokens = new ConcurrentHashMap(); _incomingTokens = new ConcurrentHashMap(); + _blacklist = new ConcurrentHashSet(); // Construct my NodeInfo // Pick ports over a big range to marginally increase security @@ -262,13 +267,13 @@ public class KRPC implements I2PSessionMuxedListener, DHT { int replyType = waiter.getReplyCode(); if (replyType == REPLY_NONE) { - if (_log.shouldLog(Log.INFO)) - _log.info("Got no reply"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got no reply"); } else if (replyType == REPLY_NODES) { List<NodeInfo> reply = (List<NodeInfo>) waiter.getReplyObject(); // It seems like we are just going to get back ourselves all the time - if (_log.shouldLog(Log.INFO)) - _log.info("Got " + reply.size() + " nodes"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got " + reply.size() + " nodes"); for (NodeInfo ni : reply) { if (! (ni.equals(_myNodeInfo) || (toTry.contains(ni) && tried.contains(ni)))) toTry.add(ni); @@ -348,14 +353,14 @@ public class KRPC implements I2PSessionMuxedListener, DHT { int replyType = waiter.getReplyCode(); if (replyType == REPLY_NONE) { - if (_log.shouldLog(Log.INFO)) - _log.info("Got no reply"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got no reply"); } else if (replyType == REPLY_PONG) { - if (_log.shouldLog(Log.INFO)) - _log.info("Got pong"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got pong"); } else if (replyType == REPLY_PEERS) { - if (_log.shouldLog(Log.INFO)) - _log.info("Got peers"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got peers"); List<Hash> reply = (List<Hash>) waiter.getReplyObject(); if (!reply.isEmpty()) { for (int j = 0; j < reply.size() && rv.size() < max; j++) { @@ -367,8 +372,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT { } } else if (replyType == REPLY_NODES) { List<NodeInfo> reply = (List<NodeInfo>) waiter.getReplyObject(); - if (_log.shouldLog(Log.INFO)) - _log.info("Got " + reply.size() + " nodes"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got " + reply.size() + " nodes"); for (NodeInfo ni : reply) { if (! (ni.equals(_myNodeInfo) || tried.contains(ni) || toTry.contains(ni))) toTry.add(ni); @@ -576,6 +581,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { } _outgoingTokens.clear(); _incomingTokens.clear(); + _blacklist.clear(); } /** @@ -592,7 +598,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { */ public String renderStatusHTML() { long uptime = Math.max(1000, _context.clock().now() - _started); - StringBuilder buf = new StringBuilder(); + StringBuilder buf = new StringBuilder(256); buf.append("<br><b>DHT DEBUG</b><br>TX: ").append(_txPkts.get()).append(" pkts / ") .append(DataHelper.formatSize2(_txBytes.get())).append("B / ") .append(DataHelper.formatSize2(_txBytes.get() * 1000 / uptime)).append("Bps<br>" + @@ -600,6 +606,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { .append(DataHelper.formatSize2(_rxBytes.get())).append("B / ") .append(DataHelper.formatSize2(_rxBytes.get() * 1000 / uptime)).append("Bps<br>" + "DHT Peers: ").append( _knownNodes.size()).append("<br>" + + "Blacklisted: ").append(_blacklist.size()).append("<br>" + "Sent tokens: ").append(_outgoingTokens.size()).append("<br>" + "Rcvd tokens: ").append(_incomingTokens.size()).append("<br>" + "Pending queries: ").append(_sentQueries.size()).append("<br>"); @@ -1079,7 +1086,12 @@ public class KRPC implements I2PSessionMuxedListener, DHT { if (oldInfo.getDestination() == null && nInfo.getDestination() != null) oldInfo.setDestination(nInfo.getDestination()); } - oldInfo.getNID().setLastSeen(); + nID = oldInfo.getNID(); + nID.setLastSeen(); + if (_blacklist.remove(nID)) { + if (_log.shouldLog(Log.INFO)) + _log.info("UN-blacklisted: " + nID); + } return oldInfo; } @@ -1109,6 +1121,13 @@ public class KRPC implements I2PSessionMuxedListener, DHT { if (_log.shouldLog(Log.INFO)) _log.info("Removed after consecutive timeouts: " + nInfo); } + if (!_blacklist.contains(nid)) { + // used as when-added time + nid.setLastSeen(); + _blacklist.add(nid); + if (_log.shouldLog(Log.INFO)) + _log.info("Blacklisted: " + nid); + } } } @@ -1223,11 +1242,11 @@ public class KRPC implements I2PSessionMuxedListener, DHT { byte[] tok = btok.getBytes(); Token token = new Token(_context, tok); _incomingTokens.put(nInfo.getNID(), token); - if (_log.shouldLog(Log.INFO)) - _log.info("Got token: " + token + ", must be a response to get_peers"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got token: " + token + ", must be a response to get_peers"); } else { - if (_log.shouldLog(Log.INFO)) - _log.info("No token and saved infohash, must be a response to find_node"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("No token and saved infohash, must be a response to find_node"); } } @@ -1259,6 +1278,11 @@ public class KRPC implements I2PSessionMuxedListener, DHT { List<NodeInfo> rv = new ArrayList(ids.length / NodeInfo.LENGTH); for (int off = 0; off < ids.length; off += NodeInfo.LENGTH) { NodeInfo nInf = new NodeInfo(ids, off); + if (_blacklist.contains(nInf.getNID())) { + if (_log.shouldLog(Log.INFO)) + _log.info("Ignoring blacklisted " + nInf.getNID() + " from: " + nInfo); + continue; + } nInf = heardAbout(nInf); rv.add(nInf); } @@ -1501,6 +1525,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { long now = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) _log.debug("KRPC cleaner starting with " + + _blacklist.size() + " in blacklist, " + _outgoingTokens.size() + " sent Tokens, " + _incomingTokens.size() + " rcvd Tokens"); for (Iterator<Token> iter = _outgoingTokens.keySet().iterator(); iter.hasNext(); ) { @@ -1513,9 +1538,16 @@ public class KRPC implements I2PSessionMuxedListener, DHT { if (tok.lastSeen() < now - MAX_INBOUND_TOKEN_AGE) iter.remove(); } + for (Iterator<NID> iter = _blacklist.iterator(); iter.hasNext(); ) { + NID nid = iter.next(); + // lastSeen() is actually when-added + if (now > nid.lastSeen() + BLACKLIST_CLEAN_TIME) + iter.remove(); + } // TODO sent queries? if (_log.shouldLog(Log.DEBUG)) _log.debug("KRPC cleaner done, now with " + + _blacklist.size() + " in blacklist, " + _outgoingTokens.size() + " sent Tokens, " + _incomingTokens.size() + " rcvd Tokens, " + _knownNodes.size() + " known peers, " + diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/NID.java b/apps/i2psnark/java/src/org/klomp/snark/dht/NID.java index 44aa29c8560a31a32f352dcd34feaef803a06c82..f1b7b221255eb1ec37af1031089eb367b47fb594 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/NID.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/NID.java @@ -18,7 +18,7 @@ public class NID extends SHA1Hash { private long lastSeen; private int fails; - private static final int MAX_FAILS = 3; + private static final int MAX_FAILS = 2; public NID() { super(null); @@ -41,6 +41,6 @@ public class NID extends SHA1Hash { * @return if more than max timeouts */ public boolean timeout() { - return fails++ > MAX_FAILS; + return ++fails > MAX_FAILS; } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java index 3a1676ea236ae647dccccaf54190b39cedebdcc7..494e93de8355b550048ddde138b88eec877b2214 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Map; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; @@ -15,6 +16,7 @@ import javax.imageio.stream.MemoryCacheImageOutputStream; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; +import net.i2p.router.util.EventLog; import net.i2p.util.Log; import org.jrobin.core.RrdException; @@ -137,9 +139,11 @@ class SummaryRenderer { // Strings.java descr = _(_listener.getRate().getRateStat().getDescription()); } - long started = ((RouterContext)_context).router().getWhenStarted(); - if (started > start && started < end) - def.vrule(started / 1000, RESTART_BAR_COLOR, _("Restart"), 4.0f); + + //long started = ((RouterContext)_context).router().getWhenStarted(); + //if (started > start && started < end) + // def.vrule(started / 1000, RESTART_BAR_COLOR, _("Restart"), 4.0f); + def.datasource(plotName, path, plotName, SummaryListener.CF, _listener.getBackendName()); if (descr.length() > 0) def.area(plotName, Color.BLUE, descr + "\\r"); @@ -151,6 +155,14 @@ class SummaryRenderer { def.gprint(plotName, "LAST", ' ' + _("now") + ": %.2f %S\\r"); // '07-Jul 21:09 UTC' with month name in the system locale SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM HH:mm"); + Map<Long, String> events = ((RouterContext)_context).router().eventLog().getEvents(EventLog.STARTED, start); + for (Map.Entry<Long, String> event : events.entrySet()) { + long started = event.getKey().longValue(); + if (started > start && started < end) { + String legend = _("Restart") + ' ' + sdf.format(new Date(started)) + " UTC " + event.getValue() + "\\r"; + def.vrule(started / 1000, RESTART_BAR_COLOR, legend, 4.0f); + } + } def.comment(sdf.format(new Date(start)) + " -- " + sdf.format(new Date(end)) + " UTC\\r"); } if (!showCredit) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index 739a4201776ece8b35490164fcf85a9ea46a9eaa..885b72cdc9e52f29ceff39693a3c903614a6feca 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -13,7 +13,6 @@ import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.SessionKey; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; /** @@ -494,7 +493,8 @@ class ConnectionManager { } _pendingPings.remove(id); } else { - SimpleTimer.getInstance().addEvent(new PingFailed(id, notifier), timeoutMs); + PingFailed pf = new PingFailed(id, notifier); + pf.schedule(timeoutMs); } boolean ok = req.pongReceived(); @@ -505,11 +505,12 @@ class ConnectionManager { public void pingComplete(boolean ok); } - private class PingFailed implements SimpleTimer.TimedEvent { + private class PingFailed extends SimpleTimer2.TimedEvent { private final Long _id; private final PingNotifier _notifier; public PingFailed(Long id, PingNotifier notifier) { + super(_context.simpleTimer2()); _id = id; _notifier = notifier; } diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 15189f876ec6a830609f5f07063428d0e5949f09..be42467f459f7ea4e019ef575beda34f2e252cb1 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -487,7 +487,7 @@ public class I2PAppContext { String val = getProperty(propName); if (val == null) return defaultVal; - return Boolean.valueOf(val).booleanValue(); + return Boolean.parseBoolean(val); } /** @@ -495,7 +495,7 @@ public class I2PAppContext { * @since 0.7.12 */ public boolean getBooleanProperty(String propName) { - return Boolean.valueOf(getProperty(propName)).booleanValue(); + return Boolean.parseBoolean(getProperty(propName)); } /** @@ -953,6 +953,7 @@ public class I2PAppContext { /** * Use instead of SimpleTimer.getInstance() * @since 0.9 to replace static instance in the class + * @deprecated use SimpleTimer2 */ public SimpleTimer simpleTimer() { if (!_simpleTimerInitialized) @@ -960,6 +961,9 @@ public class I2PAppContext { return _simpleTimer; } + /** + * @deprecated use SimpleTimer2 + */ private void initializeSimpleTimer() { synchronized (_lock19) { if (_simpleTimer == null) diff --git a/core/java/src/net/i2p/client/ClientWriterRunner.java b/core/java/src/net/i2p/client/ClientWriterRunner.java index 931af519cc8a540d1717ebfb81f51ffd44ca4287..cae086710a0035ab6d5e2c8fcc9b20cd6519ebca 100644 --- a/core/java/src/net/i2p/client/ClientWriterRunner.java +++ b/core/java/src/net/i2p/client/ClientWriterRunner.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; @@ -22,24 +23,31 @@ class ClientWriterRunner implements Runnable { private I2PSessionImpl _session; private BlockingQueue<I2CPMessage> _messagesToWrite; private static volatile long __Id = 0; + + private static final int MAX_QUEUE_SIZE = 32; + private static final long MAX_SEND_WAIT = 10*1000; /** starts the thread too */ public ClientWriterRunner(OutputStream out, I2PSessionImpl session) { _out = out; _session = session; - _messagesToWrite = new LinkedBlockingQueue(); + _messagesToWrite = new LinkedBlockingQueue(MAX_QUEUE_SIZE); Thread t = new I2PAppThread(this, "I2CP Client Writer " + (++__Id), true); t.start(); } /** - * Add this message to the writer's queue - * + * Add this message to the writer's queue. + * Blocking if queue is full. + * @throws I2PSessionException if we wait too long or are interrupted */ - public void addMessage(I2CPMessage msg) { + public void addMessage(I2CPMessage msg) throws I2PSessionException { try { - _messagesToWrite.put(msg); - } catch (InterruptedException ie) {} + if (!_messagesToWrite.offer(msg, MAX_SEND_WAIT, TimeUnit.MILLISECONDS)) + throw new I2PSessionException("Timed out waiting while write queue was full"); + } catch (InterruptedException ie) { + throw new I2PSessionException("Interrupted while write queue was full", ie); + } } /** diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 349f3d678289cb3f130644425d8419117b2e4e50..c3b993e4af08cf94e65414c1224043d46aee22aa 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -18,7 +18,6 @@ import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -44,6 +43,7 @@ import net.i2p.internal.I2CPMessageQueue; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PAppThread; +import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -140,13 +140,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** * @since 0.8.9 */ - private static final LookupCache _lookupCache = new LookupCache(16); + private static final Map<Hash, Destination> _lookupCache = new LHMCache(16); /** SSL interface (only) @since 0.8.3 */ protected static final String PROP_ENABLE_SSL = "i2cp.SSL"; private static final long VERIFY_USAGE_TIME = 60*1000; + private static final long MAX_SEND_WAIT = 10*1000; + void dateUpdated() { _dateReceived = true; synchronized (_dateReceivedLock) { @@ -643,18 +645,26 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** * Deliver an I2CP message to the router + * As of 0.9.3, may block for several seconds if the write queue to the router is full * * @throws I2PSessionException if the message is malformed or there is an error writing it out */ void sendMessage(I2CPMessage message) throws I2PSessionException { - if (isClosed()) + if (isClosed()) { throw new I2PSessionException("Already closed"); - else if (_queue != null) - _queue.offer(message); // internal - else if (_writer == null) + } else if (_queue != null) { + // internal + try { + if (!_queue.offer(message, MAX_SEND_WAIT)) + throw new I2PSessionException("Timed out waiting while write queue was full"); + } catch (InterruptedException ie) { + throw new I2PSessionException("Interrupted while write queue was full", ie); + } + } else if (_writer == null) { throw new I2PSessionException("Already closed"); - else + } else { _writer.addMessage(message); + } } /** @@ -985,21 +995,4 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa buf.append(getPrefix()); return buf.toString(); } - - /** - * @since 0.8.9 - */ - private static class LookupCache extends LinkedHashMap<Hash, Destination> { - private final int _max; - - public LookupCache(int max) { - super(max, 0.75f, true); - _max = max; - } - - @Override - protected boolean removeEldestEntry(Map.Entry<Hash, Destination> eldest) { - return size() > _max; - } - } } diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index 04af6d6460a9706b50b6db25086f783d5687dae6..db7f9eebc01c57ae89538240f7aae182f73ae6da 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -29,6 +29,7 @@ import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.SecureFileOutputStream; @@ -134,7 +135,7 @@ public class BlockfileNamingService extends DummyNamingService { super(context); _lists = new ArrayList(); _invalid = new ArrayList(); - _negativeCache = new LHM(NEGATIVE_CACHE_SIZE); + _negativeCache = new LHMCache(NEGATIVE_CACHE_SIZE); BlockFile bf = null; RAIFile raf = null; boolean readOnly = false; diff --git a/core/java/src/net/i2p/client/naming/DummyNamingService.java b/core/java/src/net/i2p/client/naming/DummyNamingService.java index 63c6cf26484d67f9b94d1d5d1ae06da6a9651428..3ce7a83984d279c165afb248d85036b90c440b52 100644 --- a/core/java/src/net/i2p/client/naming/DummyNamingService.java +++ b/core/java/src/net/i2p/client/naming/DummyNamingService.java @@ -7,13 +7,13 @@ */ package net.i2p.client.naming; -import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.Destination; +import net.i2p.util.LHMCache; /** * A Dummy naming service that can only handle base64 and b32 destinations. @@ -30,7 +30,7 @@ class DummyNamingService extends NamingService { * Classes should take care to call removeCache() for any entries that * are invalidated. */ - private static final Map<String, Destination> _cache = new LHM(CACHE_MAX_SIZE); + private static final Map<String, Destination> _cache = new LHMCache(CACHE_MAX_SIZE); /** * The naming service should only be constructed and accessed through the @@ -115,18 +115,4 @@ class DummyNamingService extends NamingService { _cache.clear(); } } - - protected static class LHM<K, V> extends LinkedHashMap<K, V> { - private final int _max; - - public LHM(int max) { - super(max, 0.75f, true); - _max = max; - } - - @Override - protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { - return size() > _max; - } - } } diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java index 4febf45e47a6a320e0b7fc5b58ef248cf5ef00e3..6d8e0da07579f0c4012feece0173d3dfa173766f 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java +++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java @@ -21,8 +21,8 @@ import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; -import net.i2p.util.ByteCache; import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; /** * Wrapper for AES cypher operation using Cryptix's Rijndael implementation. Implements @@ -38,8 +38,6 @@ public class CryptixAESEngine extends AESEngine { // keys are now cached in the SessionKey objects //private CryptixAESKeyCache _cache; - private static final ByteCache _prevCache = ByteCache.getInstance(16, 16); - /**** see comments for main() below private static final boolean USE_SYSTEM_AES; static { @@ -166,10 +164,8 @@ public class CryptixAESEngine extends AESEngine { int numblock = length / 16; if (length % 16 != 0) numblock++; - ByteArray prevA = _prevCache.acquire(); - byte prev[] = prevA.getData(); - ByteArray curA = _prevCache.acquire(); - byte cur[] = curA.getData(); + byte prev[] = SimpleByteCache.acquire(16); + byte cur[] = SimpleByteCache.acquire(16); System.arraycopy(iv, ivOffset, prev, 0, 16); for (int x = 0; x < numblock; x++) { @@ -190,8 +186,8 @@ public class CryptixAESEngine extends AESEngine { } */ - _prevCache.release(prevA); - _prevCache.release(curA); + SimpleByteCache.release(prev); + SimpleByteCache.release(cur); } /** encrypt exactly 16 bytes using the session key diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index 933231dc918085706bbce93a243d5530c7b93839..016332b0b78512f33056f13b4ca611261a28f9e3 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.Map; import java.util.Properties; +import net.i2p.util.Addresses; import net.i2p.util.OrderedProperties; /** @@ -33,9 +34,15 @@ import net.i2p.util.OrderedProperties; */ public class RouterAddress extends DataStructureImpl { private int _cost; - private Date _expiration; + //private Date _expiration; private String _transportStyle; private final Properties _options; + // cached values + private byte[] _ip; + private int _port; + + public static final String PROP_HOST = "host"; + public static final String PROP_PORT = "port"; public RouterAddress() { _cost = -1; @@ -68,18 +75,21 @@ public class RouterAddress extends DataStructureImpl { * is null, then the address never expires. * * @deprecated unused for now + * @return null always */ public Date getExpiration() { - return _expiration; + //return _expiration; + return null; } /** * Configure the expiration date of the address (null for no expiration) * * Unused for now, always null + * @deprecated unused for now */ public void setExpiration(Date expiration) { - _expiration = expiration; + //_expiration = expiration; } /** @@ -140,6 +150,51 @@ public class RouterAddress extends DataStructureImpl { _options.putAll(options); } + /** + * Caching version of InetAddress.getByName(getOption("host")).getAddress(), which is slow. + * Caches numeric host names only. + * Will resolve but not cache resolution of DNS host names. + * + * @return IP or null + * @since 0.9.3 + */ + public byte[] getIP() { + if (_ip != null) + return _ip; + byte[] rv = null; + String host = _options.getProperty(PROP_HOST); + if (host != null) { + rv = Addresses.getIP(host); + if (rv != null && + (host.replaceAll("[0-9\\.]", "").length() == 0 || + host.replaceAll("[0-9a-fA-F:]", "").length() == 0)) { + _ip = rv; + } + } + return rv; + } + + /** + * Caching version of Integer.parseInt(getOption("port")) + * Caches valid ports 1-65535 only. + * + * @return 1-65535 or 0 if invalid + * @since 0.9.3 + */ + public int getPort() { + if (_port != 0) + return _port; + String port = _options.getProperty(PROP_PORT); + if (port != null) { + try { + int rv = Integer.parseInt(port); + if (rv > 0 && rv <= 65535) + _port = rv; + } catch (NumberFormatException nfe) {} + } + return _port; + } + /** * @throws IllegalStateException if was already read in */ @@ -147,7 +202,8 @@ public class RouterAddress extends DataStructureImpl { if (_transportStyle != null) throw new IllegalStateException(); _cost = (int) DataHelper.readLong(in, 1); - _expiration = DataHelper.readDate(in); + //_expiration = DataHelper.readDate(in); + DataHelper.readDate(in); _transportStyle = DataHelper.readString(in); // reduce Object proliferation if (_transportStyle.equals("SSU")) @@ -161,7 +217,8 @@ public class RouterAddress extends DataStructureImpl { if ((_cost < 0) || (_transportStyle == null)) throw new DataFormatException("Not enough data to write a router address"); DataHelper.writeLong(out, 1, _cost); - DataHelper.writeDate(out, _expiration); + //DataHelper.writeDate(out, _expiration); + DataHelper.writeDate(out, null); DataHelper.writeString(out, _transportStyle); DataHelper.writeProperties(out, _options); } @@ -198,15 +255,13 @@ public class RouterAddress extends DataStructureImpl { buf.append("[RouterAddress: "); buf.append("\n\tTransportStyle: ").append(_transportStyle); buf.append("\n\tCost: ").append(_cost); - buf.append("\n\tExpiration: ").append(_expiration); - if (_options != null) { + //buf.append("\n\tExpiration: ").append(_expiration); buf.append("\n\tOptions: #: ").append(_options.size()); for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); } - } buf.append("]"); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/SDSCache.java b/core/java/src/net/i2p/data/SDSCache.java index 474e96479142c57289e6863fe3acc22634c6d5d6..0c635084dbf58ae40401279952bf213040558958 100644 --- a/core/java/src/net/i2p/data/SDSCache.java +++ b/core/java/src/net/i2p/data/SDSCache.java @@ -6,10 +6,10 @@ import java.io.InputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.LinkedHashMap; import java.util.Map; import net.i2p.I2PAppContext; +import net.i2p.util.LHMCache; import net.i2p.util.SimpleByteCache; /** @@ -71,7 +71,7 @@ public class SDSCache<V extends SimpleDataStructure> { */ public SDSCache(Class<V> rvClass, int len, int max) { int size = (int) (max * FACTOR); - _cache = new LHM(size); + _cache = new LHMCache(size); _datalen = len; try { _rvCon = rvClass.getConstructor(conArg); @@ -98,6 +98,11 @@ public class SDSCache<V extends SimpleDataStructure> { } /** + * WARNING - If the SDS is found in the cache, the passed-in + * byte array will be returned to the SimpleByteCache for reuse. + * Do NOT save a reference to the passed-in data, or use or modify it, + * after this call. + * * @param data non-null, the byte array for the SimpleDataStructure * @return the cached value if available, otherwise * makes a new object and returns it @@ -176,18 +181,4 @@ public class SDSCache<V extends SimpleDataStructure> { rv ^= (data[i] << (i*8)); return Integer.valueOf(rv); } - - private static class LHM<K, V> extends LinkedHashMap<K, V> { - private final int _max; - - public LHM(int max) { - super(max, 0.75f, true); - _max = max; - } - - @Override - protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { - return size() > _max; - } - } } diff --git a/core/java/src/net/i2p/internal/I2CPMessageQueue.java b/core/java/src/net/i2p/internal/I2CPMessageQueue.java index 93bea3a3f29b55ece440d048f2fdd31e21975de8..eda2d8f9572975f13e702199597478707212f636 100644 --- a/core/java/src/net/i2p/internal/I2CPMessageQueue.java +++ b/core/java/src/net/i2p/internal/I2CPMessageQueue.java @@ -23,6 +23,14 @@ public abstract class I2CPMessageQueue { */ public abstract boolean offer(I2CPMessage msg); + /** + * Send a message, blocking. + * @param timeout how long to wait for space (ms) + * @return success (false if no space available or if timed out) + * @since 0.9.3 + */ + public abstract boolean offer(I2CPMessage msg, long timeout) throws InterruptedException; + /** * Receive a message, nonblocking. * Unused for now. diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index 0bf7df7056c11089111de4b4f79f9ef60c2ad4fd..c9c5a5e87cba4b516a236371c529694bce661a59 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -10,10 +10,13 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import net.i2p.I2PAppContext; /** * Methods to get the local addresses, and other IP utilities @@ -152,6 +155,69 @@ public abstract class Addresses { } } + /** + * Textual IP to bytes, because InetAddress.getByName() is slow. + * + * @since 0.9.3 + */ + private static final Map<String, byte[]> _IPAddress; + + static { + int size; + I2PAppContext ctx = I2PAppContext.getCurrentContext(); + if (ctx != null && ctx.isRouterContext()) { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory == Long.MAX_VALUE) + maxMemory = 96*1024*1024l; + long min = 128; + long max = 4096; + // 512 nominal for 128 MB + size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (256*1024)))); + } else { + size = 32; + } + _IPAddress = new LHMCache(size); + } + + /** + * Caching version of InetAddress.getByName(host).getAddress(), which is slow. + * Caches numeric host names only. + * Will resolve but not cache DNS host names. + * + * @param host DNS or IPv4 or IPv6 host name; if null returns null + * @return IP or null + * @since 0.9.3 + */ + public static byte[] getIP(String host) { + if (host == null) + return null; + byte[] rv; + synchronized (_IPAddress) { + rv = _IPAddress.get(host); + } + if (rv == null) { + try { + rv = InetAddress.getByName(host).getAddress(); + if (host.replaceAll("[0-9\\.]", "").length() == 0 || + host.replaceAll("[0-9a-fA-F:]", "").length() == 0) { + synchronized (_IPAddress) { + _IPAddress.put(host, rv); + } + } + } catch (UnknownHostException uhe) {} + } + return rv; + } + + /** + * @since 0.9.3 + */ + public static void clearCaches() { + synchronized(_IPAddress) { + _IPAddress.clear(); + } + } + /** * Print out the local addresses */ diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java index d4ea7132d5bf00e8664b142b3e7224ad25fd22ac..7253a472e89a8fa3630863b093abffdc4854b5aa 100644 --- a/core/java/src/net/i2p/util/ByteCache.java +++ b/core/java/src/net/i2p/util/ByteCache.java @@ -13,22 +13,14 @@ import net.i2p.data.ByteArray; * Cache the objects frequently used to reduce memory churn. The ByteArray * should be held onto as long as the data referenced in it is needed. * + * For small arrays where the management of valid bytes in ByteArray + * and prezeroing isn't required, use SimpleByteArray instead. + * * Heap size control - survey of usage (April 2010) : * * <pre> Size Max MaxMem From - 16 16 256 CryptixAESEngine - 16 32 512 BloomFilterIVValidator - 16 64 1K UDP PacketBuilder - 16 128 2K tunnel HopProcessor - 16 128 2K tunnel TrivialPreprocessor - 16 128 2K tunnel InboundEndpointProcessor - 16 128 2K tunnel OutboundGatewayProcessor - - 32 64 2K UDP PacketBuilder - 32 128 4K tunnel TrivialPreprocessor - 1K 32 32K tunnel TrivialPreprocessor 1K 512 512K tunnel FragmentHandler 1K 512 512K I2NP TunnelDataMessage diff --git a/core/java/src/net/i2p/util/LHMCache.java b/core/java/src/net/i2p/util/LHMCache.java new file mode 100644 index 0000000000000000000000000000000000000000..b0818f9fbaf00606108c55e05f5c173c70427ece --- /dev/null +++ b/core/java/src/net/i2p/util/LHMCache.java @@ -0,0 +1,24 @@ +package net.i2p.util; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A LinkedHashMap with a maximum size, for use as + * an LRU cache. Unsynchronized. + * + * @since 0.9.3 + */ +public class LHMCache<K, V> extends LinkedHashMap<K, V> { + private final int _max; + + public LHMCache(int max) { + super(max, 0.75f, true); + _max = max; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + return size() > _max; + } +} diff --git a/core/java/src/net/i2p/util/LogRecord.java b/core/java/src/net/i2p/util/LogRecord.java index d9a4f6441a9cb42dc9bdbb3a7c84212fbfe42cd6..b1bd6b8d4483f5863530dbf73f4dcdb60b4db9a6 100644 --- a/core/java/src/net/i2p/util/LogRecord.java +++ b/core/java/src/net/i2p/util/LogRecord.java @@ -1,5 +1,7 @@ package net.i2p.util; +import net.i2p.data.DataHelper; + /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain @@ -59,4 +61,19 @@ class LogRecord { public Throwable getThrowable() { return _throwable; } + + /** + * Matches source class, message string, and throwable class only. + * @since 0.9.3 + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof LogRecord)) + return false; + LogRecord r = (LogRecord) o; + return _source == r._source && + DataHelper.eq(_message, r._message) && + ((_throwable == null && r._throwable == null) || + (_throwable != null && r._throwable != null && _throwable.getClass() == r._throwable.getClass())); + } } diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java index 5cde9ce33472d5fcce1f146bbcb3d2e5e068837e..a26433bb8afc4d22b63e1627ff760f15ed144ee8 100644 --- a/core/java/src/net/i2p/util/LogWriter.java +++ b/core/java/src/net/i2p/util/LogWriter.java @@ -26,8 +26,8 @@ class LogWriter implements Runnable { /** every 10 seconds? why? Just have the gui force a reread after a change?? */ private final static long CONFIG_READ_INTERVAL = 50 * 1000; private final static long FLUSH_INTERVAL = 9 * 1000; - private long _lastReadConfig = 0; - private long _numBytesInCurrentFile = 0; + private long _lastReadConfig; + private long _numBytesInCurrentFile; // volatile as it changes on log file rotation private volatile Writer _currentOut; private int _rotationNum = -1; @@ -66,16 +66,35 @@ class LogWriter implements Runnable { } public void flushRecords() { flushRecords(true); } + public void flushRecords(boolean shouldWait) { try { // zero copy, drain the manager queue directly Queue<LogRecord> records = _manager.getQueue(); if (records == null) return; if (!records.isEmpty()) { + LogRecord last = null; LogRecord rec; + int dupCount = 0; while ((rec = records.poll()) != null) { - writeRecord(rec); + if (rec.equals(last)) { + dupCount++; + } else { + if (dupCount > 0) { + if (dupCount == 1) + writeRecord("*** 1 similar message omitted\n"); + else + writeRecord("*** " + dupCount + " similar messages omitted\n"); + dupCount = 0; + } + last = rec; + writeRecord(rec); + } } + if (dupCount == 1) + writeRecord("*** 1 similar message omitted\n"); + else if (dupCount > 0) + writeRecord("*** " + dupCount + " similar messages omitted\n"); try { if (_currentOut != null) _currentOut.flush(); diff --git a/core/java/src/net/i2p/util/SimpleByteCache.java b/core/java/src/net/i2p/util/SimpleByteCache.java index 01d116abea1c04eb23fc0d35ab8cce987c15d98d..b41f9ad9b5bf87acedd4aebd7ad8d591b262732f 100644 --- a/core/java/src/net/i2p/util/SimpleByteCache.java +++ b/core/java/src/net/i2p/util/SimpleByteCache.java @@ -18,7 +18,7 @@ public final class SimpleByteCache { private static final Map<Integer, SimpleByteCache> _caches = new ConcurrentHashMap(8); - private static final int DEFAULT_SIZE = 16; + private static final int DEFAULT_SIZE = 64; /** up to this, use ABQ to minimize object churn and for performance; above this, use LBQ for two locks */ private static final int MAX_FOR_ABQ = 64; diff --git a/core/java/src/net/i2p/util/SimpleStore.java b/core/java/src/net/i2p/util/SimpleStore.java index fe946ad9e6651f2769f97520be2b5751b3457544..ebf91371293a922c13f8542cb189d5af715e8e29 100644 --- a/core/java/src/net/i2p/util/SimpleStore.java +++ b/core/java/src/net/i2p/util/SimpleStore.java @@ -5,6 +5,7 @@ package net.i2p.util; /** + * Deprecated - used only by SimpleTimer * * @author sponge */ diff --git a/core/java/src/net/i2p/util/SimpleTimer.java b/core/java/src/net/i2p/util/SimpleTimer.java index ba558ff21671ca9315556e222639f8ba7a783e44..181d01ffbdb07050a9e96fb823e3ac8810e5a1da 100644 --- a/core/java/src/net/i2p/util/SimpleTimer.java +++ b/core/java/src/net/i2p/util/SimpleTimer.java @@ -21,6 +21,7 @@ public class SimpleTimer { /** * If you have a context, use context.simpleTimer() instead + * @deprecated use SimpleTimer2 */ public static SimpleTimer getInstance() { return I2PAppContext.getGlobalContext().simpleTimer(); @@ -39,6 +40,7 @@ public class SimpleTimer { /** * To be instantiated by the context. * Others should use context.simpleTimer() instead + * @deprecated use SimpleTimer2 */ public SimpleTimer(I2PAppContext context) { this(context, "SimpleTimer"); @@ -47,6 +49,7 @@ public class SimpleTimer { /** * To be instantiated by the context. * Others should use context.simpleTimer() instead + * @deprecated use SimpleTimer2 */ private SimpleTimer(I2PAppContext context, String name) { runn = new SimpleStore(true); @@ -146,6 +149,7 @@ public class SimpleTimer { } } } + // FIXME if you plan to use this class again while (_events.containsKey(time)) time = new Long(time.longValue() + 1); _events.put(time, event); diff --git a/core/java/src/net/i2p/util/SimpleTimer2.java b/core/java/src/net/i2p/util/SimpleTimer2.java index 282c89ce3f9051d40f62f55a175d0707ae80235f..c36d6032452df26d403fc6569083636ac0ca84e4 100644 --- a/core/java/src/net/i2p/util/SimpleTimer2.java +++ b/core/java/src/net/i2p/util/SimpleTimer2.java @@ -205,10 +205,8 @@ public class SimpleTimer2 { } /** - * More efficient than reschedule(). - * Only call this after calling the non-scheduling constructor, - * or from within timeReached(), or you will get duplicates on the queue. - * Otherwise use reschedule(). + * Slightly more efficient than reschedule(). + * Does nothing if already scheduled. */ public synchronized void schedule(long timeoutMs) { if (_log.shouldLog(Log.DEBUG)) @@ -236,7 +234,8 @@ public class SimpleTimer2 { /** * Use the earliest of the new time and the old time - * Do not call from within timeReached() + * May be called from within timeReached(), but schedule() is + * better there. * * @param timeoutMs */ @@ -245,8 +244,8 @@ public class SimpleTimer2 { } /** - * useEarliestTime must be false if called from within timeReached(), as - * it won't be rescheduled, in favor of the currently running task + * May be called from within timeReached(), but schedule() is + * better there. * * @param timeoutMs * @param useEarliestTime if its already scheduled, use the earlier of the diff --git a/core/java/src/net/i2p/util/SocketTimeout.java b/core/java/src/net/i2p/util/SocketTimeout.java index 63f54d45d6c2d31c94211395deaad37577515c0c..3a8f57400b3713c028a45c1a3a18ba52857c4ce0 100644 --- a/core/java/src/net/i2p/util/SocketTimeout.java +++ b/core/java/src/net/i2p/util/SocketTimeout.java @@ -14,7 +14,7 @@ import java.util.Date; * * Use socket.setsotimeout instead? */ -public class SocketTimeout implements SimpleTimer.TimedEvent { +public class SocketTimeout extends SimpleTimer2.TimedEvent { private Socket _targetSocket; private long _startTime; private long _inactivityDelay; @@ -24,12 +24,13 @@ public class SocketTimeout implements SimpleTimer.TimedEvent { private Runnable _command; public SocketTimeout(long delay) { this(null, delay); } public SocketTimeout(Socket socket, long delay) { + super(SimpleTimer2.getInstance()); _inactivityDelay = delay; _targetSocket = socket; _cancelled = false; _lastActivity = _startTime = System.currentTimeMillis(); _totalTimeoutTime = -1; - SimpleTimer.getInstance().addEvent(SocketTimeout.this, delay); + schedule(delay); } public void timeReached() { if (_cancelled) return; @@ -44,13 +45,13 @@ public class SocketTimeout implements SimpleTimer.TimedEvent { } if (_command != null) _command.run(); } else { - SimpleTimer.getInstance().addEvent(SocketTimeout.this, _inactivityDelay); + schedule(_inactivityDelay); } } - public void cancel() { + public boolean cancel() { _cancelled = true; - SimpleTimer.getInstance().removeEvent(SocketTimeout.this); + return super.cancel(); } public void setSocket(Socket s) { _targetSocket = s; } public void resetTimer() { _lastActivity = System.currentTimeMillis(); } diff --git a/router/java/src/net/i2p/router/Blocklist.java b/router/java/src/net/i2p/router/Blocklist.java index 1cda3600485f4addb7c945810308823357333112..79fba4939ab06ec300ffd22744ccd011809c1735 100644 --- a/router/java/src/net/i2p/router/Blocklist.java +++ b/router/java/src/net/i2p/router/Blocklist.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.TreeSet; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; @@ -438,14 +439,8 @@ public class Blocklist { * of IP ranges read in from the file. */ public void add(String ip) { - InetAddress pi; - try { - pi = InetAddress.getByName(ip); - } catch (UnknownHostException uhe) { - return; - } - if (pi == null) return; - byte[] pib = pi.getAddress(); + byte[] pib = Addresses.getIP(ip); + if (pib == null) return; add(pib); } @@ -478,21 +473,13 @@ public class Blocklist { List<byte[]> rv = new ArrayList(1); RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer); if (pinfo == null) return rv; - String oldphost = null; + byte[] oldpib = null; // for each peer address for (RouterAddress pa : pinfo.getAddresses()) { - String phost = pa.getOption("host"); - if (phost == null) continue; - if (oldphost != null && oldphost.equals(phost)) continue; - oldphost = phost; - InetAddress pi; - try { - pi = InetAddress.getByName(phost); - } catch (UnknownHostException uhe) { - continue; - } - if (pi == null) continue; - byte[] pib = pi.getAddress(); + byte[] pib = pa.getIP(); + if (pib == null) continue; + if (DataHelper.eq(oldpib, pib)) continue; + oldpib = pib; rv.add(pib); } return rv; @@ -520,14 +507,8 @@ public class Blocklist { * calling this externally won't shitlist the peer, this is just an IP check */ public boolean isBlocklisted(String ip) { - InetAddress pi; - try { - pi = InetAddress.getByName(ip); - } catch (UnknownHostException uhe) { - return false; - } - if (pi == null) return false; - byte[] pib = pi.getAddress(); + byte[] pib = Addresses.getIP(ip); + if (pib == null) return false; return isBlocklisted(pib); } diff --git a/router/java/src/net/i2p/router/OutNetMessage.java b/router/java/src/net/i2p/router/OutNetMessage.java index 44674d7b76552942258fe128e8fb04158173fec0..9016d88dfc8eac2ad4f7ecfb24960d893110e25e 100644 --- a/router/java/src/net/i2p/router/OutNetMessage.java +++ b/router/java/src/net/i2p/router/OutNetMessage.java @@ -20,6 +20,7 @@ import java.util.Set; import net.i2p.data.RouterInfo; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.util.CDPQEntry; import net.i2p.util.Log; /** @@ -27,7 +28,7 @@ import net.i2p.util.Log; * delivery and jobs to be fired off if particular events occur. * */ -public class OutNetMessage { +public class OutNetMessage implements CDPQEntry { private final Log _log; private final RouterContext _context; private RouterInfo _target; @@ -47,6 +48,8 @@ public class OutNetMessage { private long _sendBegin; //private Exception _createdBy; private final long _created; + private long _enqueueTime; + private long _seqNum; /** for debugging, contains a mapping of even name to Long (e.g. "begin sending", "handleOutbound", etc) */ private HashMap<String, Long> _timestamps; /** @@ -56,6 +59,26 @@ public class OutNetMessage { private List<String> _timestampOrder; private Object _preparationBuf; + /** + * Priorities, higher is higher priority. + * @since 0.9.3 + */ + public static final int PRIORITY_HIGHEST = 1000; + public static final int PRIORITY_MY_BUILD_REQUEST = 500; + public static final int PRIORITY_MY_NETDB_LOOKUP = 500; + public static final int PRIORITY_MY_NETDB_STORE = 400; + public static final int PRIORITY_MY_DATA = 400; + public static final int PRIORITY_MY_NETDB_STORE_LOW = 300; + public static final int PRIORITY_HIS_BUILD_REQUEST = 300; + public static final int PRIORITY_BUILD_REPLY = 300; + public static final int PRIORITY_NETDB_REPLY = 300; + public static final int PRIORITY_HIS_NETDB_STORE = 200; + public static final int PRIORITY_NETDB_FLOOD = 200; + public static final int PRIORITY_PARTICIPATING = 200; + public static final int PRIORITY_NETDB_EXPLORE = 100; + public static final int PRIORITY_NETDB_HARVEST = 100; + public static final int PRIORITY_LOWEST = 100; + public OutNetMessage(RouterContext context) { _context = context; _log = context.logManager().getLog(OutNetMessage.class); @@ -264,6 +287,45 @@ public class OutNetMessage { /** time the transport tries to send the message (including any queueing) */ public long getSendTime() { return _context.clock().now() - _sendBegin; } + /** + * For CDQ + * @since 0.9.3 + */ + public void setEnqueueTime(long now) { + _enqueueTime = now; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public long getEnqueueTime() { + return _enqueueTime; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public void drop() { + } + + /** + * For CDPQ + * @since 0.9.3 + */ + public void setSeqNum(long num) { + _seqNum = num; + } + + /** + * For CDPQ + * @since 0.9.3 + */ + public long getSeqNum() { + return _seqNum; + } + /** * We've done what we need to do with the data from this message, though * we may keep the object around for a while to use its ID, jobs, etc. diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 442c8e78afc95b009485e657d675bb95a03d1484..03f2791c2f3be2b2fe5bea5a0485f6621990fb67 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -40,6 +40,7 @@ import net.i2p.router.startup.WorkingDir; import net.i2p.router.tasks.*; import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.transport.udp.UDPTransport; +import net.i2p.router.util.EventLog; import net.i2p.stat.RateStat; import net.i2p.stat.StatManager; import net.i2p.util.ByteCache; @@ -77,6 +78,7 @@ public class Router implements RouterClock.ClockShiftListener { private I2PThread _gracefulShutdownDetector; private RouterWatchdog _watchdog; private Thread _watchdogThread; + private final EventLog _eventLog; public final static String PROP_CONFIG_FILE = "router.configLocation"; @@ -100,6 +102,7 @@ public class Router implements RouterClock.ClockShiftListener { public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys"; public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress"; public final static String DNS_CACHE_TIME = "" + (5*60); + private static final String EVENTLOG = "eventlog.txt"; private static final String originalTimeZoneID; static { @@ -219,12 +222,14 @@ public class Router implements RouterClock.ClockShiftListener { // i2p.dir.pid defaults to i2p.dir.router // i2p.dir.base defaults to user.dir == $CWD _context = new RouterContext(this, envProps); + _eventLog = new EventLog(_context, new File(_context.getRouterDir(), EVENTLOG)); // This is here so that we can get the directory location from the context // for the ping file // Check for other router but do not start a thread yet so the update doesn't cause // a NCDFE if (!isOnlyRouterRunning()) { + _eventLog.addEvent(EventLog.ABORTED, "Another router running"); System.err.println("ERROR: There appears to be another router already running!"); System.err.println(" Please make sure to shut down old instances before starting up"); System.err.println(" a new one. If you are positive that no other instance is running,"); @@ -410,6 +415,12 @@ public class Router implements RouterClock.ClockShiftListener { public void runRouter() { if (_isAlive) throw new IllegalStateException(); + String last = _config.get("router.previousFullVersion"); + if (last != null) { + _eventLog.addEvent(EventLog.UPDATED, "from " + last + " to " + RouterVersion.FULL_VERSION); + saveConfig("router.previousFullVersion", null); + } + _eventLog.addEvent(EventLog.STARTED, RouterVersion.FULL_VERSION); startupStuff(); _isAlive = true; _started = _context.clock().now(); @@ -631,6 +642,13 @@ public class Router implements RouterClock.ClockShiftListener { return Certificate.NULL_CERT; } + /** + * @since 0.9.3 + */ + public EventLog eventLog() { + return _eventLog; + } + /** * Ugly list of files that we need to kill if we are building a new identity * @@ -646,7 +664,6 @@ public class Router implements RouterClock.ClockShiftListener { "sessionKeys.dat" // no longer used }; - static final String IDENTLOG = "identlog.txt"; public void killKeys() { //new Exception("Clearing identity files").printStackTrace(); int remCount = 0; @@ -671,18 +688,10 @@ public class Router implements RouterClock.ClockShiftListener { } if (remCount > 0) { - FileOutputStream log = null; - try { - log = new FileOutputStream(new File(_context.getRouterDir(), IDENTLOG), true); - log.write((new Date() + ": Old router identity keys cleared\n").getBytes()); - } catch (IOException ioe) { - // ignore - } finally { - if (log != null) - try { log.close(); } catch (IOException ioe) {} - } + _eventLog.addEvent(EventLog.REKEYED); } } + /** * Rebuild a new identity the hard way - delete all of our old identity * files, then reboot the router. @@ -836,6 +845,7 @@ public class Router implements RouterClock.ClockShiftListener { // logManager shut down in finalShutdown() _watchdog.shutdown(); _watchdogThread.interrupt(); + _eventLog.addEvent(EventLog.STOPPED, Integer.toString(exitCode)); finalShutdown(exitCode); } @@ -1139,6 +1149,7 @@ public class Router implements RouterClock.ClockShiftListener { _config.put("router.updateLastInstalled", "" + System.currentTimeMillis()); // Set the last version to the current version, since 0.8.13 _config.put("router.previousVersion", RouterVersion.VERSION); + _config.put("router.previousFullVersion", RouterVersion.FULL_VERSION); saveConfig(); ok = FileUtil.extractZip(updateFile, _context.getBaseDir()); } diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index da55abe2713dfa79137b70b797a461754f16be41..e4a5bc64dbd3797a0f83a76273c7dd3087f714b9 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -52,6 +52,8 @@ class ClientManager { /** SSL interface (only) @since 0.8.3 */ private static final String PROP_ENABLE_SSL = "i2cp.SSL"; + private static final int INTERNAL_QUEUE_SIZE = 256; + public ClientManager(RouterContext context, int port) { _ctx = context; _log = context.logManager().getLog(ClientManager.class); @@ -125,9 +127,8 @@ class ClientManager { public I2CPMessageQueue internalConnect() throws I2PSessionException { if (!_isStarted) throw new I2PSessionException("Router client manager is shut down"); - // for now we make these unlimited size - LinkedBlockingQueue<I2CPMessage> in = new LinkedBlockingQueue(); - LinkedBlockingQueue<I2CPMessage> out = new LinkedBlockingQueue(); + LinkedBlockingQueue<I2CPMessage> in = new LinkedBlockingQueue(INTERNAL_QUEUE_SIZE); + LinkedBlockingQueue<I2CPMessage> out = new LinkedBlockingQueue(INTERNAL_QUEUE_SIZE); I2CPMessageQueue myQueue = new I2CPMessageQueueImpl(in, out); I2CPMessageQueue hisQueue = new I2CPMessageQueueImpl(out, in); ClientConnectionRunner runner = new QueuedClientConnectionRunner(_ctx, this, myQueue); diff --git a/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java b/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java index f65b061766a8bf9982e09601ebf775de0b03cd7e..a783b3b9977019b0aa0926935eb74a3cec52e774 100644 --- a/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java +++ b/router/java/src/net/i2p/router/client/I2CPMessageQueueImpl.java @@ -1,6 +1,7 @@ package net.i2p.router.client; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.internal.I2CPMessageQueue; @@ -32,6 +33,16 @@ class I2CPMessageQueueImpl extends I2CPMessageQueue { return _out.offer(msg); } + /** + * Send a message, blocking. + * @param timeout how long to wait for space (ms) + * @return success (false if no space available or if timed out) + * @since 0.9.3 + */ + public boolean offer(I2CPMessage msg, long timeout) throws InterruptedException { + return _out.offer(msg, timeout, TimeUnit.MILLISECONDS); + } + /** * Receive a message, nonblocking * @return message or null if none available diff --git a/router/java/src/net/i2p/router/message/GarlicMessageHandler.java b/router/java/src/net/i2p/router/message/GarlicMessageHandler.java index 9f435c3bd55a4163da2c060827ea1bb4d9fde675..e065b3a24e69140f9b86a6b851e1988cf27f9f62 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageHandler.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageHandler.java @@ -19,6 +19,9 @@ import net.i2p.router.RouterContext; /** * HandlerJobBuilder to build jobs to handle GarlicMessages * + * This is essentially unused, as InNetMessagePool short circuits tunnel messages, + * and the garlics are handled in InboundMessageDistributor. + * Unless we get a garlic message not down a tunnel? */ public class GarlicMessageHandler implements HandlerJobBuilder { private final RouterContext _context; diff --git a/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java index 1b0e39ea7a7efbb0fa304d4f81015c62f29a84ba..3666276cbce72d47925d2c18e6c8ad9f668bb977 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java @@ -97,10 +97,12 @@ public class GarlicMessageReceiver { */ private void handleClove(GarlicClove clove) { if (!isValid(clove)) { - if (_log.shouldLog(Log.DEBUG)) - _log.warn("Invalid clove " + clove); + //if (_log.shouldLog(Log.WARN)) + // _log.warn("Invalid clove " + clove); return; } + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("valid clove " + clove); _receiver.handleClove(clove.getInstructions(), clove.getData()); } diff --git a/router/java/src/net/i2p/router/message/HandleGarlicMessageJob.java b/router/java/src/net/i2p/router/message/HandleGarlicMessageJob.java index 21788d76049ed9938e2871b6220fbdcb6b3652c1..4fd5342d53cc3b6bbbaeb133c293d8a90f91b1bb 100644 --- a/router/java/src/net/i2p/router/message/HandleGarlicMessageJob.java +++ b/router/java/src/net/i2p/router/message/HandleGarlicMessageJob.java @@ -15,6 +15,7 @@ import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.TunnelGatewayMessage; import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -24,6 +25,9 @@ import net.i2p.util.Log; * as if they arrived locally. Other instructions are not yet implemented (but * need to be. soon) * + * This is essentially unused, as InNetMessagePool short circuits tunnel messages, + * and the garlics are handled in InboundMessageDistributor. + * Unless we get a garlic message not down a tunnel? */ class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.CloveReceiver { private final Log _log; @@ -34,6 +38,9 @@ class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.Cl //private MessageHandler _handler; //private GarlicMessageParser _parser; + private final static int ROUTER_PRIORITY = OutNetMessage.PRIORITY_LOWEST; + private final static int TUNNEL_PRIORITY = OutNetMessage.PRIORITY_LOWEST; + /** * @param from ignored * @param fromHash ignored @@ -42,8 +49,8 @@ class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.Cl super(context); _log = context.logManager().getLog(HandleGarlicMessageJob.class); getContext().statManager().createRateStat("crypto.garlic.decryptFail", "How often garlic messages are undecryptable", "Encryption", new long[] { 5*60*1000, 60*60*1000, 24*60*60*1000 }); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("New handle garlicMessageJob called w/ message from [" + from + "]", new Exception("Debug")); + if (_log.shouldLog(Log.WARN)) + _log.warn("Garlic Message not down a tunnel??? from [" + from + "]", new Exception("I did it")); _message = msg; //_from = from; //_fromHash = fromHash; @@ -78,10 +85,10 @@ class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.Cl } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("router delivery instructions targetting " - + instructions.getRouter().toBase64().substring(0,4)); + + instructions.getRouter().toBase64().substring(0,4) + " for " + data); SendMessageDirectJob j = new SendMessageDirectJob(getContext(), data, instructions.getRouter(), - 10*1000, 100); + 10*1000, ROUTER_PRIORITY); // run it inline (adds to the outNetPool if it has the router info, otherwise queue a lookup) j.runJob(); //getContext().jobQueue().addJob(j); @@ -92,9 +99,12 @@ class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.Cl gw.setMessage(data); gw.setTunnelId(instructions.getTunnelId()); gw.setMessageExpiration(data.getMessageExpiration()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("tunnel delivery instructions targetting " + + instructions.getRouter().toBase64().substring(0,4) + " for " + data); SendMessageDirectJob job = new SendMessageDirectJob(getContext(), gw, instructions.getRouter(), - 10*1000, 100); + 10*1000, TUNNEL_PRIORITY); // run it inline (adds to the outNetPool if it has the router info, otherwise queue a lookup) job.runJob(); // getContext().jobQueue().addJob(job); diff --git a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java index 00dd07f2419c4670491373fdcdeb59bd37cb06ac..13f81f1e6b7c3f47bd4b5074bc3f4a38ce412d06 100644 --- a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java @@ -24,6 +24,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.TunnelGatewayMessage; import net.i2p.router.Job; import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.message.SendMessageDirectJob; @@ -40,7 +41,7 @@ public class HandleDatabaseLookupMessageJob extends JobImpl { private final static int MAX_ROUTERS_RETURNED = 3; private final static int CLOSENESS_THRESHOLD = 8; // FNDF.MAX_TO_FLOOD + 1 private final static int REPLY_TIMEOUT = 60*1000; - private final static int MESSAGE_PRIORITY = 300; + private final static int MESSAGE_PRIORITY = OutNetMessage.PRIORITY_NETDB_REPLY; /** * If a routerInfo structure isn't this recent, don't send it out. @@ -283,7 +284,7 @@ public class HandleDatabaseLookupMessageJob extends JobImpl { m.setMessage(message); m.setMessageExpiration(message.getMessageExpiration()); m.setTunnelId(replyTunnel); - SendMessageDirectJob j = new SendMessageDirectJob(getContext(), m, toPeer, 10*1000, 100); + SendMessageDirectJob j = new SendMessageDirectJob(getContext(), m, toPeer, 10*1000, MESSAGE_PRIORITY); j.runJob(); //getContext().jobQueue().addJob(j); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index 05373c385dc2c6875288ac1159cc08dc69b31501..562a4ac82155c6f1b8aa25aed1ad3efb0ebd69e4 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -41,6 +41,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad */ private static final int MAX_TO_FLOOD = 4; + private static final int FLOOD_PRIORITY = OutNetMessage.PRIORITY_NETDB_FLOOD; + private static final int FLOOD_TIMEOUT = 30*1000; + public FloodfillNetworkDatabaseFacade(RouterContext context) { super(context); _activeFloodQueries = new HashMap(); @@ -224,9 +227,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad } } - private static final int FLOOD_PRIORITY = 200; - private static final int FLOOD_TIMEOUT = 30*1000; - @Override protected PeerSelector createPeerSelector() { return new FloodfillPeerSelector(_context); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HarvesterJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HarvesterJob.java index 48ddc4f2c79fbd5e761944222e284181d72965a0..0011b2098d0d0ad7855c57fb0e017e702681d0c3 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HarvesterJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HarvesterJob.java @@ -11,6 +11,7 @@ import net.i2p.data.Hash; import net.i2p.data.RouterInfo; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; import net.i2p.router.message.SendMessageDirectJob; @@ -39,7 +40,7 @@ class HarvesterJob extends JobImpl { /** don't try to update more than 5 peers during each run */ private static final int MAX_PER_RUN = 5; /** background job, who cares */ - private static final int PRIORITY = 100; + private static final int PRIORITY = OutNetMessage.PRIORITY_NETDB_HARVEST; public static final String PROP_ENABLED = "netDb.shouldHarvest"; diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java index 0b5b8dd9626f4f7e3336f1822a5bb7e468d66b29..c21c7cee2edc0caaae64eadc27a9161d7dfef9d8 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java @@ -25,6 +25,7 @@ import net.i2p.data.i2np.DatabaseSearchReplyMessage; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.router.Job; import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; import net.i2p.util.Log; @@ -78,6 +79,10 @@ class SearchJob extends JobImpl { * */ private static final long REQUEUE_DELAY = 1000; + + // TODO pass to the tunnel dispatcher + //private final static int LOOKUP_PRIORITY = OutNetMessage.PRIORITY_MY_NETDB_LOOKUP; + //private final static int STORE_PRIORITY = OutNetMessage.PRIORITY_HIS_NETDB_STORE; /** * Create a new search for the routingKey specified @@ -445,6 +450,7 @@ class SearchJob extends JobImpl { if (FloodfillNetworkDatabaseFacade.isFloodfill(router)) _floodfillSearchesOutstanding++; getContext().messageRegistry().registerPending(sel, reply, new FailedJob(getContext(), router), timeout); + // TODO pass a priority to the dispatcher getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnelId, to); } @@ -652,6 +658,7 @@ class SearchJob extends JobImpl { if (outTunnel != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("resending leaseSet out to " + to + " through " + outTunnel + ": " + msg); + // TODO pass a priority to the dispatcher getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, to); return true; } else { diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java index f91df9b2b023ae46cf5900a4eccce6e1c85ae27f..0632da80d85eed28ae936e2e589bddf464498598 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java @@ -43,7 +43,7 @@ class StoreJob extends JobImpl { private final static int PARALLELIZATION = 4; // how many sent at a time private final static int REDUNDANCY = 4; // we want the data sent to 6 peers - private final static int STORE_PRIORITY = 100; + private final static int STORE_PRIORITY = OutNetMessage.PRIORITY_MY_NETDB_STORE; /** * Send a data structure to the floodfills diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index 65f72dd9ad32fda3fd565271a6e1b37ae15b7c7e..9b1a3569faa34557247304d8a8bf3575365afc97 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -1261,16 +1261,8 @@ public class ProfileOrganizer { if (paddr == null) return rv; for (RouterAddress pa : paddr) { - String phost = pa.getOption("host"); - if (phost == null) continue; - InetAddress pi; - try { - pi = InetAddress.getByName(phost); - } catch (UnknownHostException uhe) { - continue; - } - if (pi == null) continue; - byte[] pib = pi.getAddress(); + byte[] pib = pa.getIP(); + if (pib == null) continue; rv.add(maskedIP(pib, mask)); } return rv; diff --git a/router/java/src/net/i2p/router/tasks/RouterWatchdog.java b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java index 7cc029b77ab9b4877149907df8ccf3912a1f9bf4..213721f9f2eddfadeb2efd6dfb2876881a855924 100644 --- a/router/java/src/net/i2p/router/tasks/RouterWatchdog.java +++ b/router/java/src/net/i2p/router/tasks/RouterWatchdog.java @@ -6,6 +6,7 @@ import net.i2p.data.DataHelper; import net.i2p.router.Job; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.util.EventLog; import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import net.i2p.util.ShellCommand; @@ -107,6 +108,7 @@ public class RouterWatchdog implements Runnable { _log.error("Memory: " + DataHelper.formatSize(used) + '/' + DataHelper.formatSize(max)); if (_consecutiveErrors == 1) { _log.log(Log.CRIT, "Router appears hung, or there is severe network congestion. Watchdog starts barking!"); + _context.router().eventLog().addEvent(EventLog.WATCHDOG); // This works on linux... // It won't on windows, and we can't call i2prouter.bat either, it does something // completely different... diff --git a/router/java/src/net/i2p/router/tasks/ShutdownHook.java b/router/java/src/net/i2p/router/tasks/ShutdownHook.java index ae8e38d7a48da097e07429eb956c0b0eff277f5f..d3db8f876569b34f2373539549a0c2acc453c27a 100644 --- a/router/java/src/net/i2p/router/tasks/ShutdownHook.java +++ b/router/java/src/net/i2p/router/tasks/ShutdownHook.java @@ -10,6 +10,8 @@ package net.i2p.router.tasks; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.RouterVersion; +import net.i2p.router.util.EventLog; import net.i2p.util.Log; /** @@ -35,6 +37,7 @@ public class ShutdownHook extends Thread { // Needed to make the wrapper happy, otherwise it gets confused // and thinks we haven't shut down, possibly because it // prevents other shutdown hooks from running + _context.router().eventLog().addEvent(EventLog.CRASHED, RouterVersion.FULL_VERSION); _context.router().setKillVMOnEnd(false); _context.router().shutdown2(Router.EXIT_HARD); } diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 83da98b12598723a34f72e61258f9882918b5bd6..e139cfb2d2ddb6a8893d951af68e40eb2ce8d326 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -62,6 +62,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade { public void shutdown() { if (_manager != null) _manager.shutdown(); + _geoIP.shutdown(); } public void restart() { @@ -250,7 +251,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade { props.setProperty(NTCPAddress.PROP_PORT, port); RouterAddress addr = new RouterAddress(); addr.setCost(NTCPAddress.DEFAULT_COST); - addr.setExpiration(null); + //addr.setExpiration(null); addr.setOptions(props); addr.setTransportStyle(NTCPTransport.STYLE); //if (isNew) { diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java index 2bfdcdf8521154ba8bfc582d9b0ebeb72951b307..d4ce79ce2f4251b3f65e62c9edea75bbac085380 100644 --- a/router/java/src/net/i2p/router/transport/GeoIP.java +++ b/router/java/src/net/i2p/router/transport/GeoIP.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import net.i2p.data.Hash; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; @@ -72,6 +73,17 @@ class GeoIP { static final String COUNTRY_FILE_DEFAULT = "countries.txt"; public static final String PROP_IP_COUNTRY = "i2np.lastCountry"; + /** + * @since 0.9.3 + */ + public void shutdown() { + _codeToName.clear(); + _codeCache.clear(); + _IPToCountry.clear(); + _pendingSearch.clear(); + _notFound.clear(); + } + /** * Fire off a thread to lookup all pending IPs. * There is no indication of completion. @@ -297,14 +309,8 @@ class GeoIP { * Add to the list needing lookup */ public void add(String ip) { - InetAddress pi; - try { - pi = InetAddress.getByName(ip); - } catch (UnknownHostException uhe) { - return; - } - if (pi == null) return; - byte[] pib = pi.getAddress(); + byte[] pib = Addresses.getIP(ip); + if (pib == null) return; add(pib); } @@ -325,14 +331,8 @@ class GeoIP { * @return lower-case code, generally two letters, or null. */ public String get(String ip) { - InetAddress pi; - try { - pi = InetAddress.getByName(ip); - } catch (UnknownHostException uhe) { - return null; - } - if (pi == null) return null; - byte[] pib = pi.getAddress(); + byte[] pib = Addresses.getIP(ip); + if (pib == null) return null; return get(pib); } diff --git a/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java b/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java index 4ed7624c94a31f43f40d320b72b99d5ae38a10f6..2b79babe321510535281ea5005ede51dba0ac4bf 100644 --- a/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java +++ b/router/java/src/net/i2p/router/transport/OutboundMessageRegistry.java @@ -25,7 +25,7 @@ import net.i2p.router.ReplyJob; import net.i2p.router.RouterContext; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; /** * Tracks outbound messages. @@ -254,10 +254,11 @@ public class OutboundMessageRegistry { /** @deprecated unused */ public void renderStatusHTML(Writer out) throws IOException {} - private class CleanupTask implements SimpleTimer.TimedEvent { + private class CleanupTask extends SimpleTimer2.TimedEvent { private long _nextExpire; public CleanupTask() { + super(_context.simpleTimer2()); _nextExpire = -1; } @@ -312,14 +313,14 @@ public class OutboundMessageRegistry { if (_nextExpire <= now) _nextExpire = now + 10*1000; - SimpleTimer.getInstance().addEvent(CleanupTask.this, _nextExpire - now); + schedule(_nextExpire - now); } public void scheduleExpiration(MessageSelector sel) { long now = _context.clock().now(); if ( (_nextExpire <= now) || (sel.getExpiration() < _nextExpire) ) { _nextExpire = sel.getExpiration(); - SimpleTimer.getInstance().addEvent(CleanupTask.this, _nextExpire - now); + reschedule(_nextExpire - now); } } } diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index cd1a8199faafd65bc8be09a1a0acd12a204c5fb5..05bfba0bfdf6b9e114970d79d9723df463cc84be 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; +import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.RouterAddress; import net.i2p.data.RouterIdentity; @@ -35,6 +36,7 @@ import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.ConcurrentHashSet; +import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -53,7 +55,18 @@ public abstract class TransportImpl implements Transport { private final Map<Hash, Long> _unreachableEntries; private final Set<Hash> _wasUnreachableEntries; /** global router ident -> IP */ - private static final Map<Hash, byte[]> _IPMap = new ConcurrentHashMap(128); + private static final Map<Hash, byte[]> _IPMap; + + static { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory == Long.MAX_VALUE) + maxMemory = 96*1024*1024l; + long min = 512; + long max = 4096; + // 1024 nominal for 128 MB + int size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (128*1024)))); + _IPMap = new LHMCache(size); + } /** * Initialize the new transport @@ -585,12 +598,27 @@ public abstract class TransportImpl implements Transport { } public void setIP(Hash peer, byte[] ip) { - _IPMap.put(peer, ip); - _context.commSystem().queueLookup(ip); + byte[] old; + synchronized (_IPMap) { + old = _IPMap.put(peer, ip); + } + if (!DataHelper.eq(old, ip)) + _context.commSystem().queueLookup(ip); } public static byte[] getIP(Hash peer) { - return _IPMap.get(peer); + synchronized (_IPMap) { + return _IPMap.get(peer); + } + } + + /** + * @since 0.9.3 + */ + static void clearCaches() { + synchronized(_IPMap) { + _IPMap.clear(); + } } /** @param addr non-null */ diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index bfbc6d41fa9d4a1022d881cae24ea067b67cf442..d4587e60ea1293ff22a9360cf053e840e07f7e6d 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -185,6 +185,8 @@ public class TransportManager implements TransportEventListener { public void shutdown() { stopListening(); _dhThread.shutdown(); + Addresses.clearCaches(); + TransportImpl.clearCaches(); } public Transport getTransport(String style) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java index c94f02c85041b900219a12c2eaf9420ae4bb849d..b37d23cc11f7aea5ec0401e4029ce2f0e7360613 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java @@ -8,13 +8,13 @@ package net.i2p.router.transport.ntcp; * */ -import java.net.InetAddress; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.RouterAddress; import net.i2p.router.transport.TransportImpl; +import net.i2p.util.Addresses; import net.i2p.util.Log; /** @@ -25,9 +25,9 @@ public class NTCPAddress { private final String _host; //private InetAddress _addr; /** Port number used in RouterAddress definitions */ - public final static String PROP_PORT = "port"; + public final static String PROP_PORT = RouterAddress.PROP_PORT; /** Host name used in RouterAddress definitions */ - public final static String PROP_HOST = "host"; + public final static String PROP_HOST = RouterAddress.PROP_HOST; public static final int DEFAULT_COST = 10; public NTCPAddress(String host, int port) { @@ -59,23 +59,8 @@ public class NTCPAddress { _port = -1; return; } - String host = addr.getOption(PROP_HOST); - int iport = -1; - if (host == null) { - _host = null; - } else { - _host = host.trim(); - String port = addr.getOption(PROP_PORT); - if ( (port != null) && (port.trim().length() > 0) && !("null".equals(port)) ) { - try { - iport = Integer.parseInt(port.trim()); - } catch (NumberFormatException nfe) { - Log log = I2PAppContext.getGlobalContext().logManager().getLog(NTCPAddress.class); - log.error("Invalid port [" + port + "]", nfe); - } - } - } - _port = iport; + _host = addr.getOption(PROP_HOST); + _port = addr.getPort(); } public RouterAddress toRouterAddress() { @@ -85,7 +70,7 @@ public class NTCPAddress { RouterAddress addr = new RouterAddress(); addr.setCost(DEFAULT_COST); - addr.setExpiration(null); + //addr.setExpiration(null); Properties props = new Properties(); props.setProperty(PROP_HOST, _host); @@ -106,24 +91,11 @@ public class NTCPAddress { public boolean isPubliclyRoutable() { return isPubliclyRoutable(_host); } + public static boolean isPubliclyRoutable(String host) { if (host == null) return false; - try { - InetAddress addr = InetAddress.getByName(host); - byte quad[] = addr.getAddress(); - // allow ipv6 for ntcpaddress, since we've still got ssu - //if (quad.length != 4) { - // if (_log.shouldLog(Log.ERROR)) - // _log.error("Refusing IPv6 address (" + host + " / " + addr.getHostAddress() + ") " - // + " since not all peers support it, and we don't support restricted routes"); - // return false; - //} - return TransportImpl.isPubliclyRoutable(quad); - } catch (Throwable t) { - //if (_log.shouldLog(Log.WARN)) - // _log.warn("Error checking routability", t); - return false; - } + byte quad[] = Addresses.getIP(host); + return TransportImpl.isPubliclyRoutable(quad); } @Override diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 308995f10c90fae63ce86a7df445d655d8869c9d..4ad84d2592bb6a2d170b263d5aae9d02ccdc44c7 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -4,7 +4,9 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @@ -24,6 +26,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.transport.FIFOBandwidthLimiter; +import net.i2p.router.util.CoDelPriorityBlockingQueue; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.HexDump; import net.i2p.util.Log; @@ -83,7 +86,7 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { /** * pending unprepared OutNetMessage instances */ - private final Queue<OutNetMessage> _outbound; + private final CoDelPriorityBlockingQueue<OutNetMessage> _outbound; /** * current prepared OutNetMessage, or null - synchronize on _outbound to modify * FIXME why do we need this??? @@ -136,6 +139,8 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { public static final int BUFFER_SIZE = 16*1024; /** 2 bytes for length and 4 for CRC */ public static final int MAX_MSG_SIZE = BUFFER_SIZE - (2 + 4); + + private static final int PRIORITY = OutNetMessage.PRIORITY_MY_NETDB_STORE_LOW; /** * Create an inbound connected (though not established) NTCP connection @@ -150,8 +155,7 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { _readBufs = new ConcurrentLinkedQueue(); _writeBufs = new ConcurrentLinkedQueue(); _bwRequests = new ConcurrentHashSet(2); - // TODO possible switch to CLQ but beware non-constant size() - see below - _outbound = new LinkedBlockingQueue(); + _outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32); _isInbound = true; _decryptBlockBuf = new byte[BLOCK_SIZE]; _curReadState = new ReadState(); @@ -175,8 +179,7 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { _readBufs = new ConcurrentLinkedQueue(); _writeBufs = new ConcurrentLinkedQueue(); _bwRequests = new ConcurrentHashSet(8); - // TODO possible switch to CLQ but beware non-constant size() - see below - _outbound = new LinkedBlockingQueue(); + _outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32); _isInbound = false; _decryptBlockBuf = new byte[BLOCK_SIZE]; _curReadState = new ReadState(); @@ -295,15 +298,16 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { EventPumper.releaseBuf(bb); } - OutNetMessage msg; - while ((msg = _outbound.poll()) != null) { + List<OutNetMessage> pending = new ArrayList(); + _outbound.drainAllTo(pending); + for (OutNetMessage msg : pending) { Object buf = msg.releasePreparationBuffer(); if (buf != null) releaseBuf((PrepBuffer)buf); _transport.afterSend(msg, false, allowRequeue, msg.getLifetime()); } - msg = _currentOutbound; + OutNetMessage msg = _currentOutbound; if (msg != null) { Object buf = msg.releasePreparationBuffer(); if (buf != null) @@ -316,6 +320,9 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { * toss the message onto the connection's send queue */ public void send(OutNetMessage msg) { + /**** + always enqueue, let the queue do the dropping + if (tooBacklogged()) { boolean allowRequeue = false; // if we are too backlogged in tcp, don't try ssu boolean successful = false; @@ -335,20 +342,20 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { return; } _consecutiveBacklog = 0; - int enqueued = 0; + ****/ //if (FAST_LARGE) bufferedPrepare(msg); - boolean noOutbound = false; _outbound.offer(msg); - enqueued = _outbound.size(); + //int enqueued = _outbound.size(); // although stat description says ahead of this one, not including this one... - _context.statManager().addRateData("ntcp.sendQueueSize", enqueued); - noOutbound = (_currentOutbound == null); - if (_log.shouldLog(Log.DEBUG)) _log.debug("messages enqueued on " + toString() + ": " + enqueued + " new one: " + msg.getMessageId() + " of " + msg.getMessageType()); + //_context.statManager().addRateData("ntcp.sendQueueSize", enqueued); + boolean noOutbound = (_currentOutbound == null); + //if (_log.shouldLog(Log.DEBUG)) _log.debug("messages enqueued on " + toString() + ": " + enqueued + " new one: " + msg.getMessageId() + " of " + msg.getMessageType()); if (_established && noOutbound) _transport.getWriter().wantsWrite(this, "enqueued"); } +/**** private long queueTime() { OutNetMessage msg = _currentOutbound; if (msg == null) { @@ -358,29 +365,31 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { } return msg.getSendTime(); // does not include any of the pre-send(...) preparation } +****/ public boolean tooBacklogged() { - long queueTime = queueTime(); - if (queueTime <= 0) return false; - boolean currentOutboundSet = _currentOutbound != null; + //long queueTime = queueTime(); + //if (queueTime <= 0) return false; // perhaps we could take into account the size of the queued messages too, our // current transmission rate, and how much time is left before the new message's expiration? // ok, maybe later... if (getUptime() < 10*1000) // allow some slack just after establishment return false; - if (queueTime > 5*1000) { // bloody arbitrary. well, its half the average message lifetime... + //if (queueTime > 5*1000) { // bloody arbitrary. well, its half the average message lifetime... + if (_outbound.isBacklogged()) { // bloody arbitrary. well, its half the average message lifetime... int size = _outbound.size(); if (_log.shouldLog(Log.WARN)) { int writeBufs = _writeBufs.size(); + boolean currentOutboundSet = _currentOutbound != null; try { - _log.warn("Too backlogged: queue time " + queueTime + " and the size is " + size + _log.warn("Too backlogged: size is " + size + ", wantsWrite? " + (0 != (_conKey.interestOps()&SelectionKey.OP_WRITE)) + ", currentOut set? " + currentOutboundSet + ", writeBufs: " + writeBufs + " on " + toString()); } catch (Exception e) {} // java.nio.channels.CancelledKeyException } - _context.statManager().addRateData("ntcp.sendBacklogTime", queueTime); + //_context.statManager().addRateData("ntcp.sendBacklogTime", queueTime); return true; //} else if (size > 32) { // another arbitrary limit. // if (_log.shouldLog(Log.ERROR)) @@ -397,7 +406,7 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { DatabaseStoreMessage dsm = new DatabaseStoreMessage(_context); dsm.setEntry(_context.router().getRouterInfo()); infoMsg.setMessage(dsm); - infoMsg.setPriority(100); + infoMsg.setPriority(PRIORITY); RouterInfo target = _context.netDb().lookupRouterInfoLocally(_remotePeer.calculateHash()); if (target != null) { infoMsg.setTarget(target); @@ -649,11 +658,14 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { _log.info("attempt for multiple outbound messages with " + System.identityHashCode(_currentOutbound) + " already waiting and " + _outbound.size() + " queued"); return; } +/**** //throw new RuntimeException("We should not be preparing a write while we still have one pending"); if (queueTime() > 3*1000) { // don't stall low-priority messages +****/ msg = _outbound.poll(); if (msg == null) return; +/**** } else { // FIXME // This is a linear search to implement a priority queue, O(n**2) @@ -679,6 +691,7 @@ class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { if ((!removed) && _log.shouldLog(Log.WARN)) _log.warn("Already removed??? " + msg.getMessage().getType()); } +****/ _currentOutbound = msg; } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 24e9fdfdbb1586b65de4cee2b788dc632c997573..518596d0beabc2351a95468be59b8f751c545b07 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -298,8 +298,8 @@ public class NTCPTransport extends TransportImpl { _log.debug("no bid when trying to send to " + peer.toBase64() + " as they don't have an ntcp address"); return null; } - NTCPAddress naddr = new NTCPAddress(addr); - if ( (naddr.getPort() <= 0) || (naddr.getHost() == null) ) { + byte[] ip = addr.getIP(); + if ( (addr.getPort() <= 0) || (ip == null) ) { _context.statManager().addRateData("ntcp.connectFailedInvalidPort", 1); markUnreachable(peer); //_context.shitlist().shitlistRouter(toAddress.getIdentity().calculateHash(), "Invalid NTCP address", STYLE); @@ -307,7 +307,7 @@ public class NTCPTransport extends TransportImpl { _log.debug("no bid when trying to send to " + peer.toBase64() + " as they don't have a valid ntcp address"); return null; } - if (!naddr.isPubliclyRoutable()) { + if (!isPubliclyRoutable(ip)) { if (! _context.getProperty("i2np.ntcp.allowLocal", "false").equals("true")) { _context.statManager().addRateData("ntcp.bidRejectedLocalAddress", 1); markUnreachable(peer); diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java index c2e6e4124d7b3bdb07af62873d9d344cc437c91d..e4f8435ae40db793f6eaa7f6d71ffd1638483e5f 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java @@ -3,6 +3,7 @@ package net.i2p.router.transport.udp; import net.i2p.data.ByteArray; import net.i2p.data.Hash; import net.i2p.router.RouterContext; +import net.i2p.router.util.CDQEntry; import net.i2p.util.ByteCache; import net.i2p.util.Log; @@ -12,7 +13,7 @@ import net.i2p.util.Log; * Warning - there is no synchronization in this class, take care in * InboundMessageFragments to avoid use-after-release, etc. */ -class InboundMessageState { +class InboundMessageState implements CDQEntry { private final RouterContext _context; private final Log _log; private final long _messageId; @@ -29,6 +30,7 @@ class InboundMessageState { */ private int _lastFragment; private final long _receiveBegin; + private long _enqueueTime; private int _completeSize; private boolean _released; @@ -138,6 +140,30 @@ class InboundMessageState { return _context.clock().now() - _receiveBegin; } + /** + * For CDQ + * @since 0.9.3 + */ + public void setEnqueueTime(long now) { + _enqueueTime = now; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public long getEnqueueTime() { + return _enqueueTime; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public void drop() { + releaseResources(); + } + public Hash getFrom() { return _from; } public long getMessageId() { return _messageId; } diff --git a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java index 112ea249803144615e2f3d326d98de0fbff64dec..61061e7cb676ef02da465114b73a17e621b6005f 100644 --- a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java @@ -1,7 +1,6 @@ package net.i2p.router.transport.udp; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import net.i2p.data.Base64; import net.i2p.data.ByteArray; @@ -10,6 +9,7 @@ import net.i2p.data.i2np.I2NPMessageException; import net.i2p.data.i2np.I2NPMessageHandler; import net.i2p.data.i2np.I2NPMessageImpl; import net.i2p.router.RouterContext; +import net.i2p.router.util.CoDelBlockingQueue; //import net.i2p.util.ByteCache; import net.i2p.util.HexDump; import net.i2p.util.I2PThread; @@ -55,7 +55,7 @@ class MessageReceiver { _threadCount = Math.max(MIN_THREADS, Math.min(MAX_THREADS, ctx.bandwidthLimiter().getInboundKBytesPerSecond() / 20)); qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024))); } - _completeMessages = new LinkedBlockingQueue(qsize); + _completeMessages = new CoDelBlockingQueue(ctx, "UDP-MessageReceiver", qsize); // the runners run forever, no need to have a cache //_cache = ByteCache.getInstance(64, I2NPMessage.MAX_SIZE); diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java index f2180df8ce5452cf9f0c97bbbb2bccc223a3b376..9ce77b336418e2b1a4c5e17567d6efb9fa7205ba 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java @@ -165,9 +165,9 @@ class OutboundMessageFragments { state.releaseResources(); return; } - int active = peer.add(state); + peer.add(state); add(peer); - _context.statManager().addRateData("udp.outboundActiveCount", active, 0); + //_context.statManager().addRateData("udp.outboundActiveCount", active, 0); } else { if (_log.shouldLog(Log.WARN)) _log.warn("Error initializing " + msg); @@ -182,9 +182,9 @@ class OutboundMessageFragments { PeerState peer = state.getPeer(); if (peer == null) throw new RuntimeException("wtf, null peer for " + state); - int active = peer.add(state); + peer.add(state); add(peer); - _context.statManager().addRateData("udp.outboundActiveCount", active, 0); + //_context.statManager().addRateData("udp.outboundActiveCount", active, 0); } /** diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java index 4dc7c87faf3aefdc5c5e3926086a07c2204ab039..49a44873e44c1fcabf680ec0a17e2a753cc2e3bd 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java @@ -7,6 +7,7 @@ import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.OutNetMessage; +import net.i2p.router.util.CDPQEntry; import net.i2p.util.ByteCache; import net.i2p.util.Log; @@ -14,7 +15,7 @@ import net.i2p.util.Log; * Maintain the outbound fragmentation for resending, for a single message. * */ -class OutboundMessageState { +class OutboundMessageState implements CDPQEntry { private final I2PAppContext _context; private final Log _log; /** may be null if we are part of the establishment */ @@ -36,6 +37,9 @@ class OutboundMessageState { /** for tracking use-after-free bugs */ private boolean _released; private Exception _releasedBy; + // we can't use the ones in _message since it is null for injections + private long _enqueueTime; + private long _seqNum; public static final int MAX_MSG_SIZE = 32 * 1024; /** is this enough for a high-bandwidth router? */ @@ -104,6 +108,7 @@ class OutboundMessageState { /** * Called from OutboundMessageFragments + * @param m null if msg is "injected" * @return success */ private boolean initialize(OutNetMessage m, I2NPMessage msg, PeerState peer) { @@ -128,8 +133,8 @@ class OutboundMessageState { _expiration = _startedOn + EXPIRATION; //_expiration = msg.getExpiration(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Raw byte array for " + _messageId + ": " + Base64.encode(_messageBuf.getData(), 0, len)); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Raw byte array for " + _messageId + ": " + Base64.encode(_messageBuf.getData(), 0, len)); return true; } catch (IllegalStateException ise) { _cache.release(_messageBuf); @@ -368,6 +373,56 @@ class OutboundMessageState { } } + /** + * For CDQ + * @since 0.9.3 + */ + public void setEnqueueTime(long now) { + _enqueueTime = now; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public long getEnqueueTime() { + return _enqueueTime; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public void drop() { + _peer.getTransport().failed(this, false); + releaseResources(); + } + + /** + * For CDPQ + * @since 0.9.3 + */ + public void setSeqNum(long num) { + _seqNum = num; + } + + /** + * For CDPQ + * @since 0.9.3 + */ + public long getSeqNum() { + return _seqNum; + } + + /** + * For CDPQ + * @return OutNetMessage priority or 1000 for injected + * @since 0.9.3 + */ + public int getPriority() { + return _message != null ? _message.getPriority() : 1000; + } + @Override public String toString() { short sends[] = _fragmentSends; diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 8af7c67323d7bc5351179c9d3f0ed9abbc70d945..d6bb57806345e1a0e770aaf0a667e777e2a1599b 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -16,9 +16,9 @@ import net.i2p.data.Hash; import net.i2p.data.RouterIdentity; import net.i2p.data.SessionKey; import net.i2p.data.Signature; -import net.i2p.util.ByteCache; import net.i2p.util.Addresses; import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; /** * Big ol' class to do all our packet formatting. The UDPPackets generated are @@ -102,9 +102,6 @@ class PacketBuilder { private final Log _log; private final UDPTransport _transport; - private static final ByteCache _ivCache = ByteCache.getInstance(64, UDPPacket.IV_SIZE); - private static final ByteCache _hmacCache = ByteCache.getInstance(64, Hash.HASH_LENGTH); - /** * For debugging and stats only - does not go out on the wire. * These are chosen to be higher than the highest I2NP message type, @@ -607,12 +604,12 @@ class PacketBuilder { // ok, now the full data is in there, but we also need to encrypt // the signature, which means we need the IV - ByteArray iv = _ivCache.acquire(); - _context.random().nextBytes(iv.getData()); + byte[] iv = SimpleByteCache.acquire(UDPPacket.IV_SIZE); + _context.random().nextBytes(iv); int encrWrite = Signature.SIGNATURE_BYTES + 8; int sigBegin = off - encrWrite; - _context.aes().encrypt(data, sigBegin, data, sigBegin, state.getCipherKey(), iv.getData(), encrWrite); + _context.aes().encrypt(data, sigBegin, data, sigBegin, state.getCipherKey(), iv, encrWrite); // pad up so we're on the encryption boundary if ( (off % 16) != 0) @@ -620,7 +617,7 @@ class PacketBuilder { packet.getPacket().setLength(off); authenticate(packet, ourIntroKey, ourIntroKey, iv); setTo(packet, to, state.getSentPort()); - _ivCache.release(iv); + SimpleByteCache.release(iv); packet.setMessageType(TYPE_CREAT); return packet; } @@ -1290,10 +1287,10 @@ class PacketBuilder { * @param macKey key to generate the, er, MAC */ private void authenticate(UDPPacket packet, SessionKey cipherKey, SessionKey macKey) { - ByteArray iv = _ivCache.acquire(); - _context.random().nextBytes(iv.getData()); + byte[] iv = SimpleByteCache.acquire(UDPPacket.IV_SIZE); + _context.random().nextBytes(iv); authenticate(packet, cipherKey, macKey, iv); - _ivCache.release(iv); + SimpleByteCache.release(iv); } /** @@ -1308,38 +1305,38 @@ class PacketBuilder { * @param macKey key to generate the, er, MAC * @param iv IV to deliver */ - private void authenticate(UDPPacket packet, SessionKey cipherKey, SessionKey macKey, ByteArray iv) { + private void authenticate(UDPPacket packet, SessionKey cipherKey, SessionKey macKey, byte[] iv) { long before = System.currentTimeMillis(); int encryptOffset = packet.getPacket().getOffset() + UDPPacket.IV_SIZE + UDPPacket.MAC_SIZE; int encryptSize = packet.getPacket().getLength() - UDPPacket.IV_SIZE - UDPPacket.MAC_SIZE - packet.getPacket().getOffset(); byte data[] = packet.getPacket().getData(); - _context.aes().encrypt(data, encryptOffset, data, encryptOffset, cipherKey, iv.getData(), encryptSize); + _context.aes().encrypt(data, encryptOffset, data, encryptOffset, cipherKey, iv, encryptSize); // ok, now we need to prepare things for the MAC, which requires reordering int off = packet.getPacket().getOffset(); System.arraycopy(data, encryptOffset, data, off, encryptSize); off += encryptSize; - System.arraycopy(iv.getData(), 0, data, off, UDPPacket.IV_SIZE); + System.arraycopy(iv, 0, data, off, UDPPacket.IV_SIZE); off += UDPPacket.IV_SIZE; DataHelper.toLong(data, off, 2, encryptSize ^ PROTOCOL_VERSION); int hmacOff = packet.getPacket().getOffset(); int hmacLen = encryptSize + UDPPacket.IV_SIZE + 2; //Hash hmac = _context.hmac().calculate(macKey, data, hmacOff, hmacLen); - ByteArray ba = _hmacCache.acquire(); - _context.hmac().calculate(macKey, data, hmacOff, hmacLen, ba.getData(), 0); + byte[] ba = SimpleByteCache.acquire(Hash.HASH_LENGTH); + _context.hmac().calculate(macKey, data, hmacOff, hmacLen, ba, 0); if (_log.shouldLog(Log.DEBUG)) _log.debug("Authenticating " + packet.getPacket().getLength() + - "\nIV: " + Base64.encode(iv.getData()) + - "\nraw mac: " + Base64.encode(ba.getData()) + + "\nIV: " + Base64.encode(iv) + + "\nraw mac: " + Base64.encode(ba) + "\nMAC key: " + macKey); // ok, now lets put it back where it belongs... System.arraycopy(data, hmacOff, data, encryptOffset, encryptSize); //System.arraycopy(hmac.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE); - System.arraycopy(ba.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE); - System.arraycopy(iv.getData(), 0, data, hmacOff + UDPPacket.MAC_SIZE, UDPPacket.IV_SIZE); - _hmacCache.release(ba); + System.arraycopy(ba, 0, data, hmacOff, UDPPacket.MAC_SIZE); + System.arraycopy(iv, 0, data, hmacOff + UDPPacket.MAC_SIZE, UDPPacket.IV_SIZE); + SimpleByteCache.release(ba); long timeToAuth = System.currentTimeMillis() - before; _context.statManager().addRateData("udp.packetAuthTime", timeToAuth, timeToAuth); if (timeToAuth > 100) diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index e938599499857ad12a6e2845c346cfdfd938ae5e..46557950b9c5c021c064903fbff04b5b3c66dffb 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -16,6 +16,7 @@ import net.i2p.data.Hash; import net.i2p.data.SessionKey; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; +import net.i2p.router.util.CoDelPriorityBlockingQueue; import net.i2p.util.Log; import net.i2p.util.ConcurrentHashSet; @@ -188,8 +189,19 @@ class PeerState { /** list of InboundMessageState for active message */ private final Map<Long, InboundMessageState> _inboundMessages; - /** list of OutboundMessageState */ + + /** + * Mostly messages that have been transmitted and are awaiting acknowledgement, + * although there could be some that have not been sent yet. + */ private final List<OutboundMessageState> _outboundMessages; + + /** + * Priority queue of messages that have not yet been sent. + * They are taken from here and put in _outboundMessages. + */ + private final CoDelPriorityBlockingQueue<OutboundMessageState> _outboundQueue; + /** which outbound message is currently being retransmitted */ private OutboundMessageState _retransmitter; @@ -298,6 +310,7 @@ class PeerState { _rttDeviation = _rtt; _inboundMessages = new HashMap(8); _outboundMessages = new ArrayList(32); + _outboundQueue = new CoDelPriorityBlockingQueue(ctx, "UDP-PeerState", 32); // all createRateStat() moved to EstablishmentManager _remoteIP = remoteIP; _remotePeer = remotePeer; @@ -726,8 +739,8 @@ class PeerState { public List<Long> getCurrentFullACKs() { // no such element exception seen here List<Long> rv = new ArrayList(_currentACKs); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Returning " + _currentACKs.size() + " current acks"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Returning " + _currentACKs.size() + " current acks"); return rv; } @@ -748,8 +761,8 @@ class PeerState { public List<Long> getCurrentResendACKs() { List<Long> randomResends = new ArrayList(_currentACKsResend); Collections.shuffle(randomResends, _context.random()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Returning " + randomResends.size() + " resend acks"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Returning " + randomResends.size() + " resend acks"); return randomResends; } @@ -1194,24 +1207,26 @@ class PeerState { * TODO priority queue? (we don't implement priorities in SSU now) * TODO backlog / pushback / block instead of dropping? Can't really block here. * TODO SSU does not support isBacklogged() now - * @return total pending messages */ - public int add(OutboundMessageState state) { + public void add(OutboundMessageState state) { if (_dead) { _transport.failed(state, false); - return 0; + return; } state.setPeer(this); if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding to " + _remotePeer + ": " + state.getMessageId()); int rv = 0; - boolean fail = false; + // will never fail for CDPQ + boolean fail = !_outboundQueue.offer(state); +/**** synchronized (_outboundMessages) { rv = _outboundMessages.size() + 1; if (rv > MAX_SEND_MSGS_PENDING) { // too many queued messages to one peer? nuh uh. fail = true; rv--; +****/ /******* proactive tail drop disabled by jr 2006-04-19 so all this is pointless @@ -1250,17 +1265,17 @@ class PeerState { } *******/ - +/**** } else { _outboundMessages.add(state); } } +****/ if (fail) { if (_log.shouldLog(Log.WARN)) _log.warn("Dropping msg, OB queue full for " + toString()); _transport.failed(state, false); } - return rv; } /** drop all outbound messages */ @@ -1268,19 +1283,17 @@ class PeerState { //if (_dead) return; _dead = true; //_outboundMessages = null; - _retransmitter = null; - int sz = 0; - List<OutboundMessageState> tempList = null; + List<OutboundMessageState> tempList; synchronized (_outboundMessages) { - sz = _outboundMessages.size(); - if (sz > 0) { + _retransmitter = null; tempList = new ArrayList(_outboundMessages); _outboundMessages.clear(); - } } - for (int i = 0; i < sz; i++) - _transport.failed(tempList.get(i), false); + _outboundQueue.drainAllTo(tempList); + for (OutboundMessageState oms : tempList) { + _transport.failed(oms, false); + } // so the ACKSender will drop this peer from its queue _wantACKSendSince = -1; @@ -1291,7 +1304,7 @@ class PeerState { */ public int getOutboundMessageCount() { if (_dead) return 0; - return _outboundMessages.size(); + return _outboundMessages.size() + _outboundQueue.size(); } /** @@ -1305,7 +1318,7 @@ class PeerState { public int finishMessages() { // short circuit, unsynchronized if (_outboundMessages.isEmpty()) - return 0; + return _outboundQueue.size(); if (_dead) { dropOutbound(); @@ -1367,7 +1380,7 @@ class PeerState { state.releaseResources(); } - return rv; + return rv + _outboundQueue.size(); } /** @@ -1387,7 +1400,7 @@ class PeerState { ShouldSend should = locked_shouldSend(state); if (should == ShouldSend.YES) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Allocate sending to " + _remotePeer + ": " + state.getMessageId()); + _log.debug("Allocate sending (OLD) to " + _remotePeer + ": " + state.getMessageId()); /* while (iter.hasNext()) { OutboundMessageState later = (OutboundMessageState)iter.next(); @@ -1402,16 +1415,37 @@ class PeerState { // we don't bother looking for a smaller msg that would fit. // By not looking further, we keep strict sending order, and that allows // some efficiency in acked() below. - break; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Nothing to send (BW) to " + _remotePeer + ", with " + _outboundMessages.size() + + " / " + _outboundQueue.size() + " remaining"); + return null; } /* else { OutNetMessage msg = state.getMessage(); if (msg != null) msg.timestamp("passed over for allocation with " + msgs.size() + " peers"); } */ } + // Peek at head of _outboundQueue and see if we can send it. + // If so, pull it off, put it in _outbundMessages, test + // again for bandwidth if necessary, and return it. + OutboundMessageState state = _outboundQueue.peek(); + if (state != null && ShouldSend.YES == locked_shouldSend(state)) { + // we could get a different state, or null, when we poll, + // due to AQM drops, so we test again if necessary + OutboundMessageState dequeuedState = _outboundQueue.poll(); + if (dequeuedState != null) { + _outboundMessages.add(dequeuedState); + if (dequeuedState == state || ShouldSend.YES == locked_shouldSend(dequeuedState)) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Allocate sending (NEW) to " + _remotePeer + ": " + dequeuedState.getMessageId()); + return dequeuedState; + } + } + } } if (_log.shouldLog(Log.DEBUG)) - _log.debug("Nothing to send to " + _remotePeer + ", with " + _outboundMessages.size() + " remaining"); + _log.debug("Nothing to send to " + _remotePeer + ", with " + _outboundMessages.size() + + " / " + _outboundQueue.size() + " remaining"); return null; } @@ -1441,9 +1475,19 @@ class PeerState { rv = delay; } } + // failsafe... is this OK? + if (rv > 100 && !_outboundQueue.isEmpty()) + rv = 100; return rv; } + /** + * @since 0.9.3 + */ + public boolean isBacklogged() { + return _dead || _outboundQueue.isBacklogged(); + } + /** * If set to true, we should throttle retransmissions of all but the first message in * flight to a peer. If set to false, we will only throttle the initial flight of a @@ -1521,8 +1565,8 @@ class PeerState { int size = state.getUnackedSize(); if (allocateSendingBytes(size, state.getPushCount())) { - if (_log.shouldLog(Log.INFO)) - _log.info("Allocation of " + size + " allowed with " + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Allocation of " + size + " allowed with " + getSendWindowBytesRemaining() + "/" + getSendWindowBytes() + " remaining" @@ -1566,7 +1610,7 @@ class PeerState { /** * A full ACK was received. - * TODO if messages awaiting ack were a HashSet this would be faster. + * TODO if messages awaiting ack were a HashMap<Long, OutboundMessageState> this would be faster. * * @return true if the message was acked for the first time */ @@ -1620,8 +1664,8 @@ class PeerState { state.releaseResources(); } else { // dupack, likely - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Received an ACK for a message not pending: " + messageId); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Received an ACK for a message not pending: " + messageId); } return state != null; } @@ -1767,6 +1811,14 @@ class PeerState { } } + /** + * Convenience for OutboundMessageState so it can fail itself + * @since 0.9.3 + */ + public UDPTransport getTransport() { + return _transport; + } + // why removed? Some risk of dups in OutboundMessageFragments._activePeers ??? /* diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index 4258028d0ab8b591d0481d1e0e895bf388c8fee8..2623c07cc4139d069aa91dfec0d3b29b97672eb1 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; import net.i2p.data.SessionKey; import net.i2p.router.CommSystemFacade; @@ -584,7 +585,13 @@ class PeerTestManager { aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]); testInfo.readIntroKey(aliceIntroKey.getData(), 0); - UDPAddress addr = new UDPAddress(charlieInfo.getTargetAddress(UDPTransport.STYLE)); + RouterAddress raddr = charlieInfo.getTargetAddress(UDPTransport.STYLE); + if (raddr == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to pick a charlie"); + return; + } + UDPAddress addr = new UDPAddress(raddr); SessionKey charlieIntroKey = new SessionKey(addr.getIntroKey()); //UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, charlieIntroKey, nonce); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index 0305b0516aaed255fde7a22451d3f66a425d6799..5381bfaea4c83a11d399f02a4e07dc6171a992cf 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -12,9 +12,9 @@ import net.i2p.data.SessionKey; * FIXME public for ConfigNetHelper */ public class UDPAddress { - private String _host; + private final String _host; private InetAddress _hostAddress; - private int _port; + private final int _port; private byte[] _introKey; private String _introHosts[]; private InetAddress _introAddresses[]; @@ -23,8 +23,8 @@ public class UDPAddress { private long _introTags[]; private int _mtu; - public static final String PROP_PORT = "port"; - public static final String PROP_HOST = "host"; + public static final String PROP_PORT = RouterAddress.PROP_PORT; + public static final String PROP_HOST = RouterAddress.PROP_HOST; public static final String PROP_INTRO_KEY = "key"; public static final String PROP_MTU = "mtu"; @@ -40,16 +40,13 @@ public class UDPAddress { public UDPAddress(RouterAddress addr) { // TODO make everything final - if (addr == null) return; - _host = addr.getOption(PROP_HOST); - if (_host != null) _host = _host.trim(); - try { - String port = addr.getOption(PROP_PORT); - if (port != null) - _port = Integer.parseInt(port); - } catch (NumberFormatException nfe) { - _port = -1; + if (addr == null) { + _host = null; + _port = 0; + return; } + _host = addr.getOption(PROP_HOST); + _port = addr.getPort(); try { String mtu = addr.getOption(PROP_MTU); if (mtu != null) @@ -146,7 +143,7 @@ public class UDPAddress { } /** - * @return 0 if unset; -1 if invalid + * @return 0 if unset or invalid */ public int getPort() { return _port; } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java index 2f9f9620a38a275d8804a4f54bc79e1147a7f903..958a6d4c9e1582e966cbaf5eb31c5e68b7a09144 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java @@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; +import net.i2p.router.util.CDQEntry; import net.i2p.util.Log; /** @@ -16,7 +17,7 @@ import net.i2p.util.Log; * of object instances to allow rapid reuse. * */ -class UDPPacket { +class UDPPacket implements CDQEntry { private I2PAppContext _context; private final DatagramPacket _packet; private volatile short _priority; @@ -246,8 +247,12 @@ class UDPPacket { _context.aes().decrypt(_data, _packet.getOffset() + MAC_SIZE + IV_SIZE, _data, _packet.getOffset() + MAC_SIZE + IV_SIZE, cipherKey, _ivBuf, len - MAC_SIZE - IV_SIZE); } - /** the UDPReceiver has tossed it onto the inbound queue */ - void enqueue() { _enqueueTime = _context.clock().now(); } + /** + * For CDQ + * @since 0.9.3 + */ + public void setEnqueueTime(long now) { _enqueueTime = now; } + /** a packet handler has pulled it off the inbound queue */ void received() { _receivedTime = _context.clock().now(); } @@ -256,8 +261,11 @@ class UDPPacket { /** a packet handler has finished parsing out the good bits */ //void afterHandling() { _afterHandlingTime = _context.clock().now(); } - /** the UDPReceiver has tossed it onto the inbound queue */ - //long getTimeSinceEnqueue() { return (_enqueueTime > 0 ? _context.clock().now() - _enqueueTime : 0); } + /** + * For CDQ + * @since 0.9.3 + */ + public long getEnqueueTime() { return _enqueueTime; } /** a packet handler has pulled it off the inbound queue */ long getTimeSinceReceived() { return (_receivedTime > 0 ? _context.clock().now() - _receivedTime : 0); } @@ -269,8 +277,6 @@ class UDPPacket { // Following 5: All used only for stats in PacketHandler, commented out - /** when it was added to the endpoint's receive queue */ - //long getEnqueueTime() { return _enqueueTime; } /** when it was pulled off the endpoint receive queue */ //long getReceivedTime() { return _receivedTime; } /** when we began validate() */ @@ -326,6 +332,14 @@ class UDPPacket { return rv; } + /** + * For CDQ + * @since 0.9.3 + */ + public void drop() { + release(); + } + public void release() { verifyNotReleased(); _released = true; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index 4500680eab9df75983ed1d5914bb7945a1c03cd5..09fb43822c787e5facf8691cde4b0aedc9a6183d 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -4,10 +4,10 @@ import java.io.IOException; import java.net.DatagramSocket; import java.util.Arrays; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import net.i2p.router.RouterContext; import net.i2p.router.transport.FIFOBandwidthLimiter; +import net.i2p.router.util.CoDelBlockingQueue; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; @@ -47,7 +47,7 @@ class UDPReceiver { if (maxMemory == Long.MAX_VALUE) maxMemory = 96*1024*1024l; int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024))); - _inboundQueue = new LinkedBlockingQueue(qsize); + _inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize); _socket = socket; _transport = transport; _runner = new Runner(); @@ -177,6 +177,7 @@ class UDPReceiver { return 0; } +/**** packet.enqueue(); boolean rejected = false; int queueSize = 0; @@ -190,6 +191,7 @@ class UDPReceiver { } } if (!rejected) { +****/ try { _inboundQueue.put(packet); } catch (InterruptedException ie) { @@ -198,6 +200,7 @@ class UDPReceiver { } //return queueSize + 1; return 0; +/**** } // rejected @@ -214,6 +217,7 @@ class UDPReceiver { _log.warn(msg.toString()); } return queueSize; +****/ } /**** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java index f8eaaf5f85c939b0ba8fe29bbcfe9bc33a346827..88bdfb5b9857c2676712345682815957eece4e80 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java @@ -4,10 +4,10 @@ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import net.i2p.router.RouterContext; import net.i2p.router.transport.FIFOBandwidthLimiter; +import net.i2p.router.util.CoDelBlockingQueue; import net.i2p.util.I2PThread; import net.i2p.util.Log; @@ -35,7 +35,7 @@ class UDPSender { if (maxMemory == Long.MAX_VALUE) maxMemory = 96*1024*1024l; int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (1024*1024))); - _outboundQueue = new LinkedBlockingQueue(qsize); + _outboundQueue = new CoDelBlockingQueue(ctx, "UDP-Sender", qsize); _socket = socket; _runner = new Runner(); _name = name; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index 20f08eb412a7c8f6aaa8823eadcc8bcb9598870c..f738c210404ade0d3271c5bf197e9b655d2af953 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -42,6 +42,7 @@ import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; import net.i2p.util.Translate; /** @@ -369,7 +370,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority // _flooder.startup(); _expireEvent.setIsAlive(true); _testEvent.setIsAlive(true); // this queues it for 3-6 minutes in the future... - SimpleTimer.getInstance().addEvent(_testEvent, 10*1000); // lets requeue it for Real Soon + _testEvent.reschedule(10*1000); // lets requeue it for Real Soon } public void shutdown() { @@ -681,7 +682,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _context.router().rebuildRouterInfo(); } _testEvent.forceRun(); - SimpleTimer.getInstance().addEvent(_testEvent, 5*1000); + _testEvent.reschedule(5*1000); return updated; } @@ -859,7 +860,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (getReachabilityStatus() != CommSystemFacade.STATUS_OK) { _testEvent.forceRun(); - SimpleTimer.getInstance().addEvent(_testEvent, 0); + _testEvent.reschedule(0); } return true; } @@ -933,7 +934,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority } private class RemoveDropList implements SimpleTimer.TimedEvent { - private RemoteHostId _peer; + private final RemoteHostId _peer; public RemoveDropList(RemoteHostId peer) { _peer = peer; } public void timeReached() { _dropList.remove(_peer); @@ -1202,27 +1203,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority //UDPAddress ua = new UDPAddress(addr); //if (ua.getIntroducerCount() <= 0) { if (addr.getOption("ihost0") == null) { - String host = addr.getOption(UDPAddress.PROP_HOST); - String port = addr.getOption(UDPAddress.PROP_PORT); - if (host == null || port == null) { + byte[] ip = addr.getIP(); + int port = addr.getPort(); + if (ip == null || port <= 0 || + (!isValid(ip)) || + Arrays.equals(ip, getExternalIP())) { markUnreachable(to); return null; } - try { - InetAddress ia = InetAddress.getByName(host); - int iport = Integer.parseInt(port); - if (iport <= 0 || iport > 65535 || (!isValid(ia.getAddress())) || - Arrays.equals(ia.getAddress(), getExternalIP())) { - markUnreachable(to); - return null; - } - } catch (UnknownHostException uhe) { - markUnreachable(to); - return null; - } catch (NumberFormatException nfe) { - markUnreachable(to); - return null; - } } if (!allowConnection()) return _cachedBid[TRANSIENT_FAIL_BID]; @@ -1337,6 +1325,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _fragments.add(msg); } + /** + * "injected" message from the EstablishmentManager + */ void send(I2NPMessage msg, PeerState peer) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Injecting a data message to a new peer: " + peer); @@ -1446,7 +1437,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority addr.setCost(DEFAULT_COST + 1); else addr.setCost(DEFAULT_COST); - addr.setExpiration(null); + //addr.setExpiration(null); addr.setTransportStyle(STYLE); addr.setOptions(options); @@ -1687,7 +1678,17 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return getPeerState(dest) != null; } + /** + * @since 0.9.3 + */ + @Override + public boolean isBacklogged(Hash dest) { + PeerState peer = _peersByIdent.get(dest); + return peer != null && peer.isBacklogged(); + } + public boolean allowConnection() { + return _peersByIdent.size() < getMaxConnections(); } @@ -2196,6 +2197,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority buf.append(THINSP).append(peer.getConcurrentSends()); buf.append(THINSP).append(peer.getConcurrentSendWindow()); buf.append(THINSP).append(peer.getConsecutiveSendRejections()); + if (peer.isBacklogged()) + buf.append(' ').append(_("backlogged")); buf.append("</td>"); buf.append("<td class=\"cells\" align=\"right\">"); @@ -2361,12 +2364,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public String toString() { return "UDP bid @ " + getLatencyMs(); } } - private class ExpirePeerEvent implements SimpleTimer.TimedEvent { + private class ExpirePeerEvent extends SimpleTimer2.TimedEvent { private final Set<PeerState> _expirePeers; private final List<PeerState> _expireBuffer; private volatile boolean _alive; public ExpirePeerEvent() { + super(_context.simpleTimer2()); _expirePeers = new ConcurrentHashSet(128); _expireBuffer = new ArrayList(); } @@ -2403,7 +2407,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _expireBuffer.clear(); if (_alive) - SimpleTimer.getInstance().addEvent(ExpirePeerEvent.this, 30*1000); + schedule(30*1000); } public void add(PeerState peer) { _expirePeers.add(peer); @@ -2414,9 +2418,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public void setIsAlive(boolean isAlive) { _alive = isAlive; if (isAlive) { - SimpleTimer.getInstance().addEvent(ExpirePeerEvent.this, 30*1000); + reschedule(30*1000); } else { - SimpleTimer.getInstance().removeEvent(ExpirePeerEvent.this); + cancel(); _expirePeers.clear(); } } @@ -2515,12 +2519,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority //return ( (val != null) && ("true".equals(val)) ); } - private class PeerTestEvent implements SimpleTimer.TimedEvent { + private class PeerTestEvent extends SimpleTimer2.TimedEvent { private volatile boolean _alive; /** when did we last test our reachability */ private long _lastTested; private boolean _forceRun; + PeerTestEvent() { + super(_context.simpleTimer2()); + } + public void timeReached() { if (shouldTest()) { long now = _context.clock().now(); @@ -2532,7 +2540,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority long delay = (TEST_FREQUENCY / 2) + _context.random().nextInt(TEST_FREQUENCY); if (delay <= 0) throw new RuntimeException("wtf, delay is " + delay); - SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay); + schedule(delay); } } @@ -2558,9 +2566,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _alive = isAlive; if (isAlive) { long delay = _context.random().nextInt(2*TEST_FREQUENCY); - SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay); + reschedule(delay); } else { - SimpleTimer.getInstance().removeEvent(PeerTestEvent.this); + cancel(); } } } diff --git a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java index c3e80b33b5934e4ed60b7458d5285e431878b049..ff7bdedf8fc3c67dccf934195f542080f58558a6 100644 --- a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java +++ b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java @@ -104,7 +104,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { /* See TunnelGateway.QueuePreprocessor for Javadoc */ @Override - public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { + public boolean preprocessQueue(List<PendingGatewayMessage> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { if (_log.shouldLog(Log.INFO)) display(0, pending, "Starting"); StringBuilder timingBuf = null; @@ -131,7 +131,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { // loop until we fill up a single message for (int i = 0; i < pending.size(); i++) { long pendingStart = System.currentTimeMillis(); - TunnelGateway.Pending msg = pending.get(i); + PendingGatewayMessage msg = pending.get(i); int instructionsSize = getInstructionsSize(msg); instructionsSize += getInstructionAugmentationSize(msg, allocated, instructionsSize); int curWanted = msg.getData().length - msg.getOffset() + instructionsSize; @@ -169,7 +169,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { // Remove what we sent from the pending queue for (int j = 0; j < i; j++) { - TunnelGateway.Pending cur = pending.remove(0); + PendingGatewayMessage cur = pending.remove(0); if (cur.getOffset() < cur.getData().length) throw new IllegalArgumentException("i=" + i + " j=" + j + " off=" + cur.getOffset() + " len=" + cur.getData().length + " alloc=" + allocated); @@ -181,7 +181,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { } if (msg.getOffset() >= msg.getData().length) { // ok, this last message fit perfectly, remove it too - TunnelGateway.Pending cur = pending.remove(0); + PendingGatewayMessage cur = pending.remove(0); if (timingBuf != null) timingBuf.append(" sent perfect fit " + cur).append("."); notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), msg.getData().length, msg.getMessageIds(), "flushed tail, remaining: " + pending); @@ -230,7 +230,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { // Remove everything in the outgoing message from the pending queue int beforeSize = pending.size(); for (int i = 0; i < beforeSize; i++) { - TunnelGateway.Pending cur = pending.get(0); + PendingGatewayMessage cur = pending.get(0); if (cur.getOffset() < cur.getData().length) break; pending.remove(0); @@ -316,7 +316,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { * * title: allocated: X pending: X (delay: X) [0]:offset/length/lifetime [1]:etc. */ - private void display(long allocated, List<TunnelGateway.Pending> pending, String title) { + private void display(long allocated, List<PendingGatewayMessage> pending, String title) { if (_log.shouldLog(Log.INFO)) { long highestDelay = 0; StringBuilder buf = new StringBuilder(128); @@ -327,7 +327,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { if (_pendingSince > 0) buf.append(" delay: ").append(getDelayAmount(false)); for (int i = 0; i < pending.size(); i++) { - TunnelGateway.Pending curPending = pending.get(i); + PendingGatewayMessage curPending = pending.get(i); buf.append(" [").append(i).append("]:"); buf.append(curPending.getOffset()).append('/').append(curPending.getData().length).append('/'); buf.append(curPending.getLifetime()); @@ -347,7 +347,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { * @param startAt first index in pending to send (inclusive) * @param sendThrough last index in pending to send (inclusive) */ - protected void send(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { + protected void send(List<PendingGatewayMessage> pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending " + startAt + ":" + sendThrough + " out of " + pending); @@ -384,7 +384,7 @@ class BatchedPreprocessor extends TrivialPreprocessor { long msgId = sender.sendPreprocessed(preprocessed, rec); for (int i = 0; i < pending.size(); i++) { - TunnelGateway.Pending cur = pending.get(i); + PendingGatewayMessage cur = pending.get(i); cur.addMessageId(msgId); } if (_log.shouldLog(Log.DEBUG)) @@ -397,9 +397,9 @@ class BatchedPreprocessor extends TrivialPreprocessor { * * @return new offset into the target for further bytes to be written */ - private int writeFragments(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, byte target[], int offset) { + private int writeFragments(List<PendingGatewayMessage> pending, int startAt, int sendThrough, byte target[], int offset) { for (int i = startAt; i <= sendThrough; i++) { - TunnelGateway.Pending msg = pending.get(i); + PendingGatewayMessage msg = pending.get(i); int prevOffset = offset; if (msg.getOffset() == 0) { offset = writeFirstFragment(msg, target, offset); diff --git a/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java index 58944818ea120c6da4a91a02fb7847934e5614d8..857c7fdecd846f9988e98bf4901fb5ac73cdf07f 100644 --- a/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java +++ b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java @@ -5,7 +5,7 @@ import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.router.util.DecayingBloomFilter; import net.i2p.router.util.DecayingHashSet; -import net.i2p.util.ByteCache; +import net.i2p.util.SimpleByteCache; /** * Manage the IV validation for all of the router's tunnels by way of a big @@ -15,7 +15,6 @@ import net.i2p.util.ByteCache; class BloomFilterIVValidator implements IVValidator { private final RouterContext _context; private final DecayingBloomFilter _filter; - private final ByteCache _ivXorCache = ByteCache.getInstance(32, HopProcessor.IV_LENGTH); /** * After 2*halflife, an entry is completely forgotten from the bloom filter. @@ -57,10 +56,10 @@ class BloomFilterIVValidator implements IVValidator { } public boolean receiveIV(byte ivData[], int ivOffset, byte payload[], int payloadOffset) { - ByteArray buf = _ivXorCache.acquire(); - DataHelper.xor(ivData, ivOffset, payload, payloadOffset, buf.getData(), 0, HopProcessor.IV_LENGTH); - boolean dup = _filter.add(buf.getData()); - _ivXorCache.release(buf); + byte[] buf = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); + DataHelper.xor(ivData, ivOffset, payload, payloadOffset, buf, 0, HopProcessor.IV_LENGTH); + boolean dup = _filter.add(buf); + SimpleByteCache.release(buf); if (dup) _context.statManager().addRateData("tunnel.duplicateIV", 1); return !dup; // return true if it is OK, false if it isn't } diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java index d24ee38dac3e9a82fd6ed9f6237a0a0e33938efc..f951e56f0bd61a228c30cbe309083403649b2ef6 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java @@ -16,7 +16,7 @@ import net.i2p.util.ByteCache; import net.i2p.util.HexDump; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; /** * Handle fragments at the endpoint of a tunnel, peeling off fully completed @@ -369,7 +369,7 @@ class FragmentHandler { _fragmentedMessages.remove(Long.valueOf(messageId)); } if (msg.getExpireEvent() != null) - SimpleTimer.getInstance().removeEvent(msg.getExpireEvent()); + msg.getExpireEvent().cancel(); receiveComplete(msg); } else { noteReception(msg.getMessageId(), 0, msg); @@ -378,7 +378,7 @@ class FragmentHandler { msg.setExpireEvent(evt); if (_log.shouldLog(Log.DEBUG)) _log.debug("In " + MAX_DEFRAGMENT_TIME + " dropping " + messageId); - SimpleTimer.getInstance().addEvent(evt, MAX_DEFRAGMENT_TIME); + evt.schedule(MAX_DEFRAGMENT_TIME); } } } @@ -437,7 +437,7 @@ class FragmentHandler { _fragmentedMessages.remove(Long.valueOf(messageId)); } if (msg.getExpireEvent() != null) - SimpleTimer.getInstance().removeEvent(msg.getExpireEvent()); + msg.getExpireEvent().cancel(); _context.statManager().addRateData("tunnel.fragmentedComplete", msg.getFragmentCount(), msg.getLifetime()); receiveComplete(msg); } else { @@ -447,7 +447,7 @@ class FragmentHandler { msg.setExpireEvent(evt); if (_log.shouldLog(Log.DEBUG)) _log.debug("In " + MAX_DEFRAGMENT_TIME + " dropping " + msg.getMessageId() + "/" + fragmentNum); - SimpleTimer.getInstance().addEvent(evt, MAX_DEFRAGMENT_TIME); + evt.schedule(MAX_DEFRAGMENT_TIME); } } } @@ -548,10 +548,11 @@ class FragmentHandler { public void receiveComplete(I2NPMessage msg, Hash toRouter, TunnelId toTunnel); } - private class RemoveFailed implements SimpleTimer.TimedEvent { + private class RemoveFailed extends SimpleTimer2.TimedEvent { private final FragmentedMessage _msg; public RemoveFailed(FragmentedMessage msg) { + super(_context.simpleTimer2()); _msg = msg; } diff --git a/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java b/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java index 592cc0e9a2af72cfedd007862df30496380c66d8..2a0c59e9134803a8585b995712d6f87bc146e486 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentedMessage.java @@ -7,7 +7,7 @@ import net.i2p.data.Hash; import net.i2p.data.TunnelId; import net.i2p.util.ByteCache; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; /** * Gather fragments of I2NPMessages at a tunnel endpoint, making them available @@ -28,7 +28,7 @@ class FragmentedMessage { private final long _createdOn; private boolean _completed; private long _releasedAfter; - private SimpleTimer.TimedEvent _expireEvent; + private SimpleTimer2.TimedEvent _expireEvent; private static final ByteCache _cache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE); // 64 is pretty absurd, 32 is too, most likely @@ -160,9 +160,11 @@ class FragmentedMessage { found++; return found; } + /** used in the fragment handler so we can cancel the expire event on success */ - SimpleTimer.TimedEvent getExpireEvent() { return _expireEvent; } - void setExpireEvent(SimpleTimer.TimedEvent evt) { _expireEvent = evt; } + public SimpleTimer2.TimedEvent getExpireEvent() { return _expireEvent; } + + public void setExpireEvent(SimpleTimer2.TimedEvent evt) { _expireEvent = evt; } /** have we received all of the fragments? */ public boolean isComplete() { diff --git a/router/java/src/net/i2p/router/tunnel/HopProcessor.java b/router/java/src/net/i2p/router/tunnel/HopProcessor.java index 792288a773188ad130cb51a994d8e868235498f9..386f96d798709d6fa41cd90d374b489b69e6dc50 100644 --- a/router/java/src/net/i2p/router/tunnel/HopProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/HopProcessor.java @@ -2,7 +2,6 @@ package net.i2p.router.tunnel; import net.i2p.I2PAppContext; import net.i2p.data.Hash; -import net.i2p.util.ByteCache; import net.i2p.util.Log; /** @@ -29,7 +28,6 @@ class HopProcessor { */ static final boolean USE_DOUBLE_IV_ENCRYPTION = true; static final int IV_LENGTH = 16; - private static final ByteCache _cache = ByteCache.getInstance(128, IV_LENGTH); /** @deprecated unused */ public HopProcessor(I2PAppContext ctx, HopConfig config) { diff --git a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java index bd362055b6f8a7af6c18467e9dcd67358132c625..2fd0b26c07ff9792ec12f7ff11ab111d5a85bf5c 100644 --- a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java @@ -3,8 +3,8 @@ package net.i2p.router.tunnel; import net.i2p.data.ByteArray; import net.i2p.data.Hash; import net.i2p.router.RouterContext; -import net.i2p.util.ByteCache; import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; /** * Receive the inbound tunnel message, removing all of the layers @@ -21,7 +21,6 @@ class InboundEndpointProcessor { private final IVValidator _validator; static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION; - private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH); /** @deprecated unused */ public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg) { @@ -54,8 +53,7 @@ class InboundEndpointProcessor { return false; } - ByteArray ba = _cache.acquire(); - byte iv[] = ba.getData(); //new byte[HopProcessor.IV_LENGTH]; + byte iv[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); System.arraycopy(orig, offset, iv, 0, iv.length); //if (_config.getLength() > 1) // _log.debug("IV at inbound endpoint before decrypt: " + Base64.encode(iv)); @@ -64,7 +62,7 @@ class InboundEndpointProcessor { if (!ok) { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid IV, dropping at IBEP " + _config); - _cache.release(ba); + SimpleByteCache.release(iv); return false; } @@ -72,7 +70,7 @@ class InboundEndpointProcessor { if (USE_ENCRYPTION) decrypt(_context, _config, iv, orig, offset, length); - _cache.release(ba); + SimpleByteCache.release(iv); if (_config.getLength() > 0) { int rtt = 0; // dunno... may not be related to an rtt @@ -91,8 +89,7 @@ class InboundEndpointProcessor { */ private void decrypt(RouterContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) { //Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class); - ByteArray ba = _cache.acquire(); - byte cur[] = ba.getData(); // new byte[HopProcessor.IV_LENGTH]; // so we dont malloc + byte cur[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); for (int i = cfg.getLength()-2; i >= 0; i--) { // dont include the endpoint, since that is the creator OutboundGatewayProcessor.decrypt(ctx, iv, orig, offset, length, cur, cfg.getConfig(i)); //if (log.shouldLog(Log.DEBUG)) { @@ -100,7 +97,7 @@ class InboundEndpointProcessor { //log.debug("hop " + i + ": " + Base64.encode(orig, offset + HopProcessor.IV_LENGTH, length - HopProcessor.IV_LENGTH)); //} } - _cache.release(ba); + SimpleByteCache.release(cur); } } diff --git a/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java b/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java index 1131808c39dbedf1b5d8d179f58d317b51ee6204..6cca9171a4b303982667e6efc16895af46cc535d 100644 --- a/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java +++ b/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java @@ -15,6 +15,7 @@ class InboundGatewayReceiver implements TunnelGateway.Receiver { private RouterInfo _target; private static final long MAX_LOOKUP_TIME = 15*1000; + private static final int PRIORITY = OutNetMessage.PRIORITY_PARTICIPATING; public InboundGatewayReceiver(RouterContext ctx, HopConfig cfg) { _context = ctx; @@ -58,7 +59,7 @@ class InboundGatewayReceiver implements TunnelGateway.Receiver { out.setMessage(msg); out.setTarget(_target); out.setExpiration(msg.getMessageExpiration()); - out.setPriority(200); + out.setPriority(PRIORITY); _context.outNetMessagePool().add(out); return msg.getUniqueId(); } diff --git a/router/java/src/net/i2p/router/tunnel/OutboundGatewayMessage.java b/router/java/src/net/i2p/router/tunnel/OutboundGatewayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..b3fe4e7a51d71b1c0fa7d614d6330cd784db3568 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundGatewayMessage.java @@ -0,0 +1,91 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.*; +import net.i2p.router.util.CDPQEntry; + +/** + * Stores all the state for an unsent or partially-sent message + * + * @since 0.9.3 + */ +class OutboundGatewayMessage extends PendingGatewayMessage implements CDPQEntry { + private long _seqNum; + private final int _priority; + + public OutboundGatewayMessage(I2NPMessage message, Hash toRouter, TunnelId toTunnel) { + super(message, toRouter, toTunnel); + _priority = getPriority(message); + } + + /** + * For CDPQ + */ + public void setSeqNum(long num) { + _seqNum = num; + } + + /** + * For CDPQ + */ + public long getSeqNum() { + return _seqNum; + } + + /** + * For CDPQ + */ + public int getPriority() { + return _priority; + } + + /** + * This is just for priority in the queue waiting for the fragmenter. + * After the fragmenter, they will be OutNetMessages with priority 400. + * We use the same 100-500 priority as OutNetMessage so the stats + * in CoDelPriorityBlockingQueue work. + * + * We could - perhaps - have BatchedPreprocessor pass the max priority of + * any message fragment in a TunnelDataMessage to the OutboundReceiver, to + * set the OutNetMessage priority - but that may just make more of an + * out-of-order mess and failed reconstruction of fragments. + */ + private static int getPriority(I2NPMessage message) { + switch (message.getType()) { + + // tagset/LS reply + case DeliveryStatusMessage.MESSAGE_TYPE: + return 1000; + + // building new IB tunnel + case TunnelBuildMessage.MESSAGE_TYPE: + case VariableTunnelBuildMessage.MESSAGE_TYPE: + return 500; + + // LS store + case DatabaseStoreMessage.MESSAGE_TYPE: + return 400; + + // LS verify + case DatabaseLookupMessage.MESSAGE_TYPE: + return 300; + + // regular data + case GarlicMessage.MESSAGE_TYPE: + return 200; + + // these shouldn't go into a OBGW + case DatabaseSearchReplyMessage.MESSAGE_TYPE: + case DataMessage.MESSAGE_TYPE: + case TunnelBuildReplyMessage.MESSAGE_TYPE: + case TunnelDataMessage.MESSAGE_TYPE: + case TunnelGatewayMessage.MESSAGE_TYPE: + case VariableTunnelBuildReplyMessage.MESSAGE_TYPE: + default: + return 100; + + } + } +} + diff --git a/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java index 4deb3bed173881d94f5eaedc7f8a8cd68a522a66..3d86d230ff75b27e70690bfc1c434bcfffcd81aa 100644 --- a/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java @@ -3,8 +3,8 @@ package net.i2p.router.tunnel; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.ByteArray; -import net.i2p.util.ByteCache; import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; /** * Turn the preprocessed tunnel data into something that can be delivered to the @@ -18,7 +18,6 @@ class OutboundGatewayProcessor { private final TunnelCreatorConfig _config; static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION; - private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH); public OutboundGatewayProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg) { _context = ctx; @@ -35,8 +34,7 @@ class OutboundGatewayProcessor { * @param length how much of orig can we write to (must be a multiple of 16). */ public void process(byte orig[], int offset, int length) { - ByteArray ba = _cache.acquire(); - byte iv[] = ba.getData(); // new byte[HopProcessor.IV_LENGTH]; + byte iv[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); //_context.random().nextBytes(iv); //System.arraycopy(iv, 0, orig, offset, HopProcessor.IV_LENGTH); System.arraycopy(orig, offset, iv, 0, HopProcessor.IV_LENGTH); @@ -49,7 +47,7 @@ class OutboundGatewayProcessor { decrypt(_context, _config, iv, orig, offset, length); if (_log.shouldLog(Log.DEBUG)) _log.debug("finished processing the preprocessed data"); - _cache.release(ba); + SimpleByteCache.release(iv); } /** @@ -58,8 +56,7 @@ class OutboundGatewayProcessor { */ private void decrypt(I2PAppContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) { Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class); - ByteArray ba = _cache.acquire(); - byte cur[] = ba.getData(); // new byte[HopProcessor.IV_LENGTH]; // so we dont malloc + byte cur[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); for (int i = cfg.getLength()-1; i >= 1; i--) { // dont include hop 0, since that is the creator decrypt(ctx, iv, orig, offset, length, cur, cfg.getConfig(i)); if (log.shouldLog(Log.DEBUG)) { @@ -67,7 +64,7 @@ class OutboundGatewayProcessor { //log.debug("hop " + i + ": " + Base64.encode(orig, offset + HopProcessor.IV_LENGTH, length - HopProcessor.IV_LENGTH)); } } - _cache.release(ba); + SimpleByteCache.release(cur); } /** diff --git a/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java b/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java index 28f0c54368e7ca6af43ead64f78fe6a878860826..abcb5a74248b6f8b0aea6531adc6263295f2b2a7 100644 --- a/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java +++ b/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java @@ -20,6 +20,7 @@ class OutboundReceiver implements TunnelGateway.Receiver { private RouterInfo _nextHopCache; private static final long MAX_LOOKUP_TIME = 15*1000; + private static final int PRIORITY = OutNetMessage.PRIORITY_MY_DATA; public OutboundReceiver(RouterContext ctx, TunnelCreatorConfig cfg) { _context = ctx; @@ -61,7 +62,7 @@ class OutboundReceiver implements TunnelGateway.Receiver { m.setMessage(msg); m.setExpiration(msg.getMessageExpiration()); m.setTarget(ri); - m.setPriority(400); + m.setPriority(PRIORITY); _context.outNetMessagePool().add(m); _config.incrementProcessedMessages(); } diff --git a/router/java/src/net/i2p/router/tunnel/PendingGatewayMessage.java b/router/java/src/net/i2p/router/tunnel/PendingGatewayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..6e50e4e92f9b336d398a2b039e5f31a965c66020 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/PendingGatewayMessage.java @@ -0,0 +1,134 @@ +package net.i2p.router.tunnel; + +import java.util.ArrayList; +import java.util.List; + +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.RouterContext; +import net.i2p.router.util.CDQEntry; + +/** + * Stores all the state for an unsent or partially-sent message + * + * @since 0.9.3 refactored from TunnelGateway.Pending + */ +class PendingGatewayMessage implements CDQEntry { + protected final Hash _toRouter; + protected final TunnelId _toTunnel; + protected final long _messageId; + protected final long _expiration; + protected final byte _remaining[]; + protected int _offset; + protected int _fragmentNumber; + protected final long _created; + private List<Long> _messageIds; + private long _enqueueTime; + + public PendingGatewayMessage(I2NPMessage message, Hash toRouter, TunnelId toTunnel) { + _toRouter = toRouter; + _toTunnel = toTunnel; + _messageId = message.getUniqueId(); + _expiration = message.getMessageExpiration(); + _remaining = message.toByteArray(); + _created = System.currentTimeMillis(); + } + + /** may be null */ + public Hash getToRouter() { return _toRouter; } + + /** may be null */ + public TunnelId getToTunnel() { return _toTunnel; } + + public long getMessageId() { return _messageId; } + + public long getExpiration() { return _expiration; } + + /** raw unfragmented message to send */ + public byte[] getData() { return _remaining; } + + /** index into the data to be sent */ + public int getOffset() { return _offset; } + + /** move the offset */ + public void setOffset(int offset) { _offset = offset; } + + public long getLifetime() { return System.currentTimeMillis()-_created; } + + /** which fragment are we working on (0 for the first fragment) */ + public int getFragmentNumber() { return _fragmentNumber; } + + /** ok, fragment sent, increment what the next will be */ + public void incrementFragmentNumber() { _fragmentNumber++; } + + /** + * Add an ID to the list of the TunnelDataMssages this message was fragmented into. + * Unused except in notePreprocessing() calls for debugging + */ + public void addMessageId(long id) { + synchronized (this) { + if (_messageIds == null) + _messageIds = new ArrayList(); + _messageIds.add(Long.valueOf(id)); + } + } + + /** + * The IDs of the TunnelDataMssages this message was fragmented into. + * Unused except in notePreprocessing() calls for debugging + */ + public List<Long> getMessageIds() { + synchronized (this) { + if (_messageIds != null) + return new ArrayList(_messageIds); + else + return new ArrayList(); + } + } + + /** + * For CDQ + * @since 0.9.3 + */ + public void setEnqueueTime(long now) { + _enqueueTime = now; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public long getEnqueueTime() { + return _enqueueTime; + } + + /** + * For CDQ + * @since 0.9.3 + */ + public void drop() { + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("Message ").append(_messageId); //.append(" on "); + //buf.append(TunnelGateway.this.toString()); + if (_toRouter != null) { + buf.append(" targetting "); + buf.append(_toRouter.toBase64()).append(" "); + if (_toTunnel != null) + buf.append(_toTunnel.getTunnelId()); + } + buf.append(" actual lifetime "); + buf.append(getLifetime()).append("ms"); + buf.append(" potential lifetime "); + buf.append(_expiration - _created).append("ms"); + buf.append(" size ").append(_remaining.length); + buf.append(" offset ").append(_offset); + buf.append(" frag ").append(_fragmentNumber); + return buf.toString(); + } +} + diff --git a/router/java/src/net/i2p/router/tunnel/PumpedTunnelGateway.java b/router/java/src/net/i2p/router/tunnel/PumpedTunnelGateway.java index ab4bd8f76b15029c718cfb85d515a88291da778c..488af9e7f694cb308ea7c1d4892e2f25f0aa958d 100644 --- a/router/java/src/net/i2p/router/tunnel/PumpedTunnelGateway.java +++ b/router/java/src/net/i2p/router/tunnel/PumpedTunnelGateway.java @@ -2,13 +2,14 @@ package net.i2p.router.tunnel; import java.util.List; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import net.i2p.data.Hash; import net.i2p.data.TunnelId; import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.util.CoDelBlockingQueue; +import net.i2p.router.util.CoDelPriorityBlockingQueue; import net.i2p.util.Log; /** @@ -35,9 +36,15 @@ import net.i2p.util.Log; * */ class PumpedTunnelGateway extends TunnelGateway { - private final BlockingQueue<Pending> _prequeue; + private final BlockingQueue<PendingGatewayMessage> _prequeue; private final TunnelGatewayPumper _pumper; + private final boolean _isInbound; + private static final int MAX_OB_MSGS_PER_PUMP = 16; + private static final int MAX_IB_MSGS_PER_PUMP = 8; + private static final int INITIAL_OB_QUEUE = 64; + private static final int MAX_IB_QUEUE = 1024; + /** * @param preprocessor this pulls Pending messages off a list, builds some * full preprocessed messages, and pumps those into the sender @@ -48,7 +55,15 @@ class PumpedTunnelGateway extends TunnelGateway { */ public PumpedTunnelGateway(RouterContext context, QueuePreprocessor preprocessor, Sender sender, Receiver receiver, TunnelGatewayPumper pumper) { super(context, preprocessor, sender, receiver); - _prequeue = new LinkedBlockingQueue(); + if (getClass() == PumpedTunnelGateway.class) { + // Unbounded priority queue for outbound + _prequeue = new CoDelPriorityBlockingQueue(context, "OBGW", INITIAL_OB_QUEUE); + _isInbound = false; + } else { // extended by ThrottledPTG for IB + // Bounded non-priority queue for inbound + _prequeue = new CoDelBlockingQueue(context, "IBGW", MAX_IB_QUEUE); + _isInbound = true; + } _pumper = pumper; } @@ -57,16 +72,26 @@ class PumpedTunnelGateway extends TunnelGateway { * coallesced with other pending messages) or after a brief pause (_flushFrequency). * If it is queued up past its expiration, it is silently dropped * + * This is only for OBGWs. See TPTG override for IBGWs. + * * @param msg message to be sent through the tunnel * @param toRouter router to send to after the endpoint (or null for endpoint processing) * @param toTunnel tunnel to send to after the endpoint (or null for endpoint or router processing) */ @Override public void add(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) { + OutboundGatewayMessage cur = new OutboundGatewayMessage(msg, toRouter, toTunnel); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("OB PTG add type " + msg.getType() + " pri " + cur.getPriority()); + add(cur); + } + + protected void add(PendingGatewayMessage cur) { _messagesSent++; - Pending cur = new PendingImpl(msg, toRouter, toTunnel); - _prequeue.offer(cur); - _pumper.wantsPumping(this); + if (_prequeue.offer(cur)) + _pumper.wantsPumping(this); + else + _context.statManager().addRateData("tunnel.dropGatewayOverflow", 1); } /** @@ -79,8 +104,12 @@ class PumpedTunnelGateway extends TunnelGateway { * @param queueBuf Empty list for convenience, to use as a temporary buffer. * Must be empty when called; will always be emptied before return. */ - void pump(List<Pending> queueBuf) { - _prequeue.drainTo(queueBuf); + void pump(List<PendingGatewayMessage> queueBuf) { + // TODO if an IBGW, and the next hop is backlogged, + // drain less or none... better to let things back up here. + // Don't do this for OBGWs? + int max = _isInbound ? MAX_IB_MSGS_PER_PUMP : MAX_OB_MSGS_PER_PUMP; + _prequeue.drainTo(queueBuf, max); if (queueBuf.isEmpty()) return; @@ -105,7 +134,7 @@ class PumpedTunnelGateway extends TunnelGateway { // expire any as necessary, even if its framented for (int i = 0; i < _queue.size(); i++) { - Pending m = _queue.get(i); + PendingGatewayMessage m = _queue.get(i); if (m.getExpiration() + Router.CLOCK_FUDGE_FACTOR < _lastFlush) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Expire on the queue (size=" + _queue.size() + "): " + m); @@ -120,18 +149,21 @@ class PumpedTunnelGateway extends TunnelGateway { } if (delayedFlush) { - _context.simpleTimer().addEvent(_delayedFlush, delayAmount); + _delayedFlush.reschedule(delayAmount); } - _context.statManager().addRateData("tunnel.lockedGatewayAdd", afterAdded-beforeLock, remaining); - long complete = System.currentTimeMillis(); - if (_log.shouldLog(Log.DEBUG)) + //_context.statManager().addRateData("tunnel.lockedGatewayAdd", afterAdded-beforeLock, remaining); + if (_log.shouldLog(Log.DEBUG)) { + long complete = System.currentTimeMillis(); _log.debug("Time to add " + queueBuf.size() + " messages to " + toString() + ": " + (complete-startAdd) + " delayed? " + delayedFlush + " remaining: " + remaining + " add: " + (afterAdded-beforeLock) + " preprocess: " + (afterPreprocess-afterAdded) + " expire: " + (afterExpire-afterPreprocess) + " queue flush: " + (complete-afterExpire)); + } queueBuf.clear(); + if (!_prequeue.isEmpty()) + _pumper.wantsPumping(this); } } diff --git a/router/java/src/net/i2p/router/tunnel/ThrottledPumpedTunnelGateway.java b/router/java/src/net/i2p/router/tunnel/ThrottledPumpedTunnelGateway.java index efb65aa476dc4600266ca66d3f44c85b61fc4abf..d6da1494490c97b51c0431d9febf13b79b0455d3 100644 --- a/router/java/src/net/i2p/router/tunnel/ThrottledPumpedTunnelGateway.java +++ b/router/java/src/net/i2p/router/tunnel/ThrottledPumpedTunnelGateway.java @@ -42,6 +42,6 @@ class ThrottledPumpedTunnelGateway extends PumpedTunnelGateway { _config.incrementProcessedMessages(); return; } - super.add(msg, toRouter, toTunnel); + add(new PendingGatewayMessage(msg, toRouter, toTunnel)); } } diff --git a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java index 5ff23456c3a1af1248d9dc10d9bc234db1e214dd..e6edc3c85e35f46fb4cba1d183658c1b211d6ed5 100644 --- a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java +++ b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java @@ -9,6 +9,7 @@ import net.i2p.data.Hash; import net.i2p.router.RouterContext; import net.i2p.util.ByteCache; import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; /** * Do the simplest thing possible for preprocessing - for each message available, @@ -33,9 +34,6 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { */ protected static final ByteCache _dataCache = ByteCache.getInstance(32, PREPROCESSED_SIZE); - private static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE); - private static final ByteCache _hashCache = ByteCache.getInstance(128, Hash.HASH_LENGTH); - public TrivialPreprocessor(RouterContext ctx) { _context = ctx; _log = ctx.logManager().getLog(getClass()); @@ -50,7 +48,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { * * NOTE: Unused here, see BatchedPreprocessor override, super is not called. */ - public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { + public boolean preprocessQueue(List<PendingGatewayMessage> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) { throw new IllegalArgumentException("unused, right?"); } @@ -63,16 +61,15 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { * @param fragmentLength fragments[0:fragmentLength] is used */ protected void preprocess(byte fragments[], int fragmentLength) { - ByteArray ivBuf = _ivCache.acquire(); - byte iv[] = ivBuf.getData(); // new byte[IV_SIZE]; + byte iv[] = SimpleByteCache.acquire(IV_SIZE); _context.random().nextBytes(iv); // payload ready, now H(instructions+payload+IV) System.arraycopy(iv, 0, fragments, fragmentLength, IV_SIZE); - ByteArray hashBuf = _hashCache.acquire(); + byte[] hashBuf = SimpleByteCache.acquire(Hash.HASH_LENGTH); //Hash h = _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE); - _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE, hashBuf.getData(), 0); + _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE, hashBuf, 0); //Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE); //_log.debug("before shift: " + Base64.encode(target)); @@ -91,12 +88,12 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { System.arraycopy(iv, 0, fragments, offset, IV_SIZE); offset += IV_SIZE; //System.arraycopy(h.getData(), 0, fragments, offset, 4); - System.arraycopy(hashBuf.getData(), 0, fragments, offset, 4); + System.arraycopy(hashBuf, 0, fragments, offset, 4); offset += 4; //_log.debug("before pad : " + Base64.encode(target)); - _hashCache.release(hashBuf); - _ivCache.release(ivBuf); + SimpleByteCache.release(hashBuf); + SimpleByteCache.release(iv); // fits in a single message, so may be smaller than the full size int numPadBytes = PREPROCESSED_SIZE // max @@ -155,7 +152,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { private static final byte MASK_TUNNEL = (byte)(FragmentHandler.TYPE_TUNNEL << 5); private static final byte MASK_ROUTER = (byte)(FragmentHandler.TYPE_ROUTER << 5); - protected int writeFirstFragment(TunnelGateway.Pending msg, byte target[], int offset) { + protected int writeFirstFragment(PendingGatewayMessage msg, byte target[], int offset) { boolean fragmented = false; int instructionsLength = getInstructionsSize(msg); int payloadLength = msg.getData().length - msg.getOffset(); @@ -221,7 +218,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { return offset; } - protected int writeSubsequentFragment(TunnelGateway.Pending msg, byte target[], int offset) { + protected int writeSubsequentFragment(PendingGatewayMessage msg, byte target[], int offset) { boolean isLast = true; int instructionsLength = getInstructionsSize(msg); @@ -269,7 +266,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { * Does NOT include 4 for the message ID if the message will be fragmented; * call getInstructionAugmentationSize() for that. */ - protected int getInstructionsSize(TunnelGateway.Pending msg) { + protected int getInstructionsSize(PendingGatewayMessage msg) { if (msg.getFragmentNumber() > 0) return 7; // control byte @@ -287,7 +284,7 @@ class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { } /** @return 0 or 4 */ - protected int getInstructionAugmentationSize(TunnelGateway.Pending msg, int offset, int instructionsSize) { + protected int getInstructionAugmentationSize(PendingGatewayMessage msg, int offset, int instructionsSize) { int payloadLength = msg.getData().length - msg.getOffset(); if (offset + payloadLength + instructionsSize + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) { // requires fragmentation, so include the messageId diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java index 2503aa9ea207b8c17786498370a26e8ff667b08c..e2a6561afe9998cced9aa7fff80a0357ed58bc86 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java @@ -29,6 +29,43 @@ import net.i2p.util.Log; * Handle the actual processing and forwarding of messages through the * various tunnels. * + *<pre> + * For each type of tunnel, it creates a chain of handlers, as follows: + * + * Following tunnels are created by us: + * + * Outbound Gateway > 0 hops: + * PumpedTunnelGateway + * BatchedRouterPreprocessor -> OutboundSender -> OutboundReceiver -> OutNetMessagePool + * + * Outbound zero-hop Gateway+Endpoint: + * TunnelGatewayZeroHop + * OutboundMessageDistributor -> OutNetMessagePool + * + * Inbound Endpoint > 0 hops: + * TunnelParticipant + * RouterFragmentHandler -> InboundEndpointProcessor -> InboundMessageDistributor -> InNetMessagePool + * + * Inbound zero-hop Gateway+Endpoint: + * TunnelGatewayZeroHop + * InboundMessageDistributor -> InNetMessagePool + * + * + * Following tunnels are NOT created by us: + * + * Participant (not gateway or endpoint) + * TunnelParticipant + * HopProcessor -> OutNetMessagePool + * + * Outbound Endpoint > 0 hops: + * OutboundTunnelEndpoint + * RouterFragmentHandler -> HopProcessor -> OutboundMessageDistributor -> OutNetMessagePool + * + * Inbound Gateway > 0 hops: + * ThrottledPumpedTunnelGateway + * BatchedRouterPreprocessor -> InboundSender -> InboundGatewayReceiver -> OutNetMessagePool + * + *</pre> */ public class TunnelDispatcher implements Service { private final RouterContext _context; @@ -174,6 +211,8 @@ public class TunnelDispatcher implements Service { // following are for InboundMessageDistributor ctx.statManager().createRateStat("tunnel.dropDangerousClientTunnelMessage", "How many tunnel messages come down a client tunnel that we shouldn't expect (lifetime is the 'I2NP type')", "Tunnels", new long[] { 60*60*1000 }); ctx.statManager().createRateStat("tunnel.handleLoadClove", "When do we receive load test cloves", "Tunnels", new long[] { 60*60*1000 }); + // following is for PumpedTunnelGateway + ctx.statManager().createRateStat("tunnel.dropGatewayOverflow", "Dropped message at GW, queue full", "Tunnels", new long[] { 60*60*1000 }); } /** for IBGW */ @@ -770,7 +809,7 @@ public class TunnelDispatcher implements Service { } ******/ - public void startup() { + public synchronized void startup() { // Note that we only use the validator for participants and OBEPs, not IBGWs, so // this BW estimate will be high by about 33% assuming 2-hop tunnels average _validator = new BloomFilterIVValidator(_context, getShareBandwidth(_context)); @@ -784,7 +823,7 @@ public class TunnelDispatcher implements Service { return (int) (pct * Math.min(irateKBps, orateKBps)); } - public void shutdown() { + public synchronized void shutdown() { if (_validator != null) _validator.destroy(); _validator = null; @@ -794,6 +833,7 @@ public class TunnelDispatcher implements Service { _participants.clear(); _inboundGateways.clear(); _participatingConfig.clear(); + _leaveJob.clear(); } public void restart() { @@ -827,6 +867,10 @@ public class TunnelDispatcher implements Service { public void add(HopConfig cfg) { _configs.offer(cfg); } + + public void clear() { + _configs.clear(); + } public String getName() { return "Expire participating tunnels"; } public void runJob() { diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java index de21ea079d655e9055de3e18f7a19f28164592e5..03c93b8cef5072aa5a8fcb1890597e1e575b723c 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java @@ -10,7 +10,7 @@ import net.i2p.data.i2np.TunnelGatewayMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; /** * Serve as the gatekeeper for a tunnel, accepting messages, coallescing and/or @@ -32,11 +32,12 @@ import net.i2p.util.SimpleTimer; * or if debugging, verify that it can be decrypted properly)</li> * </ol> * + * Unused directly - see PumpedTunnelGateway, ThrottledPumpedTunnelGateway, and TunnelGatewayZeroHop overrides. */ class TunnelGateway { protected final RouterContext _context; protected final Log _log; - protected final List<Pending> _queue; + protected final List<PendingGatewayMessage> _queue; protected final QueuePreprocessor _preprocessor; protected final Sender _sender; protected final Receiver _receiver; @@ -53,7 +54,7 @@ class TunnelGateway { * @param receiver this receives the encrypted message and forwards it off * to the first hop */ - public TunnelGateway(RouterContext context, QueuePreprocessor preprocessor, Sender sender, Receiver receiver) { + protected TunnelGateway(RouterContext context, QueuePreprocessor preprocessor, Sender sender, Receiver receiver) { _context = context; _log = context.logManager().getLog(getClass()); _queue = new ArrayList(4); @@ -63,8 +64,8 @@ class TunnelGateway { //_flushFrequency = 500; _delayedFlush = new DelayedFlush(); _lastFlush = _context.clock().now(); - _context.statManager().createRateStat("tunnel.lockedGatewayAdd", "How long do we block when adding a message to a tunnel gateway's queue", "Tunnels", new long[] { 60*1000, 10*60*1000 }); - _context.statManager().createRateStat("tunnel.lockedGatewayCheck", "How long do we block when flushing a tunnel gateway's queue", "Tunnels", new long[] { 60*1000, 10*60*1000 }); + //_context.statManager().createRateStat("tunnel.lockedGatewayAdd", "How long do we block when adding a message to a tunnel gateway's queue", "Tunnels", new long[] { 60*1000, 10*60*1000 }); + //_context.statManager().createRateStat("tunnel.lockedGatewayCheck", "How long do we block when flushing a tunnel gateway's queue", "Tunnels", new long[] { 60*1000, 10*60*1000 }); } /** @@ -81,11 +82,15 @@ class TunnelGateway { * coallesced with other pending messages) or after a brief pause (_flushFrequency). * If it is queued up past its expiration, it is silently dropped * + * UNUSED - see overrides + * * @param msg message to be sent through the tunnel * @param toRouter router to send to after the endpoint (or null for endpoint processing) * @param toTunnel tunnel to send to after the endpoint (or null for endpoint or router processing) */ public void add(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) { + throw new UnsupportedOperationException("unused, right?"); +/**** _messagesSent++; long startAdd = System.currentTimeMillis(); boolean delayedFlush = false; @@ -124,7 +129,7 @@ class TunnelGateway { } if (delayedFlush) { - _context.simpleTimer().addEvent(_delayedFlush, delayAmount); + _delayedFlush.reschedule(delayAmount); } _context.statManager().addRateData("tunnel.lockedGatewayAdd", afterAdded-beforeLock, remaining); if (_log.shouldLog(Log.DEBUG)) { @@ -137,6 +142,7 @@ class TunnelGateway { + " expire: " + (afterExpire-afterPreprocess) + " queue flush: " + (complete-afterExpire)); } +****/ } public int getMessagesSent() { return _messagesSent; } @@ -165,7 +171,7 @@ class TunnelGateway { * * @return true if we should delay before preprocessing again */ - public boolean preprocessQueue(List<Pending> pending, Sender sender, Receiver receiver); + public boolean preprocessQueue(List<PendingGatewayMessage> pending, Sender sender, Receiver receiver); /** how long do we want to wait before flushing */ public long getDelayAmount(); @@ -178,137 +184,43 @@ class TunnelGateway { */ public long receiveEncrypted(byte encrypted[]); } - - /** - * Stores all the state for an unsent or partially-sent message - */ - public static class Pending { - protected final Hash _toRouter; - protected final TunnelId _toTunnel; - protected final long _messageId; - protected final long _expiration; - protected final byte _remaining[]; - protected int _offset; - protected int _fragmentNumber; - protected final long _created; - private List<Long> _messageIds; - - public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel) { - this(message, toRouter, toTunnel, System.currentTimeMillis()); - } - public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel, long now) { - _toRouter = toRouter; - _toTunnel = toTunnel; - _messageId = message.getUniqueId(); - _expiration = message.getMessageExpiration(); - _remaining = message.toByteArray(); - _created = now; - } - /** may be null */ - public Hash getToRouter() { return _toRouter; } - /** may be null */ - public TunnelId getToTunnel() { return _toTunnel; } - public long getMessageId() { return _messageId; } - public long getExpiration() { return _expiration; } - /** raw unfragmented message to send */ - public byte[] getData() { return _remaining; } - /** index into the data to be sent */ - public int getOffset() { return _offset; } - /** move the offset */ - public void setOffset(int offset) { _offset = offset; } - public long getLifetime() { return System.currentTimeMillis()-_created; } - /** which fragment are we working on (0 for the first fragment) */ - public int getFragmentNumber() { return _fragmentNumber; } - /** ok, fragment sent, increment what the next will be */ - public void incrementFragmentNumber() { _fragmentNumber++; } - /** - * Add an ID to the list of the TunnelDataMssages this message was fragmented into. - * Unused except in notePreprocessing() calls for debugging - */ - public void addMessageId(long id) { - synchronized (Pending.this) { - if (_messageIds == null) - _messageIds = new ArrayList(); - _messageIds.add(Long.valueOf(id)); - } - } - /** - * The IDs of the TunnelDataMssages this message was fragmented into. - * Unused except in notePreprocessing() calls for debugging - */ - public List<Long> getMessageIds() { - synchronized (Pending.this) { - if (_messageIds != null) - return new ArrayList(_messageIds); - else - return new ArrayList(); - } - } - } - /** Extend for debugging */ - class PendingImpl extends Pending { - public PendingImpl(I2NPMessage message, Hash toRouter, TunnelId toTunnel) { - super(message, toRouter, toTunnel, _context.clock().now()); - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("Message ").append(_messageId).append(" on "); - buf.append(TunnelGateway.this.toString()); - if (_toRouter != null) { - buf.append(" targetting "); - buf.append(_toRouter.toBase64()).append(" "); - if (_toTunnel != null) - buf.append(_toTunnel.getTunnelId()); - } - long now = _context.clock().now(); - buf.append(" actual lifetime "); - buf.append(now - _created).append("ms"); - buf.append(" potential lifetime "); - buf.append(_expiration - _created).append("ms"); - buf.append(" size ").append(_remaining.length); - buf.append(" offset ").append(_offset); - buf.append(" frag ").append(_fragmentNumber); - return buf.toString(); - } + protected class DelayedFlush extends SimpleTimer2.TimedEvent { + DelayedFlush() { + super(_context.simpleTimer2()); + } - @Override - public long getLifetime() { return _context.clock().now()-_created; } - } - - private class DelayedFlush implements SimpleTimer.TimedEvent { public void timeReached() { boolean wantRequeue = false; - int remaining = 0; - long beforeLock = _context.clock().now(); - long afterChecked = -1; + //int remaining = 0; + //long beforeLock = _context.clock().now(); + //long afterChecked = -1; long delayAmount = -1; //if (_queue.size() > 10000) // stay out of the synchronized block // System.out.println("foo!"); synchronized (_queue) { //if (_queue.size() > 10000) // stay in the synchronized block // System.out.println("foo!"); - afterChecked = _context.clock().now(); + //afterChecked = _context.clock().now(); if (!_queue.isEmpty()) { - if ( (remaining > 0) && (_log.shouldLog(Log.DEBUG)) ) - _log.debug("Remaining before delayed flush preprocessing: " + _queue); + //if ( (remaining > 0) && (_log.shouldLog(Log.DEBUG)) ) + // _log.debug("Remaining before delayed flush preprocessing: " + _queue); wantRequeue = _preprocessor.preprocessQueue(_queue, _sender, _receiver); - if (wantRequeue) + if (wantRequeue) { delayAmount = _preprocessor.getDelayAmount(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Remaining after delayed flush preprocessing (requeue? " + wantRequeue + "): " + _queue); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Remaining after delayed flush preprocessing: " + _queue); + } } - remaining = _queue.size(); + //remaining = _queue.size(); } if (wantRequeue) - _context.simpleTimer().addEvent(_delayedFlush, delayAmount); + schedule(delayAmount); else _lastFlush = _context.clock().now(); - _context.statManager().addRateData("tunnel.lockedGatewayCheck", afterChecked-beforeLock, remaining); + //_context.statManager().addRateData("tunnel.lockedGatewayCheck", afterChecked-beforeLock, remaining); } } } diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java index ec1e98e6aa8a975727ed46ba362f7a2f9b400648..380023811ca70c1a0942091e3588924bc90524e7 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java @@ -1,7 +1,10 @@ package net.i2p.router.tunnel; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -9,13 +12,17 @@ import net.i2p.router.RouterContext; import net.i2p.util.I2PThread; /** - * run through the tunnel gateways that have had messages added to them and push - * those messages through the preprocessing and sending process + * Run through the tunnel gateways that have had messages added to them and push + * those messages through the preprocessing and sending process. + * + * TODO do we need this many threads? + * TODO this combines IBGWs and OBGWs, do we wish to separate the two + * and/or prioritize OBGWs (i.e. our outbound traffic) over IBGWs (participating)? */ class TunnelGatewayPumper implements Runnable { private final RouterContext _context; - private final BlockingQueue<PumpedTunnelGateway> _wantsPumping; - private boolean _stop; + private final Set<PumpedTunnelGateway> _wantsPumping; + private volatile boolean _stop; private static final int MIN_PUMPERS = 1; private static final int MAX_PUMPERS = 4; private final int _pumpers; @@ -23,7 +30,7 @@ class TunnelGatewayPumper implements Runnable { /** Creates a new instance of TunnelGatewayPumper */ public TunnelGatewayPumper(RouterContext ctx) { _context = ctx; - _wantsPumping = new LinkedBlockingQueue(); + _wantsPumping = new LinkedHashSet(16); long maxMemory = Runtime.getRuntime().maxMemory(); if (maxMemory == Long.MAX_VALUE) maxMemory = 96*1024*1024l; @@ -35,9 +42,10 @@ class TunnelGatewayPumper implements Runnable { public void stopPumping() { _stop=true; _wantsPumping.clear(); - PumpedTunnelGateway poison = new PoisonPTG(_context); - for (int i = 0; i < _pumpers; i++) - _wantsPumping.offer(poison); + for (int i = 0; i < _pumpers; i++) { + PumpedTunnelGateway poison = new PoisonPTG(_context); + wantsPumping(poison); + } for (int i = 1; i <= 5 && !_wantsPumping.isEmpty(); i++) { try { Thread.sleep(i * 50); @@ -47,16 +55,28 @@ class TunnelGatewayPumper implements Runnable { } public void wantsPumping(PumpedTunnelGateway gw) { - if (!_stop) - _wantsPumping.offer(gw); + if (!_stop) { + synchronized (_wantsPumping) { + if (_wantsPumping.add(gw)) + _wantsPumping.notify(); + } + } } public void run() { PumpedTunnelGateway gw = null; - List<TunnelGateway.Pending> queueBuf = new ArrayList(32); + List<PendingGatewayMessage> queueBuf = new ArrayList(32); while (!_stop) { try { - gw = _wantsPumping.take(); + synchronized (_wantsPumping) { + if (_wantsPumping.isEmpty()) { + _wantsPumping.wait(); + } else { + Iterator<PumpedTunnelGateway> iter = _wantsPumping.iterator(); + gw = iter.next(); + iter.remove(); + } + } } catch (InterruptedException ie) {} if (gw != null) { if (gw.getMessagesSent() == POISON_PTG) diff --git a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java index dfb3ac1e1e3f0bf0ecdb33b15206ab7e34d6f315..758a991ffc8043b3bd1d524372a9c01452f3b654 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java @@ -30,6 +30,7 @@ class TunnelParticipant { private static final long MAX_LOOKUP_TIME = 15*1000; /** for next hop when a tunnel is first created */ private static final long LONG_MAX_LOOKUP_TIME = 30*1000; + private static final int PRIORITY = OutNetMessage.PRIORITY_PARTICIPATING; /** not an inbound endpoint */ public TunnelParticipant(RouterContext ctx, HopConfig config, HopProcessor processor) { @@ -196,7 +197,7 @@ class TunnelParticipant { m.setMessage(msg); m.setExpiration(msg.getMessageExpiration()); m.setTarget(ri); - m.setPriority(200); + m.setPriority(PRIORITY); if (_log.shouldLog(Log.DEBUG)) _log.debug("Forward on from " + _config + ": " + msg); _context.outNetMessagePool().add(m); diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java index f578f76726e87b0a81c28b4a7a115c103668a3c7..5c4693fd2c2ac3abee822a3e15ffae5c6e4f76b0 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java @@ -61,6 +61,7 @@ class BuildHandler implements Runnable { private static final int MAX_QUEUE = 192; private static final int NEXT_HOP_LOOKUP_TIMEOUT = 15*1000; + private static final int PRIORITY = OutNetMessage.PRIORITY_BUILD_REPLY; /** * This must be high, as if we timeout the send we remove the tunnel from @@ -689,7 +690,7 @@ class BuildHandler implements Runnable { OutNetMessage msg = new OutNetMessage(_context); msg.setMessage(state.msg); msg.setExpiration(state.msg.getMessageExpiration()); - msg.setPriority(300); + msg.setPriority(PRIORITY); msg.setTarget(nextPeerInfo); if (response == 0) msg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg)); @@ -722,7 +723,7 @@ class BuildHandler implements Runnable { OutNetMessage outMsg = new OutNetMessage(_context); outMsg.setExpiration(m.getMessageExpiration()); outMsg.setMessage(m); - outMsg.setPriority(300); + outMsg.setPriority(PRIORITY); outMsg.setTarget(nextPeerInfo); if (response == 0) outMsg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg)); diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java index 1b8e0a9b9504e785a1263ad991a47e70eff792ee..31f807c7c0f21c688f4a57cb8691e75dec90ef34 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java @@ -29,7 +29,9 @@ abstract class BuildRequestor { for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++) ORDER.add(Integer.valueOf(i)); } - private static final int PRIORITY = 500; + + private static final int PRIORITY = OutNetMessage.PRIORITY_MY_BUILD_REQUEST; + /** * At 10 seconds, we were receiving about 20% of replies after expiration * Todo: make this variable on a per-request basis, to account for tunnel length, diff --git a/router/java/src/net/i2p/router/util/CDPQEntry.java b/router/java/src/net/i2p/router/util/CDPQEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..a04e7f522b4b05b0b8149a0325b4a58c678eeeb7 --- /dev/null +++ b/router/java/src/net/i2p/router/util/CDPQEntry.java @@ -0,0 +1,9 @@ +package net.i2p.router.util; + +/** + * For CoDelPriorityQueue + * @since 0.9.3 + */ +public interface CDPQEntry extends CDQEntry, PQEntry { + +} diff --git a/router/java/src/net/i2p/router/util/CDQEntry.java b/router/java/src/net/i2p/router/util/CDQEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..776c03ef057f79d0feee764545b3cee5fd715a15 --- /dev/null +++ b/router/java/src/net/i2p/router/util/CDQEntry.java @@ -0,0 +1,20 @@ +package net.i2p.router.util; + +/** + * For CoDelQueue + * @since 0.9.3 + */ +public interface CDQEntry { + + /** + * To be set by the queue + */ + public void setEnqueueTime(long time); + + public long getEnqueueTime(); + + /** + * Implement any reclaimation of resources here + */ + public void drop(); +} diff --git a/router/java/src/net/i2p/router/util/CoDelBlockingQueue.java b/router/java/src/net/i2p/router/util/CoDelBlockingQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..3605f0bd5fdbfa3c94cfbfb1896ce11fd1c89479 --- /dev/null +++ b/router/java/src/net/i2p/router/util/CoDelBlockingQueue.java @@ -0,0 +1,317 @@ +package net.i2p.router.util; + +import java.util.Collection; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; + +/** + * CoDel implementation of Active Queue Management. + * Ref: http://queue.acm.org/detail.cfm?id=2209336 + * Ref: http://queue.acm.org/appendices/codel.html + * + * Code and comments are directly from appendix above, apparently public domain. + * + * Input: add(), offer(), and put() are overridden to add a timestamp. + * + * Output : take(), poll(), and drainTo() are overridden to implement AQM and drop entries + * if necessary. peek(), and remove() are NOT overridden, and do + * NOT implement AQM or update stats. + * + * @since 0.9.3 + */ +public class CoDelBlockingQueue<E extends CDQEntry> extends LinkedBlockingQueue<E> { + + private final I2PAppContext _context; + private final Log _log; + private final String _name; + private final int _capacity; + + // following 4 are state variables defined by sample code, locked by this + /** Time when we'll declare we're above target (0 if below) */ + private long _first_above_time; + /** Time to drop next packet */ + private long _drop_next; + /** Packets dropped since going into drop state */ + private int _count; + /** true if in drop state */ + private boolean _dropping; + + /** following is a per-request global for ease of use, locked by this */ + private long _now; + + /** debugging */ + private static final AtomicLong __id = new AtomicLong(); + private final long _id; + + /** + * Quote: + * Below a target of 5 ms, utilization suffers for some conditions and traffic loads; + * above 5 ms there is very little or no improvement in utilization. + * + * I2P: Raise to 15 due to multithreading environment + * + * Maybe need to make configurable per-instance. + */ + private static final long TARGET = 15; + + /** + * Quote: + * A setting of 100 ms works well across a range of RTTs from 10 ms to 1 second + * + * Maybe need to make configurable per-instance. + */ + private static final long INTERVAL = 100; + //private static final int MAXPACKET = 512; + + private final String STAT_DROP; + private final String STAT_DELAY; + private static final long[] RATES = {5*60*1000, 60*60*1000}; + private static final long BACKLOG_TIME = 2*1000; + + /** + * @param name for stats + */ + public CoDelBlockingQueue(I2PAppContext ctx, String name, int capacity) { + super(capacity); + _context = ctx; + _log = ctx.logManager().getLog(CoDelBlockingQueue.class); + _name = name; + _capacity = capacity; + STAT_DROP = "codel." + name + ".drop"; + STAT_DELAY = "codel." + name + ".delay"; + ctx.statManager().createRequiredRateStat(STAT_DROP, "queue delay of dropped items", "Router", RATES); + ctx.statManager().createRequiredRateStat(STAT_DELAY, "average queue delay", "Router", RATES); + _id = __id.incrementAndGet(); + } + + @Override + public boolean add(E o) { + o.setEnqueueTime(_context.clock().now()); + return super.add(o); + } + + @Override + public boolean offer(E o) { + o.setEnqueueTime(_context.clock().now()); + return super.offer(o); + } + + @Override + public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException { + o.setEnqueueTime(_context.clock().now()); + return super.offer(o, timeout, unit); + } + + @Override + public void put(E o) throws InterruptedException { + o.setEnqueueTime(_context.clock().now()); + super.put(o); + } + + @Override + public void clear() { + super.clear(); + synchronized(this) { + _first_above_time = 0; + _drop_next = 0; + _count = 0; + _dropping = false; + } + } + + @Override + public E take() throws InterruptedException { + E rv; + do { + rv = deque(); + } while (rv == null); + return rv; + } + + @Override + public E poll() { + E rv = super.poll(); + return codel(rv); + } + + /** + * Updates stats and possibly drops while draining. + */ + @Override + public int drainTo(Collection<? super E> c) { + int rv = 0; + E e; + while ((e = poll()) != null) { + c.add(e); + rv++; + } + return rv; + } + + /** + * Updates stats and possibly drops while draining. + */ + @Override + public int drainTo(Collection<? super E> c, int maxElements) { + int rv = 0; + E e; + while ((e = poll()) != null && rv++ < maxElements) { + c.add(e); + } + return rv; + } + + /** + * Drains all, without updating stats or dropping. + */ + public int drainAllTo(Collection<? super E> c) { + return super.drainTo(c); + } + + /** + * Has the head of the queue been waiting too long, + * or is the queue almost full? + */ + public boolean isBacklogged() { + E e = peek(); + if (e == null) + return false; + return _dropping || + _context.clock().now() - e.getEnqueueTime() >= BACKLOG_TIME || + remainingCapacity() < _capacity / 4; + } + + /////// private below here + + /** + * Caller must synch on this + * @param entry may be null + */ + private boolean updateVars(E entry) { + // This is a helper routine that tracks whether the sojourn time + // is above or below target and, if above, if it has remained above continuously for at least interval. + // It returns a boolean indicating whether it is OK to drop (sojourn time above target + // for at least interval) + if (entry == null) { + _first_above_time = 0; + return false; + } + _now = _context.clock().now(); + boolean ok_to_drop = false; + long sojurn = _now - entry.getEnqueueTime(); + _context.statManager().addRateData(STAT_DELAY, sojurn); + // I2P use isEmpty instead of size() < MAXPACKET + if (sojurn < TARGET || isEmpty()) { + _first_above_time = 0; + } else { + if (_first_above_time == 0) { + // just went above from below. if we stay above + // for at least INTERVAL we'll say it's ok to drop + _first_above_time = _now + INTERVAL; + } else if (_now >= _first_above_time) { + ok_to_drop = true; + } + } + return ok_to_drop; + } + + /** + * @return if null, call again + */ + private E deque() throws InterruptedException { + E rv = super.take(); + return codel(rv); + } + + + /** + * @param rv may be null + * @return rv or a subequent entry or null if dropped + */ + private E codel(E rv) { + synchronized (this) { + // non-blocking inside this synchronized block + + boolean ok_to_drop = updateVars(rv); + // All of the work of CoDel is done here. + // There are two branches: if we're in packet-dropping state (meaning that the queue-sojourn + // time has gone above target and hasn't come down yet), then we need to check if it's time + // to leave or if it's time for the next drop(s); if we're not in dropping state, then we need + // to decide if it's time to enter and do the initial drop. + if (_dropping) { + if (!ok_to_drop) { + // sojurn time below target - leave dropping state + _dropping = false; + } else if (_now >= _drop_next) { + // It's time for the next drop. Drop the current packet and dequeue the next. + // The dequeue might take us out of dropping state. If not, schedule the next drop. + // A large backlog might result in drop rates so high that the next drop should happen now; + // hence, the while loop. + while (_now >= _drop_next && _dropping) { + drop(rv); + _count++; + // I2P - we poll here instead of lock so we don't get stuck + // inside the lock. If empty, deque() will be called again. + rv = super.poll(); + ok_to_drop = updateVars(rv); + if (!ok_to_drop) { + // leave dropping state + _dropping = false; + } else { + // schedule the next drop + control_law(_drop_next); + } + } + } + } else if (ok_to_drop && + (_now - _drop_next < INTERVAL || _now - _first_above_time >= INTERVAL)) { + // If we get here, then we're not in dropping state. If the sojourn time has been above + // target for interval, then we decide whether it's time to enter dropping state. + // We do so if we've been either in dropping state recently or above target for a relatively + // long time. The "recently" check helps ensure that when we're successfully controlling + // the queue we react quickly (in one interval) and start with the drop rate that controlled + // the queue last time rather than relearn the correct rate from scratch. If we haven't been + // dropping recently, the "long time above" check adds some hysteresis to the state entry + // so we don't drop on a slightly bigger-than-normal traffic pulse into an otherwise quiet queue. + drop(rv); + // I2P - we poll here instead of lock so we don't get stuck + // inside the lock. If empty, deque() will be called again. + rv = super.poll(); + updateVars(rv); + _dropping = true; + // If we're in a drop cycle, the drop rate that controlled the queue + // on the last cycle is a good starting point to control it now. + if (_now - _drop_next < INTERVAL) + _count = _count > 2 ? _count - 2 : 1; + else + _count = 1; + control_law(_now); + } + } + return rv; + } + + private void drop(E entry) { + long delay = _context.clock().now() - entry.getEnqueueTime(); + _context.statManager().addRateData(STAT_DROP, delay); + if (_log.shouldLog(Log.WARN)) + _log.warn("CDQ #" + _id + ' ' + _name + " dropped item with delay " + delay + ", " + + DataHelper.formatDuration(_context.clock().now() - _first_above_time) + " since first above, " + + DataHelper.formatDuration(_context.clock().now() - _drop_next) + " since drop next, " + + (_count+1) + " dropped in this phase, " + + size() + " remaining in queue: " + entry); + entry.drop(); + } + + /** + * Caller must synch on this + */ + private void control_law(long t) { + _drop_next = t + (long) (INTERVAL / Math.sqrt(_count)); + } +} diff --git a/router/java/src/net/i2p/router/util/CoDelPriorityBlockingQueue.java b/router/java/src/net/i2p/router/util/CoDelPriorityBlockingQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..2e5901d9f074c38cd7f538afc8a79044c86e4d9a --- /dev/null +++ b/router/java/src/net/i2p/router/util/CoDelPriorityBlockingQueue.java @@ -0,0 +1,316 @@ +package net.i2p.router.util; + +import java.util.Collection; +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; + +/** + * CoDel implementation of Active Queue Management. + * Ref: http://queue.acm.org/detail.cfm?id=2209336 + * Ref: http://queue.acm.org/appendices/codel.html + * + * Code and comments are directly from appendix above, apparently public domain. + * + * Input: add(), offer(), and put() are overridden to add a timestamp. + * + * Output : take(), poll(), and drainTo() are overridden to implement AQM and drop entries + * if necessary. peek(), and remove() are NOT overridden, and do + * NOT implement AQM or update stats. + * + * @since 0.9.3 + */ +public class CoDelPriorityBlockingQueue<E extends CDPQEntry> extends PriBlockingQueue<E> { + + private final I2PAppContext _context; + private final Log _log; + private final String _name; + private final AtomicLong _seqNum = new AtomicLong(); + + // following 4 are state variables defined by sample code, locked by this + /** Time when we'll declare we're above target (0 if below) */ + private long _first_above_time; + /** Time to drop next packet */ + private long _drop_next; + /** Packets dropped since going into drop state */ + private int _count; + /** true if in drop state */ + private boolean _dropping; + + /** following is a per-request global for ease of use, locked by this */ + private long _now; + + private int _lastDroppedPriority; + + /** debugging */ + static final AtomicLong __id = new AtomicLong(); + private final long _id; + + /** + * Quote: + * Below a target of 5 ms, utilization suffers for some conditions and traffic loads; + * above 5 ms there is very little or no improvement in utilization. + * + * I2P: Raise to 15 due to multithreading environment + * + * Maybe need to make configurable per-instance. + */ + private static final long TARGET = 15; + + /** + * Quote: + * A setting of 100 ms works well across a range of RTTs from 10 ms to 1 second + * + * I2P: Raise to 300 due to longer end-to-end RTTs + * + * Maybe need to make configurable per-instance. + */ + private static final long INTERVAL = 300; + //private static final int MAXPACKET = 512; + + private final String STAT_DROP; + private final String STAT_DELAY; + private static final long[] RATES = {5*60*1000, 60*60*1000}; + public static final int MIN_PRIORITY = 100; + private static final int[] PRIORITIES = {MIN_PRIORITY, 200, 300, 400, 500}; + /** if priority is >= this, never drop */ + public static final int DONT_DROP_PRIORITY = 1000; + private static final long BACKLOG_TIME = 2*1000; + + /** + * @param name for stats + */ + public CoDelPriorityBlockingQueue(I2PAppContext ctx, String name, int initialCapacity) { + super(initialCapacity); + _context = ctx; + _log = ctx.logManager().getLog(CoDelPriorityBlockingQueue.class); + _name = name; + STAT_DROP = "codel." + name + ".drop."; + STAT_DELAY = "codel." + name + ".delay"; + for (int i = 0; i < PRIORITIES.length; i++) { + ctx.statManager().createRequiredRateStat(STAT_DROP + PRIORITIES[i], "queue delay of dropped items by priority", "Router", RATES); + } + ctx.statManager().createRequiredRateStat(STAT_DELAY, "average queue delay", "Router", RATES); + _id = __id.incrementAndGet(); + } + + @Override + public void clear() { + super.clear(); + synchronized(this) { + _first_above_time = 0; + _drop_next = 0; + _count = 0; + _dropping = false; + } + } + + @Override + public E take() throws InterruptedException { + E rv; + do { + rv = deque(); + } while (rv == null); + return rv; + } + + @Override + public E poll() { + E rv = super.poll(); + return codel(rv); + } + + /** + * Updates stats and possibly drops while draining. + */ + @Override + public int drainTo(Collection<? super E> c) { + int rv = 0; + E e; + while ((e = poll()) != null) { + c.add(e); + rv++; + } + return rv; + } + + /** + * Updates stats and possibly drops while draining. + */ + @Override + public int drainTo(Collection<? super E> c, int maxElements) { + int rv = 0; + E e; + while ((e = poll()) != null && rv++ < maxElements) { + c.add(e); + } + return rv; + } + + /** + * Drains all, without updating stats or dropping. + */ + public int drainAllTo(Collection<? super E> c) { + return super.drainTo(c); + } + + /** + * Has the head of the queue been waiting too long, + * or is the queue too big? + */ + @Override + public boolean isBacklogged() { + E e = peek(); + if (e == null) + return false; + return _dropping || + _context.clock().now() - e.getEnqueueTime() >= BACKLOG_TIME || + size() >= BACKLOG_SIZE; + } + + /////// private below here + + @Override + protected void timestamp(E o) { + o.setSeqNum(_seqNum.incrementAndGet()); + o.setEnqueueTime(_context.clock().now()); + if (o.getPriority() < MIN_PRIORITY && _log.shouldLog(Log.WARN)) + _log.warn(_name + " added item with low priority " + o.getPriority() + + ": " + o); + } + + /** + * Caller must synch on this + * @param entry may be null + */ + private boolean updateVars(E entry) { + // This is a helper routine that tracks whether the sojourn time + // is above or below target and, if above, if it has remained above continuously for at least interval. + // It returns a boolean indicating whether it is OK to drop (sojourn time above target + // for at least interval) + if (entry == null) { + _first_above_time = 0; + return false; + } + _now = _context.clock().now(); + boolean ok_to_drop = false; + long sojurn = _now - entry.getEnqueueTime(); + _context.statManager().addRateData(STAT_DELAY, sojurn); + // I2P use isEmpty instead of size() < MAXPACKET + if (sojurn < TARGET || isEmpty()) { + _first_above_time = 0; + } else { + if (_first_above_time == 0) { + // just went above from below. if we stay above + // for at least INTERVAL we'll say it's ok to drop + _first_above_time = _now + INTERVAL; + } else if (_now >= _first_above_time) { + ok_to_drop = true; + } + } + return ok_to_drop; + } + + /** + * @return if null, call again + */ + private E deque() throws InterruptedException { + E rv = super.take(); + return codel(rv); + } + + /** + * @param rv may be null + * @return rv or a subequent entry or null if dropped + */ + private E codel(E rv) { + synchronized (this) { + // non-blocking inside this synchronized block + + boolean ok_to_drop = updateVars(rv); + // All of the work of CoDel is done here. + // There are two branches: if we're in packet-dropping state (meaning that the queue-sojourn + // time has gone above target and hasn't come down yet), then we need to check if it's time + // to leave or if it's time for the next drop(s); if we're not in dropping state, then we need + // to decide if it's time to enter and do the initial drop. + if (_dropping) { + if (!ok_to_drop) { + // sojurn time below target - leave dropping state + _dropping = false; + } else if (_now >= _drop_next) { + // It's time for the next drop. Drop the current packet and dequeue the next. + // The dequeue might take us out of dropping state. If not, schedule the next drop. + // A large backlog might result in drop rates so high that the next drop should happen now; + // hence, the while loop. + while (_now >= _drop_next && _dropping && rv.getPriority() <= _lastDroppedPriority) { + drop(rv); + _count++; + // I2P - we poll here instead of lock so we don't get stuck + // inside the lock. If empty, deque() will be called again. + rv = super.poll(); + ok_to_drop = updateVars(rv); + if (!ok_to_drop) { + // leave dropping state + _dropping = false; + } else { + // schedule the next drop + control_law(_drop_next); + } + } + } + } else if (ok_to_drop && + rv.getPriority() < DONT_DROP_PRIORITY && + (_now - _drop_next < INTERVAL || _now - _first_above_time >= INTERVAL)) { + // If we get here, then we're not in dropping state. If the sojourn time has been above + // target for interval, then we decide whether it's time to enter dropping state. + // We do so if we've been either in dropping state recently or above target for a relatively + // long time. The "recently" check helps ensure that when we're successfully controlling + // the queue we react quickly (in one interval) and start with the drop rate that controlled + // the queue last time rather than relearn the correct rate from scratch. If we haven't been + // dropping recently, the "long time above" check adds some hysteresis to the state entry + // so we don't drop on a slightly bigger-than-normal traffic pulse into an otherwise quiet queue. + drop(rv); + _lastDroppedPriority = rv.getPriority(); + // I2P - we poll here instead of lock so we don't get stuck + // inside the lock. If empty, deque() will be called again. + rv = super.poll(); + updateVars(rv); + _dropping = true; + // If we're in a drop cycle, the drop rate that controlled the queue + // on the last cycle is a good starting point to control it now. + if (_now - _drop_next < INTERVAL) + _count = _count > 2 ? _count - 2 : 1; + else + _count = 1; + control_law(_now); + } + } + return rv; + } + + private void drop(E entry) { + long delay = _context.clock().now() - entry.getEnqueueTime(); + _context.statManager().addRateData(STAT_DROP + entry.getPriority(), delay); + if (_log.shouldLog(Log.WARN)) + _log.warn("CDPQ #" + _id + ' ' + _name + " dropped item with delay " + delay + ", priority " + + entry.getPriority() + ", seq " + + entry.getSeqNum() + ", " + + DataHelper.formatDuration(_context.clock().now() - _first_above_time) + " since first above, " + + DataHelper.formatDuration(_context.clock().now() - _drop_next) + " since drop next, " + + (_count+1) + " dropped in this phase, " + + size() + " remaining in queue: " + entry); + entry.drop(); + } + + /** + * Caller must synch on this + */ + private void control_law(long t) { + _drop_next = t + (long) (INTERVAL / Math.sqrt(_count)); + } +} diff --git a/router/java/src/net/i2p/router/util/DecayingBloomFilter.java b/router/java/src/net/i2p/router/util/DecayingBloomFilter.java index c219a57ed48588fd1458732ccf5de78fe7f69307..7f5f2dee4ae0142da2d88455e534e86c90c587b8 100644 --- a/router/java/src/net/i2p/router/util/DecayingBloomFilter.java +++ b/router/java/src/net/i2p/router/util/DecayingBloomFilter.java @@ -7,7 +7,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; import org.xlattice.crypto.filters.BloomSHA1; @@ -38,7 +38,7 @@ public class DecayingBloomFilter { private final long _longToEntryMask; protected long _currentDuplicates; protected volatile boolean _keepDecaying; - protected final SimpleTimer.TimedEvent _decayEvent; + protected final SimpleTimer2.TimedEvent _decayEvent; /** just for logging */ protected final String _name; /** synchronize against this lock when switching double buffers */ @@ -64,7 +64,7 @@ public class DecayingBloomFilter { context.addShutdownTask(new Shutdown()); _decayEvent = new DecayEvent(); _keepDecaying = true; - SimpleTimer.getInstance().addEvent(_decayEvent, _durationMs); + _decayEvent.schedule(_durationMs); } /** @@ -118,7 +118,7 @@ public class DecayingBloomFilter { } _decayEvent = new DecayEvent(); _keepDecaying = true; - SimpleTimer.getInstance().addEvent(_decayEvent, _durationMs); + _decayEvent.schedule(_durationMs); if (_log.shouldLog(Log.WARN)) _log.warn("New DBF " + name + " m = " + m + " k = " + k + " entryBytes = " + entryBytes + " numExtenders = " + numExtenders + " cycle (s) = " + (durationMs / 1000)); @@ -274,7 +274,7 @@ public class DecayingBloomFilter { public void stopDecaying() { _keepDecaying = false; - SimpleTimer.getInstance().removeEvent(_decayEvent); + _decayEvent.cancel(); } protected void decay() { @@ -310,11 +310,15 @@ public class DecayingBloomFilter { } } - private class DecayEvent implements SimpleTimer.TimedEvent { + private class DecayEvent extends SimpleTimer2.TimedEvent { + DecayEvent() { + super(_context.simpleTimer2()); + } + public void timeReached() { if (_keepDecaying) { decay(); - SimpleTimer.getInstance().addEvent(DecayEvent.this, _durationMs); + schedule(_durationMs); } } } diff --git a/router/java/src/net/i2p/router/util/EventLog.java b/router/java/src/net/i2p/router/util/EventLog.java new file mode 100644 index 0000000000000000000000000000000000000000..9a2d9d20e114cfe080d320561f078929613f2114 --- /dev/null +++ b/router/java/src/net/i2p/router/util/EventLog.java @@ -0,0 +1,145 @@ +package net.i2p.router.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.i2p.I2PAppContext; +import net.i2p.util.SecureFileOutputStream; + +/** + * Simple event logger for occasional events, + * with caching for reads. + * Does not keep the file open. + * @since 0.9.3 + */ +public class EventLog { + + private final I2PAppContext _context; + private final File _file; + /** event to cached map */ + private final Map<String, SortedMap<Long, String>> _cache; + /** event to starting time of cached map */ + private final Map<String, Long> _cacheTime; + + /** for convenience, not required */ + public static final String ABORTED = "aborted"; + public static final String CHANGE_IP = "changeIP"; + public static final String CHANGE_PORT = "changePort"; + public static final String CLOCK_SHIFT = "clockShift"; + public static final String CRASHED = "crashed"; + public static final String INSTALLED = "installed"; + public static final String INSTALL_FAILED = "intallFailed"; + public static final String NEW_IDENT = "newIdent"; + public static final String REKEYED = "rekeyed"; + public static final String SOFT_RESTART = "softRestart"; + public static final String STARTED = "started"; + public static final String STOPPED = "stopped"; + public static final String UPDATED = "updated"; + public static final String WATCHDOG = "watchdog"; + + /** + * @param file must be absolute + * @throws IllegalArgumentException if not absolute + */ + public EventLog(I2PAppContext ctx, File file) { + if (!file.isAbsolute()) + throw new IllegalArgumentException(); + _context = ctx; + _file = file; + _cache = new HashMap(4); + _cacheTime = new HashMap(4); + } + + /** + * Append an event. Fails silently. + * @param event no spaces, e.g. "started" + * @throws IllegalArgumentException if event contains a space or newline + */ + public void addEvent(String event) { + addEvent(event, null); + } + + /** + * Append an event. Fails silently. + * @param event no spaces or newlines, e.g. "started" + * @param info no newlines, may be blank or null + * @throws IllegalArgumentException if event contains a space or either contains a newline + */ + public synchronized void addEvent(String event, String info) { + if (event.contains(" ") || event.contains("\n") || + (info != null && info.contains("\n"))) + throw new IllegalArgumentException(); + _cache.remove(event); + _cacheTime.remove(event); + OutputStream out = null; + try { + out = new SecureFileOutputStream(_file, true); + StringBuilder buf = new StringBuilder(128); + buf.append(_context.clock().now()).append(' ').append(event); + if (info != null && info.length() > 0) + buf.append(' ').append(info); + buf.append('\n'); + out.write(buf.toString().getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + + /** + * Caches. + * Fails silently. + * @param event matching this event only, case sensitive + * @param since since this time, 0 for all + * @return non-null, Map of times to (possibly empty) info strings, sorted, earliest first, unmodifiable + */ + public synchronized SortedMap<Long, String> getEvents(String event, long since) { + SortedMap<Long, String> rv = _cache.get(event); + if (rv != null) { + Long cacheTime = _cacheTime.get(event); + if (cacheTime != null) { + if (since >= cacheTime.longValue()) + return rv.tailMap(Long.valueOf(since)); + } + } + rv = new TreeMap(); + InputStream in = null; + try { + in = new FileInputStream(_file); + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String line = null; + while ( (line = br.readLine()) != null) { + try { + String[] s = line.split(" ", 3); + if (!s[1].equals(event)) + continue; + long time = Long.parseLong(s[0]); + if (time <= since) + continue; + Long ltime = Long.valueOf(time); + String info = s.length > 2 ? s[2] : ""; + rv.put(time, info); + } catch (IndexOutOfBoundsException ioobe) { + } catch (NumberFormatException nfe) { + } + } + rv = Collections.unmodifiableSortedMap(rv); + _cache.put(event, rv); + _cacheTime.put(event, Long.valueOf(since)); + } catch (IOException ioe) { + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + return rv; + } +} diff --git a/router/java/src/net/i2p/router/util/PQEntry.java b/router/java/src/net/i2p/router/util/PQEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..e92d552dfa06b52598cd5faa30270cf524bdcbbb --- /dev/null +++ b/router/java/src/net/i2p/router/util/PQEntry.java @@ -0,0 +1,23 @@ +package net.i2p.router.util; + +/** + * For PriBlockingQueue + * @since 0.9.3 + */ +public interface PQEntry { + + /** + * Higher is higher priority + */ + public int getPriority(); + + /** + * To be set by the queue + */ + public void setSeqNum(long num); + + /** + * Needed to ensure FIFO ordering within a single priority + */ + public long getSeqNum(); +} diff --git a/router/java/src/net/i2p/router/util/PriBlockingQueue.java b/router/java/src/net/i2p/router/util/PriBlockingQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..486728bc486eab0d28ccd19ccee37accdd3f3464 --- /dev/null +++ b/router/java/src/net/i2p/router/util/PriBlockingQueue.java @@ -0,0 +1,76 @@ +package net.i2p.router.util; + +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Priority Blocking Queue using methods in the entries, + * as definied in PQEntry, to store priority and sequence number, + * ensuring FIFO order within a priority. + * + * Input: add(), offer(), and put() are overridden to add a sequence number. + * + * @since 0.9.3 + */ +public class PriBlockingQueue<E extends PQEntry> extends PriorityBlockingQueue<E> { + + private final AtomicLong _seqNum = new AtomicLong(); + + protected static final int BACKLOG_SIZE = 256; + + public PriBlockingQueue(int initialCapacity) { + super(initialCapacity, new PriorityComparator()); + } + + @Override + public boolean add(E o) { + timestamp(o); + return super.add(o); + } + + @Override + public boolean offer(E o) { + timestamp(o); + return super.offer(o); + } + + @Override + public boolean offer(E o, long timeout, TimeUnit unit) { + timestamp(o); + return super.offer(o, timeout, unit); + } + + @Override + public void put(E o) { + timestamp(o); + super.put(o); + } + + /** + * Is the queue too big? + */ + public boolean isBacklogged() { + return size() >= BACKLOG_SIZE; + } + + /////// private below here + + protected void timestamp(E o) { + o.setSeqNum(_seqNum.incrementAndGet()); + } + + /** + * highest priority first, then lowest sequence number first + */ + private static class PriorityComparator<E extends PQEntry> implements Comparator<E> { + public int compare(E l, E r) { + int d = r.getPriority() - l.getPriority(); + if (d != 0) + return d; + long ld = l.getSeqNum() - r.getSeqNum(); + return ld > 0 ? 1 : -1; + } + } +}