package net.i2p.router.tunnel; import java.util.List; import java.util.concurrent.BlockingQueue; 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; /** * This is used for all gateways with more than zero hops. * * Serve as the gatekeeper for a tunnel, accepting messages, coallescing and/or * fragmenting them before wrapping them up for tunnel delivery. The flow here * is: <ol> * <li>add an I2NPMessage (and a target tunnel/router, if necessary)</li> * <li>that message is queued up into a TunnelGateway.Pending and offered to the * assigned QueuePreprocessor.</li> * <li>that QueuePreprocessor may then take off any of the TunnelGateway.Pending * messages or instruct the TunnelGateway to offer it the messages again in * a short while (in an attempt to coallesce them). * <li>when the QueueProcessor accepts a TunnelGateway.Pending, it preprocesses * it into fragments, forwarding each preprocessed fragment group through * the Sender.</li> * <li>the Sender then encrypts the preprocessed data and delivers it to the * Receiver.</li> * <li>the Receiver now has the encrypted message and may do with it as it * pleases (e.g. wrap it as necessary and enqueue it onto the OutNetMessagePool, * or if debugging, verify that it can be decrypted properly)</li> * </ol> * */ class PumpedTunnelGateway extends TunnelGateway { private final BlockingQueue<PendingGatewayMessage> _prequeue; private final TunnelGatewayPumper _pumper; private final boolean _isInbound; private final Hash _nextHop; /** * warning - these limit total messages per second throughput due to * requeue delay in TunnelGatewayPumper to max * 1000 / REQUEUE_TIME */ private static final int MAX_OB_MSGS_PER_PUMP = 64; private static final int MAX_IB_MSGS_PER_PUMP = 24; 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 * @param sender this takes a preprocessed message, encrypts it, and sends it to * the receiver * @param receiver this receives the encrypted message and forwards it off * to the first hop */ @SuppressWarnings({ "unchecked", "rawtypes" }) public PumpedTunnelGateway(RouterContext context, QueuePreprocessor preprocessor, Sender sender, Receiver receiver, TunnelGatewayPumper pumper) { super(context, preprocessor, sender, receiver); if (getClass() == PumpedTunnelGateway.class) { // Unbounded priority queue for outbound // fixme lint PendingGatewayMessage is not a CDPQEntry _prequeue = new CoDelPriorityBlockingQueue(context, "OBGW", INITIAL_OB_QUEUE); _nextHop = receiver.getSendTo(); _isInbound = false; } else if (receiver != null) { // extended by ThrottledPTG for IB // Bounded non-priority queue for inbound _prequeue = new CoDelBlockingQueue<PendingGatewayMessage>(context, "IBGW", MAX_IB_QUEUE); _nextHop = receiver.getSendTo(); _isInbound = true; } else { // Poison PTG _prequeue = null; _nextHop = null; _isInbound = true; } _pumper = pumper; } /** * Add a message to be sent down the tunnel, either sending it now (perhaps * 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++; if (_prequeue.offer(cur)) _pumper.wantsPumping(this); else _context.statManager().addRateData("tunnel.dropGatewayOverflow", 1); } /** * run in one of the TunnelGatewayPumper's threads, this pulls pending messages * off the prequeue, adds them to the queue and then tries to preprocess the queue, * scheduling a later delayed flush as necessary. this allows the gw.add call to * go quickly, rather than blocking its callers on potentially substantial * processing. * * @param queueBuf Empty list for convenience, to use as a temporary buffer. * Must be empty when called; will always be emptied before return. * @return true if we did not finish, and the pumper should be requeued. */ public boolean pump(List<PendingGatewayMessage> queueBuf) { // If the next hop is backlogged, // drain only a little... better to let things back up here, // before fragmentation, where we have priority queueing (for OBGW) int max; boolean backlogged = _context.commSystem().isBacklogged(_nextHop); if (backlogged && _log.shouldLog(Log.INFO)) _log.info("PTG backlogged, queued to " + _nextHop + " : " + _prequeue.size() + " IB? " + _isInbound); if (backlogged) max = _isInbound ? 1 : 2; else max = _isInbound ? MAX_IB_MSGS_PER_PUMP : MAX_OB_MSGS_PER_PUMP; _prequeue.drainTo(queueBuf, max); if (queueBuf.isEmpty()) return false; boolean rv = !_prequeue.isEmpty(); long startAdd = System.currentTimeMillis(); long beforeLock = startAdd; long afterAdded = -1; boolean delayedFlush = false; long delayAmount = -1; int remaining = 0; long afterPreprocess = 0; long afterExpire = 0; synchronized (_queue) { _queue.addAll(queueBuf); afterAdded = System.currentTimeMillis(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Added before direct flush preprocessing for " + toString() + ": " + _queue); delayedFlush = _preprocessor.preprocessQueue(_queue, _sender, _receiver); afterPreprocess = System.currentTimeMillis(); if (delayedFlush) delayAmount = _preprocessor.getDelayAmount(); _lastFlush = _context.clock().now(); // expire any as necessary, even if its framented for (int i = 0; i < _queue.size(); 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); _queue.remove(i); i--; } } afterExpire = System.currentTimeMillis(); remaining = _queue.size(); if ( (remaining > 0) && (_log.shouldLog(Log.DEBUG)) ) _log.debug("Remaining after preprocessing: " + _queue); } if (delayedFlush) { _delayedFlush.reschedule(delayAmount); } //_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 (rv && _log.shouldLog(Log.INFO)) _log.info("PTG remaining to " + _nextHop + " : " + _prequeue.size() + " IB? " + _isInbound + " backlogged? " + backlogged); return rv; } }