diff --git a/router/java/src/net/i2p/router/InNetMessage.java b/router/java/src/net/i2p/router/InNetMessage.java deleted file mode 100644 index cd02548a7..000000000 --- a/router/java/src/net/i2p/router/InNetMessage.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.i2p.router; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import net.i2p.data.Hash; -import net.i2p.data.RouterIdentity; -import net.i2p.data.i2np.I2NPMessage; - -/** - * Wrap an I2NP message received from the network prior to handling and processing. - * - */ -public class InNetMessage { - private RouterContext _context; - private I2NPMessage _message; - private RouterIdentity _fromRouter; - private Hash _fromRouterHash; - private long _created; - - public InNetMessage(RouterContext context) { - _context = context; - setMessage(null); - setFromRouter(null); - setFromRouterHash(null); - context.messageStateMonitor().inboundMessageAdded(); - _created = context.clock().now(); - _context.statManager().createRateStat("inNetMessage.timeToDiscard", - "How long until we discard an inbound msg?", - "InNetMessage", new long[] { 5*60*1000, 30*60*1000, 60*60*1000 }); - } - - /** - * Retrieve the message - * - */ - public I2NPMessage getMessage() { return _message; } - public void setMessage(I2NPMessage msg) { _message = msg; } - - /** - * Hash of the router identity from which this message was received, if availale - * - */ - public Hash getFromRouterHash() { return _fromRouterHash; } - public void setFromRouterHash(Hash routerIdentHash) { _fromRouterHash = routerIdentHash; } - - /** - * Router identity from which this message was received, if availale - * - */ - public RouterIdentity getFromRouter() { return _fromRouter; } - public void setFromRouter(RouterIdentity router) { _fromRouter = router; } - - /** - * Call this after we're done dealing with this message (when we no - * longer need its data) - * - */ - public void processingComplete() { - _message = null; - _context.messageStateMonitor().inboundMessageRead(); - long timeToDiscard = _context.clock().now() - _created; - _context.statManager().addRateData("inNetMessage.timeToDiscard", - timeToDiscard, timeToDiscard); - } - - public void finalize() { - _context.messageStateMonitor().inboundMessageFinalized(); - } - - public String toString() { - StringBuffer buf = new StringBuffer(512); - buf.append("InNetMessage: from [").append(getFromRouter()); - buf.append("] aka [").append(getFromRouterHash()); - buf.append("] message: ").append(getMessage()); - return buf.toString(); - } -} diff --git a/router/java/src/net/i2p/router/TunnelPoolSettings.java b/router/java/src/net/i2p/router/TunnelPoolSettings.java new file mode 100644 index 000000000..14a6e6679 --- /dev/null +++ b/router/java/src/net/i2p/router/TunnelPoolSettings.java @@ -0,0 +1,195 @@ +package net.i2p.router; + +import java.util.Iterator; +import java.util.Properties; + +import net.i2p.data.Hash; + +/** + * Wrap up the settings for a pool of tunnels (duh) + * + */ +public class TunnelPoolSettings { + private Hash _destination; + private String _destinationNickname; + private int _quantity; + private int _backupQuantity; + private int _rebuildPeriod; + private int _duration; + private int _length; + private int _lengthVariance; + private boolean _isInbound; + private boolean _isExploratory; + private boolean _allowZeroHop; + private Properties _unknownOptions; + + /** prefix used to override the router's defaults for clients */ + public static final String PREFIX_DEFAULT = "router.defaultPool."; + /** prefix used to configure the inbound exploratory pool */ + public static final String PREFIX_INBOUND_EXPLORATORY = "router.inboundPool."; + /** prefix used to configure the outbound exploratory pool */ + public static final String PREFIX_OUTBOUND_EXPLORATORY = "router.outboundPool."; + + public static final String PROP_NICKNAME = "nickname"; + public static final String PROP_QUANTITY = "quantity"; + public static final String PROP_BACKUP_QUANTITY = "backupQuantity"; + public static final String PROP_REBUILD_PERIOD = "rebuildPeriod"; + public static final String PROP_DURATION = "duration"; + public static final String PROP_LENGTH = "length"; + public static final String PROP_LENGTH_VARIANCE = "lengthVariance"; + public static final String PROP_ALLOW_ZERO_HOP = "allowZeroHop"; + + public static final int DEFAULT_QUANTITY = 2; + public static final int DEFAULT_BACKUP_QUANTITY = 0; + public static final int DEFAULT_REBUILD_PERIOD = 60*1000; + public static final int DEFAULT_DURATION = 10*60*1000; + public static final int DEFAULT_LENGTH = 2; + public static final int DEFAULT_LENGTH_VARIANCE = -1; + public static final boolean DEFAULT_ALLOW_ZERO_HOP = true; + + public TunnelPoolSettings() { + _quantity = DEFAULT_QUANTITY; + _backupQuantity = DEFAULT_BACKUP_QUANTITY; + _rebuildPeriod = DEFAULT_REBUILD_PERIOD; + _duration = DEFAULT_DURATION; + _length = DEFAULT_LENGTH; + _lengthVariance = DEFAULT_LENGTH_VARIANCE; + _allowZeroHop = DEFAULT_ALLOW_ZERO_HOP; + _isInbound = false; + _isExploratory = false; + _destination = null; + _destinationNickname = null; + _unknownOptions = new Properties(); + } + + /** how many tunnels should be available at all times */ + public int getQuantity() { return _quantity; } + public void setQuantity(int quantity) { _quantity = quantity; } + + /** how many backup tunnels should be kept waiting in the wings */ + public int getBackupQuantity() { return _backupQuantity; } + public void setBackupQuantity(int quantity) { _backupQuantity = quantity; } + + /** how long before tunnel expiration should new tunnels be built */ + public int getRebuildPeriod() { return _rebuildPeriod; } + public void setRebuildPeriod(int periodMs) { _rebuildPeriod = periodMs; } + + /** how many remote hops should be in the tunnel */ + public int getLength() { return _length; } + public void setLength(int length) { _length = length; } + + /** if there are no tunnels to build with, will this pool allow 0 hop tunnels? */ + public boolean getAllowZeroHop() { return _allowZeroHop; } + public void setAllowZeroHop(boolean ok) { _allowZeroHop = ok; } + + /** + * how should the length be varied. if negative, this randomly skews from + * (length - variance) to (length + variance), or if positive, from length + * to (length + variance), inclusive. + * + */ + public int getLengthVariance() { return _lengthVariance; } + public void setLengthVariance(int variance) { _lengthVariance = variance; } + + /** is this an inbound tunnel? */ + public boolean isInbound() { return _isInbound; } + public void setIsInbound(boolean isInbound) { _isInbound = isInbound; } + + /** is this an exploratory tunnel (or a client tunnel) */ + public boolean isExploratory() { return _isExploratory; } + public void setIsExploratory(boolean isExploratory) { _isExploratory = isExploratory; } + + public int getDuration() { return _duration; } + public void setDuration(int ms) { _duration = ms; } + + /** what destination is this a tunnel for (or null if none) */ + public Hash getDestination() { return _destination; } + public void setDestination(Hash dest) { _destination = dest; } + + /** what user supplied name was given to the client connected (can be null) */ + public String getDestinationNickname() { return _destinationNickname; } + public void setDestinationNickname(String name) { _destinationNickname = name; } + + public Properties getUnknownOptions() { return _unknownOptions; } + + public void readFromProperties(String prefix, Properties props) { + for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String value = props.getProperty(name); + if (name.startsWith(prefix)) { + if (name.equalsIgnoreCase(prefix + PROP_ALLOW_ZERO_HOP)) + _allowZeroHop = getBoolean(value, DEFAULT_ALLOW_ZERO_HOP); + else if (name.equalsIgnoreCase(prefix + PROP_BACKUP_QUANTITY)) + _backupQuantity = getInt(value, DEFAULT_BACKUP_QUANTITY); + else if (name.equalsIgnoreCase(prefix + PROP_DURATION)) + _duration = getInt(value, DEFAULT_DURATION); + else if (name.equalsIgnoreCase(prefix + PROP_LENGTH)) + _length = getInt(value, DEFAULT_LENGTH); + else if (name.equalsIgnoreCase(prefix + PROP_LENGTH_VARIANCE)) + _lengthVariance = getInt(value, DEFAULT_LENGTH_VARIANCE); + else if (name.equalsIgnoreCase(prefix + PROP_QUANTITY)) + _quantity = getInt(value, DEFAULT_QUANTITY); + else if (name.equalsIgnoreCase(prefix + PROP_REBUILD_PERIOD)) + _rebuildPeriod = getInt(value, DEFAULT_REBUILD_PERIOD); + else if (name.equalsIgnoreCase(prefix + PROP_NICKNAME)) + _destinationNickname = value; + else + _unknownOptions.setProperty(name.substring((prefix != null ? prefix.length() : 0)), value); + } + } + } + + public void writeToProperties(String prefix, Properties props) { + if (props == null) return; + props.setProperty(prefix + PROP_ALLOW_ZERO_HOP, ""+_allowZeroHop); + props.setProperty(prefix + PROP_BACKUP_QUANTITY, ""+_backupQuantity); + props.setProperty(prefix + PROP_DURATION, ""+_duration); + props.setProperty(prefix + PROP_LENGTH, ""+_length); + props.setProperty(prefix + PROP_LENGTH_VARIANCE, ""+_lengthVariance); + if (_destinationNickname != null) + props.setProperty(prefix + PROP_NICKNAME, ""+_destinationNickname); + props.setProperty(prefix + PROP_QUANTITY, ""+_quantity); + props.setProperty(prefix + PROP_REBUILD_PERIOD, ""+_rebuildPeriod); + for (Iterator iter = _unknownOptions.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String val = _unknownOptions.getProperty(name); + props.setProperty(prefix + name, val); + } + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Properties p = new Properties(); + writeToProperties("", p); + buf.append("Tunnel pool settings:\n"); + buf.append("====================================\n"); + for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String val = p.getProperty(name); + buf.append(name).append(" = [").append(val).append("]\n"); + } + buf.append("is inbound? ").append(_isInbound).append("\n"); + buf.append("is exploratory? ").append(_isExploratory).append("\n"); + buf.append("====================================\n"); + return buf.toString(); + } + + //// + //// + + private static final boolean getBoolean(String str, boolean defaultValue) { + if (str == null) return defaultValue; + boolean v = "TRUE".equalsIgnoreCase(str) || "YES".equalsIgnoreCase(str); + return v; + } + private static final int getInt(String str, int defaultValue) { return (int)getLong(str, defaultValue); } + private static final long getLong(String str, long defaultValue) { + if (str == null) return defaultValue; + try { + long val = Long.parseLong(str); + return val; + } catch (NumberFormatException nfe) { + return defaultValue; + } + } +} diff --git a/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java new file mode 100644 index 000000000..9dca4ac65 --- /dev/null +++ b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java @@ -0,0 +1,115 @@ +package net.i2p.router.message; +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.PrivateKey; +import net.i2p.data.RouterIdentity; +import net.i2p.data.i2np.DeliveryInstructions; +import net.i2p.data.i2np.GarlicClove; +import net.i2p.data.i2np.GarlicMessage; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.LeaseSetKeys; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * Unencrypt a garlic message and pass off any valid cloves to the configured + * receiver to dispatch as they choose. + * + */ +public class GarlicMessageReceiver { + private RouterContext _context; + private Log _log; + private CloveReceiver _receiver; + private Hash _clientDestination; + private GarlicMessageParser _parser; + + private final static int FORWARD_PRIORITY = 50; + + public interface CloveReceiver { + public void handleClove(DeliveryInstructions instructions, I2NPMessage data); + } + + public GarlicMessageReceiver(RouterContext context, CloveReceiver receiver) { + this(context, receiver, null); + } + public GarlicMessageReceiver(RouterContext context, CloveReceiver receiver, Hash clientDestination) { + _context = context; + _log = context.logManager().getLog(GarlicMessageReceiver.class); + _context.statManager().createRateStat("crypto.garlic.decryptFail", "How often garlic messages are undecryptable", "Encryption", new long[] { 5*60*1000, 60*60*1000, 24*60*60*1000 }); + _clientDestination = clientDestination; + _parser = new GarlicMessageParser(context); + _receiver = receiver; + } + + public void receive(GarlicMessage message) { + PrivateKey decryptionKey = null; + if (_clientDestination != null) { + LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination); + if (keys != null) { + decryptionKey = keys.getDecryptionKey(); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Not trying to decrypt a garlic routed message to a disconnected client"); + return; + } + } else { + decryptionKey = _context.keyManager().getPrivateKey(); + } + + CloveSet set = _parser.getGarlicCloves(message, decryptionKey); + if (set != null) { + for (int i = 0; i < set.getCloveCount(); i++) { + GarlicClove clove = set.getClove(i); + handleClove(clove); + } + } else { + if (_log.shouldLog(Log.ERROR)) + _log.error("CloveMessageParser failed to decrypt the message [" + message.getUniqueId() + + "]", new Exception("Decrypt garlic failed")); + _context.statManager().addRateData("crypto.garlic.decryptFail", 1, 0); + _context.messageHistory().messageProcessingError(message.getUniqueId(), + message.getClass().getName(), + "Garlic could not be decrypted"); + } + } + + /** + * Validate and pass off any valid cloves to the receiver + * + */ + private void handleClove(GarlicClove clove) { + if (!isValid(clove)) { + if (_log.shouldLog(Log.DEBUG)) + _log.warn("Invalid clove " + clove); + return; + } + if (_receiver != null) + _receiver.handleClove(clove.getInstructions(), clove.getData()); + } + + private boolean isValid(GarlicClove clove) { + boolean valid = _context.messageValidator().validateMessage(clove.getCloveId(), + clove.getExpiration().getTime()); + if (!valid) { + String howLongAgo = DataHelper.formatDuration(_context.clock().now()-clove.getExpiration().getTime()); + if (_log.shouldLog(Log.ERROR)) + _log.error("Clove is NOT valid: id=" + clove.getCloveId() + + " expiration " + howLongAgo + " ago", new Exception("Invalid within...")); + _context.messageHistory().messageProcessingError(clove.getCloveId(), + clove.getData().getClass().getName(), + "Clove is not valid (expiration " + howLongAgo + " ago)"); + } + return valid; + } +} diff --git a/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java b/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java deleted file mode 100644 index f88cdc373..000000000 --- a/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java +++ /dev/null @@ -1,672 +0,0 @@ -package net.i2p.router.message; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; - -import net.i2p.data.DataFormatException; -import net.i2p.data.DataHelper; -import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.data.Payload; -import net.i2p.data.RouterIdentity; -import net.i2p.data.SessionKey; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DataMessage; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.I2NPMessageException; -import net.i2p.data.i2np.I2NPMessageHandler; -import net.i2p.data.i2np.TunnelMessage; -import net.i2p.data.i2np.TunnelVerificationStructure; -import net.i2p.router.ClientMessage; -import net.i2p.router.InNetMessage; -import net.i2p.router.Job; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageReceptionInfo; -import net.i2p.router.MessageSelector; -import net.i2p.router.ReplyJob; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.util.Log; - -public class HandleTunnelMessageJob extends JobImpl { - private Log _log; - private TunnelMessage _message; - private RouterIdentity _from; - private Hash _fromHash; - private I2NPMessageHandler _handler; - - private final static int FORWARD_TIMEOUT = 60*1000; - private final static int FORWARD_PRIORITY = 400; - - public HandleTunnelMessageJob(RouterContext ctx, TunnelMessage msg, RouterIdentity from, Hash fromHash) { - super(ctx); - _log = ctx.logManager().getLog(HandleTunnelMessageJob.class); - _handler = new I2NPMessageHandler(ctx); - ctx.statManager().createRateStat("tunnel.unknownTunnelTimeLeft", "How much time is left on tunnel messages we receive that are for unknown tunnels?", "Tunnels", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("tunnel.gatewayMessageSize", "How large are the messages we are forwarding on as an inbound gateway?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("tunnel.relayMessageSize", "How large are the messages we are forwarding on as a participant in a tunnel?", "Tunnels", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("tunnel.endpointMessageSize", "How large are the messages we are forwarding in as an outbound endpoint?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("tunnel.expiredAfterAcceptTime", "How long after expiration do we finally start running an expired tunnel message?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _message = msg; - _from = from; - _fromHash = fromHash; - } - - private TunnelInfo validate(TunnelId id) { - long excessLag = getContext().clock().now() - _message.getMessageExpiration().getTime(); - if (excessLag > Router.CLOCK_FUDGE_FACTOR) { - // expired while on the queue - if (_log.shouldLog(Log.WARN)) - _log.warn("Accepted message (" + _message.getUniqueId() + ") expired on the queue for tunnel " - + id.getTunnelId() + " expiring " - + excessLag - + "ms ago"); - getContext().statManager().addRateData("tunnel.expiredAfterAcceptTime", excessLag, excessLag); - getContext().messageHistory().messageProcessingError(_message.getUniqueId(), - TunnelMessage.class.getName(), - "tunnel message expired on the queue"); - return null; - } else if (excessLag > 0) { - // almost expired while on the queue - if (_log.shouldLog(Log.WARN)) - _log.warn("Accepted message (" + _message.getUniqueId() + ") *almost* expired on the queue for tunnel " - + id.getTunnelId() + " expiring " - + excessLag - + "ms ago"); - } else { - // not expired - } - - TunnelInfo info = getContext().tunnelManager().getTunnelInfo(id); - - if (info == null) { - Hash from = _fromHash; - if (_from != null) - from = _from.getHash(); - getContext().messageHistory().droppedTunnelMessage(id, _message.getUniqueId(), - _message.getMessageExpiration(), - from); - if (_log.shouldLog(Log.ERROR)) - _log.error("Received a message for an unknown tunnel [" + id.getTunnelId() - + "], dropping it: " + _message, getAddedBy()); - long timeRemaining = _message.getMessageExpiration().getTime() - getContext().clock().now(); - getContext().statManager().addRateData("tunnel.unknownTunnelTimeLeft", timeRemaining, 0); - long lag = getTiming().getActualStart() - getTiming().getStartAfter(); - if (_log.shouldLog(Log.ERROR)) - _log.error("Lag processing a dropped tunnel message: " + lag); - return null; - } - - info = getUs(info); - if (info == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("We are not part of a known tunnel?? wtf! drop.", getAddedBy()); - long timeRemaining = _message.getMessageExpiration().getTime() - getContext().clock().now(); - getContext().statManager().addRateData("tunnel.unknownTunnelTimeLeft", timeRemaining, 0); - return null; - } - - return info; - } - - /** - * The current router may be the gateway to the tunnel since there is no - * verification data, or it could be a b0rked message. - * - */ - private void receiveUnverified(TunnelInfo info) { - if (info.getSigningKey() != null) { - if (info.getNextHop() != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("We are the gateway to tunnel " + info.getTunnelId().getTunnelId()); - byte data[] = _message.getData(); - I2NPMessage msg = getBody(data); - getContext().jobQueue().addJob(new HandleGatewayMessageJob(getContext(), msg, info, data.length)); - return; - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("We are the gateway and the endpoint for tunnel " + info.getTunnelId().getTunnelId()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Process locally"); - if (info.getDestination() != null) { - if (!getContext().clientManager().isLocal(info.getDestination())) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Received a message on a tunnel allocated to a client that has disconnected - dropping it!"); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Dropping message for disconnected client: " + _message); - - getContext().messageHistory().droppedOtherMessage(_message); - getContext().messageHistory().messageProcessingError(_message.getUniqueId(), - _message.getClass().getName(), - "Disconnected client"); - return; - } - } - - I2NPMessage body = getBody(_message.getData()); - if (body != null) { - getContext().jobQueue().addJob(new HandleLocallyJob(getContext(), body, info)); - return; - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("Body is null! content of message.getData() = [" + - DataHelper.toString(_message.getData()) + "]", getAddedBy()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Message that failed: " + _message, getAddedBy()); - return; - } - } - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("Received a message that we are not the gateway for on tunnel " - + info.getTunnelId().getTunnelId() - + " without a verification structure: " + _message, getAddedBy()); - return; - } - } - - /** - * We may be a participant in the tunnel, as there is a verification structure. - * - */ - private void receiveParticipant(TunnelInfo info) { - // participant - TunnelVerificationStructure struct = _message.getVerificationStructure(); - boolean ok = struct.verifySignature(getContext(), info.getVerificationKey().getKey()); - if (!ok) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Failed tunnel verification! Spoofing / tagging attack? " + _message, getAddedBy()); - return; - } else { - if (info.getNextHop() != null) { - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + info.getTunnelId().getTunnelId() - + " received where we're not the gateway and there are remaining hops, so forward it on to " - + info.getNextHop().toBase64() + " via SendTunnelMessageJob"); - - getContext().statManager().addRateData("tunnel.relayMessageSize", - _message.getData().length, 0); - - TunnelMessage msg = new TunnelMessage(getContext()); - msg.setData(_message.getData()); - msg.setEncryptedDeliveryInstructions(_message.getEncryptedDeliveryInstructions()); - msg.setTunnelId(info.getNextHopId()); - msg.setVerificationStructure(_message.getVerificationStructure()); - msg.setMessageExpiration(_message.getMessageExpiration()); - - int timeoutMs = (int)(_message.getMessageExpiration().getTime() - getContext().clock().now()); - timeoutMs += Router.CLOCK_FUDGE_FACTOR; - - if (timeoutMs < 1000) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Message " + _message.getUniqueId() + " is valid and we would pass it on through tunnel " - + info.getTunnelId().getTunnelId() + ", but its too late (expired " + timeoutMs + "ms ago)"); - return; - } - - SendMessageDirectJob j = new SendMessageDirectJob(getContext(), msg, - info.getNextHop(), - timeoutMs, - FORWARD_PRIORITY); - getContext().jobQueue().addJob(j); - return; - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("No more hops, unwrap and follow the instructions"); - getContext().jobQueue().addJob(new HandleEndpointJob(getContext(), info)); - return; - } - } - } - - public String getName() { return "Handle Inbound Tunnel Message"; } - public void runJob() { - TunnelId id = _message.getTunnelId(); - - TunnelInfo info = validate(id); - if (info == null) - return; - - info.messageProcessed(_message.getMessageSize()); - - //if ( (_message.getVerificationStructure() == null) && (info.getSigningKey() != null) ) { - if (_message.getVerificationStructure() == null) { - receiveUnverified(info); - } else { - receiveParticipant(info); - } - } - - private void processLocally(TunnelInfo ourPlace) { - if (ourPlace.getEncryptionKey() == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Argh, somehow we don't have the decryption key and we have no more steps", getAddedBy()); - return; - } - DeliveryInstructions instructions = getInstructions(_message.getEncryptedDeliveryInstructions(), - ourPlace.getEncryptionKey().getKey()); - if (instructions == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("We are the endpoint of a non-zero length tunnel and we don't have instructions. DROP.", getAddedBy()); - return; - } else { - I2NPMessage body = null; - if (instructions.getEncrypted()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Body in the tunnel IS encrypted"); - body = decryptBody(_message.getData(), instructions.getEncryptionKey()); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Body in the tunnel is NOT encrypted: " + instructions - + "\n" + _message, new Exception("Hmmm...")); - body = getBody(_message.getData()); - } - - if (body == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Unable to recover the body from the tunnel", getAddedBy()); - return; - } else { - getContext().jobQueue().addJob(new ProcessBodyLocallyJob(getContext(), body, instructions, ourPlace)); - } - } - } - - private void honorInstructions(DeliveryInstructions instructions, I2NPMessage body) { - getContext().statManager().addRateData("tunnel.endpointMessageSize", _message.getData().length, 0); - - switch (instructions.getDeliveryMode()) { - case DeliveryInstructions.DELIVERY_MODE_LOCAL: - sendToLocal(body); - break; - case DeliveryInstructions.DELIVERY_MODE_ROUTER: - if (getContext().routerHash().equals(instructions.getRouter())) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Delivery instructions point at a router, but we're that router, so send to local"); - sendToLocal(body); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Delivery instructions point at a router, and we're not that router, so forward it off"); - sendToRouter(instructions.getRouter(), body); - } - break; - case DeliveryInstructions.DELIVERY_MODE_TUNNEL: - sendToTunnel(instructions.getRouter(), instructions.getTunnelId(), body); - break; - case DeliveryInstructions.DELIVERY_MODE_DESTINATION: - sendToDest(instructions.getDestination(), body); - break; - } - } - - private void sendToDest(Hash dest, I2NPMessage body) { - if (body instanceof DataMessage) { - boolean isLocal = getContext().clientManager().isLocal(dest); - if (isLocal) { - deliverMessage(null, dest, (DataMessage)body); - return; - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("Delivery to remote destinations is not yet supported", getAddedBy()); - return; - } - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("Deliver something other than a DataMessage to a Destination? I don't think so."); - return; - } - } - - private void sendToTunnel(Hash router, TunnelId id, I2NPMessage body) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending on to requested tunnel " + id.getTunnelId() + " on router " - + router.toBase64()); - - int timeoutMs = (int)(body.getMessageExpiration().getTime() - getContext().clock().now()); - if (timeoutMs < 5000) - timeoutMs = FORWARD_TIMEOUT; - - TunnelInfo curInfo = getContext().tunnelManager().getTunnelInfo(_message.getTunnelId()); - if (curInfo == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel went away (" + _message.getTunnelId() + ")"); - return; - } - if (curInfo.getTunnelId().getType() != TunnelId.TYPE_INBOUND) { - // we are not processing a request at the end of an inbound tunnel, so - // there's no reason to hide our location. honor the request directly - - TunnelMessage msg = new TunnelMessage(getContext()); - msg.setTunnelId(id); - msg.setData(body.toByteArray()); - - getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), msg, router, timeoutMs, FORWARD_PRIORITY)); - - String bodyType = body.getClass().getName(); - getContext().messageHistory().wrap(bodyType, body.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId()); - } else { - // the instructions request that we forward a message remotely from - // the hidden location. honor it by sending it out a tunnel - TunnelId outTunnelId = selectOutboundTunnelId(); - if (outTunnelId == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("No outbound tunnels available to forward the message, dropping it"); - return; - } - getContext().jobQueue().addJob(new SendTunnelMessageJob(getContext(), body, outTunnelId, router, id, - null, null, null, null, timeoutMs, FORWARD_PRIORITY)); - } - } - - private TunnelId selectOutboundTunnelId() { - TunnelSelectionCriteria criteria = new TunnelSelectionCriteria(); - criteria.setMinimumTunnelsRequired(1); - criteria.setMaximumTunnelsRequired(1); - List ids = getContext().tunnelManager().selectOutboundTunnelIds(criteria); - if ( (ids == null) || (ids.size() <= 0) ) - return null; - else - return (TunnelId)ids.get(0); - } - - private void sendToRouter(Hash router, I2NPMessage body) { - // TODO: we may want to send it via a tunnel later on, but for now, direct will do. - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending on to requested router " + router.toBase64()); - int timeoutMs = (int)(body.getMessageExpiration().getTime() - getContext().clock().now()); - if (timeoutMs < 5000) - timeoutMs = FORWARD_TIMEOUT; - getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), body, router, timeoutMs, FORWARD_PRIORITY)); - } - - private void sendToLocal(I2NPMessage body) { - InNetMessage msg = new InNetMessage(getContext()); - msg.setMessage(body); - msg.setFromRouter(_from); - msg.setFromRouterHash(_fromHash); - getContext().inNetMessagePool().add(msg); - } - - private void deliverMessage(Destination dest, Hash destHash, DataMessage msg) { - boolean valid = getContext().messageValidator().validateMessage(msg.getUniqueId(), msg.getMessageExpiration().getTime()); - if (!valid) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Duplicate data message received [" + msg.getUniqueId() - + " expiring on " + msg.getMessageExpiration() + "]"); - getContext().messageHistory().droppedOtherMessage(msg); - getContext().messageHistory().messageProcessingError(msg.getUniqueId(), msg.getClass().getName(), - "Duplicate payload"); - return; - } - - ClientMessage cmsg = new ClientMessage(); - - Payload payload = new Payload(); - payload.setEncryptedData(msg.getData()); - - MessageReceptionInfo info = new MessageReceptionInfo(); - info.setFromPeer(_fromHash); - info.setFromTunnel(_message.getTunnelId()); - - cmsg.setDestination(dest); - cmsg.setDestinationHash(destHash); - cmsg.setPayload(payload); - cmsg.setReceptionInfo(info); - - getContext().messageHistory().receivePayloadMessage(msg.getUniqueId()); - // if the destination isn't local, the ClientMessagePool forwards it off as an OutboundClientMessageJob - getContext().clientMessagePool().add(cmsg); - } - - private I2NPMessage getBody(byte body[]) { - try { - return _handler.readMessage(body); // new ByteArrayInputStream(body)); - } catch (I2NPMessageException ime) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error parsing the message body", ime); - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error parsing the message body", ioe); - } - return null; - } - - private I2NPMessage decryptBody(byte encryptedMessage[], SessionKey key) { - byte iv[] = new byte[16]; - Hash h = getContext().sha().calculateHash(key.getData()); - System.arraycopy(h.getData(), 0, iv, 0, iv.length); - byte decrypted[] = getContext().aes().safeDecrypt(encryptedMessage, key, iv); - if (decrypted == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error decrypting the message", getAddedBy()); - return null; - } - if (decrypted.length <= 0) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Received an empty decrypted message? encrypted length: " + encryptedMessage.length, getAddedBy()); - return null; - } - return getBody(decrypted); - } - - private DeliveryInstructions getInstructions(byte encryptedInstructions[], SessionKey key) { - try { - byte iv[] = new byte[16]; - Hash h = getContext().sha().calculateHash(key.getData()); - System.arraycopy(h.getData(), 0, iv, 0, iv.length); - byte decrypted[] = getContext().aes().safeDecrypt(encryptedInstructions, key, iv); - if (decrypted == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error decrypting the instructions", getAddedBy()); - return null; - } - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.readBytes(new ByteArrayInputStream(decrypted)); - return instructions; - } catch (DataFormatException dfe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error parsing the decrypted instructions", dfe); - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error reading the decrypted instructions", ioe); - } - return null; - } - - private TunnelInfo getUs(TunnelInfo info) { - Hash us = getContext().routerHash(); - while (info != null) { - if (us.equals(info.getThisHop())) - return info; - info = info.getNextHopInfo(); - } - return null; - } - - private boolean validateMessage(TunnelMessage msg, TunnelInfo info) { - TunnelVerificationStructure vstruct = _message.getVerificationStructure(); - if (vstruct == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Verification structure missing. invalid"); - return false; - } - - if ( (info.getVerificationKey() == null) || (info.getVerificationKey().getKey() == null) ) { - if (_log.shouldLog(Log.ERROR)) - _log.error("wtf, no verification key for the tunnel? " + info, getAddedBy()); - return false; - } - - if (!vstruct.verifySignature(getContext(), info.getVerificationKey().getKey())) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Received a tunnel message with an invalid signature!"); - // shitlist the sender? - return false; - } - - // now validate the message - Hash msgHash = getContext().sha().calculateHash(_message.getData()); - if (msgHash.equals(vstruct.getMessageHash())) { - // hash matches. good. - return true; - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("validateMessage: Signed hash does not match real hash. Data has been tampered with!"); - // shitlist the sender! - return false; - } - } - - public void dropped() { - getContext().messageHistory().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), - "Dropped due to overload"); - } - - //// - // series of subjobs for breaking this task into smaller components - //// - - /** we're the gateway, lets deal */ - private class HandleGatewayMessageJob extends JobImpl { - private I2NPMessage _body; - private int _length; - private TunnelInfo _info; - - public HandleGatewayMessageJob(RouterContext enclosingContext, I2NPMessage body, TunnelInfo tunnel, int length) { - super(enclosingContext); - _body = body; - _length = length; - _info = tunnel; - } - public void runJob() { - RouterContext ctx = HandleTunnelMessageJob.this.getContext(); - if (_body != null) { - long expiration = _body.getMessageExpiration().getTime(); - long timeout = expiration - ctx.clock().now(); - ctx.statManager().addRateData("tunnel.gatewayMessageSize", _length, 0); - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + _info.getTunnelId() - + " received at the gateway (us), and since its > 0 length, forward the " - + _body.getClass().getName() + " message on to " - + _info.getNextHop().toBase64() + " via SendTunnelMessageJob expiring in " - + timeout + "ms"); - - MessageSelector selector = null; - Job onFailure = null; - Job onSuccess = null; - ReplyJob onReply = null; - Hash targetRouter = null; - TunnelId targetTunnelId = null; - SendTunnelMessageJob j = new SendTunnelMessageJob(ctx, _body, _info.getNextHopId(), targetRouter, targetTunnelId, onSuccess, onReply, onFailure, selector, timeout, FORWARD_PRIORITY); - ctx.jobQueue().addJob(j); - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn("Body of the message for the tunnel could not be parsed"); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Message that failed: " + _message); - } - } - public String getName() { return "Handle Tunnel Message (gateway)"; } - } - - /** zero hop tunnel */ - private class HandleLocallyJob extends JobImpl { - private I2NPMessage _body; - private TunnelInfo _info; - - public HandleLocallyJob(RouterContext enclosingContext, I2NPMessage body, TunnelInfo tunnel) { - super(enclosingContext); - _body = body; - _info = tunnel; - } - - public void runJob() { - if (_body instanceof DataMessage) { - // we know where to send it and its something a client can handle, so lets send 'er to the client - if (_log.shouldLog(Log.WARN)) - _log.debug("Deliver the message to a local client, as its a payload message and we know the destination"); - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + _info.getTunnelId() + " received at the gateway (us), but its a 0 length tunnel and the message is a DataMessage, so send it to " - + _info.getDestination().calculateHash().toBase64()); - deliverMessage(_info.getDestination(), null, (DataMessage)_body); - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + _info.getTunnelId() + - " received at the gateway (us), but its a 0 length tunnel though it is a " - + _body.getClass().getName() + ", so process it locally"); - InNetMessage msg = new InNetMessage(HandleLocallyJob.this.getContext()); - msg.setFromRouter(_from); - msg.setFromRouterHash(_fromHash); - msg.setMessage(_body); - HandleLocallyJob.this.getContext().inNetMessagePool().add(msg); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Message added to Inbound network pool for local processing: " + _message); - } - } - public String getName() { return "Handle Tunnel Message (0 hop)"; } - } - - /** we're the endpoint of the inbound tunnel */ - private class HandleEndpointJob extends JobImpl { - private TunnelInfo _info; - public HandleEndpointJob(RouterContext enclosingContext, TunnelInfo info) { - super(enclosingContext); - _info = info; - } - public void runJob() { - processLocally(_info); - } - public String getName() { return "Handle Tunnel Message (inbound endpoint)"; } - } - - /** endpoint of outbound 1+ hop tunnel with instructions */ - private class ProcessBodyLocallyJob extends JobImpl { - private I2NPMessage _body; - private TunnelInfo _ourPlace; - private DeliveryInstructions _instructions; - public ProcessBodyLocallyJob(RouterContext enclosingContext, I2NPMessage body, DeliveryInstructions instructions, TunnelInfo ourPlace) { - super(enclosingContext); - _body = body; - _instructions = instructions; - _ourPlace = ourPlace; - } - public void runJob() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Body read: " + _body); - if ( (_ourPlace.getDestination() != null) && (_body instanceof DataMessage) ) { - // we know where to send it and its something a client can handle, so lets send 'er to the client - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Deliver the message to a local client, as its a payload message and we know the destination"); - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + _ourPlace.getTunnelId().getTunnelId() - + " received where we're the endpoint containing a DataMessage message, so deliver it to " - + _ourPlace.getDestination().calculateHash().toBase64()); - deliverMessage(_ourPlace.getDestination(), null, (DataMessage)_body); - return; - } else { - // Honor the delivery instructions - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + _ourPlace.getTunnelId().getTunnelId() - + " received where we're the endpoint containing a " - + _body.getClass().getName() + " message, so honor the delivery instructions: " - + _instructions.toString()); - honorInstructions(_instructions, _body); - return; - } - } - public String getName() { return "Handle Tunnel Message (outbound endpoint)"; } - } -} diff --git a/router/java/src/net/i2p/router/message/MessageHandler.java b/router/java/src/net/i2p/router/message/MessageHandler.java deleted file mode 100644 index 08c7b2843..000000000 --- a/router/java/src/net/i2p/router/message/MessageHandler.java +++ /dev/null @@ -1,201 +0,0 @@ -package net.i2p.router.message; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.io.ByteArrayOutputStream; - -import java.util.List; - -import net.i2p.data.Hash; -import net.i2p.data.Payload; -import net.i2p.data.RouterIdentity; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DataMessage; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelMessage; -import net.i2p.router.ClientMessage; -import net.i2p.router.InNetMessage; -import net.i2p.router.MessageReceptionInfo; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.util.Log; - -/** - * Implement the inbound message processing logic to forward based on delivery instructions and - * send acks. - * - */ -class MessageHandler { - private Log _log; - private RouterContext _context; - - public MessageHandler(RouterContext ctx) { - _context = ctx; - _log = _context.logManager().getLog(MessageHandler.class); - } - - public void handleMessage(DeliveryInstructions instructions, I2NPMessage message, - long replyId, RouterIdentity from, Hash fromHash, - long expiration, int priority, boolean sendDirect) { - switch (instructions.getDeliveryMode()) { - case DeliveryInstructions.DELIVERY_MODE_LOCAL: - _log.debug("Instructions for LOCAL DELIVERY"); - if (message.getType() == DataMessage.MESSAGE_TYPE) { - handleLocalDestination(instructions, message, fromHash); - } else { - handleLocalRouter(message, from, fromHash); - } - break; - case DeliveryInstructions.DELIVERY_MODE_ROUTER: - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions for ROUTER DELIVERY to " - + instructions.getRouter().toBase64()); - if (_context.routerHash().equals(instructions.getRouter())) { - handleLocalRouter(message, from, fromHash); - } else { - handleRemoteRouter(message, instructions, expiration, priority); - } - break; - case DeliveryInstructions.DELIVERY_MODE_DESTINATION: - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions for DESTINATION DELIVERY to " - + instructions.getDestination().toBase64()); - if (_context.clientManager().isLocal(instructions.getDestination())) { - handleLocalDestination(instructions, message, fromHash); - } else { - _log.error("Instructions requests forwarding on to a non-local destination. Not yet supported"); - } - break; - case DeliveryInstructions.DELIVERY_MODE_TUNNEL: - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions for TUNNEL DELIVERY to" - + instructions.getTunnelId().getTunnelId() + " on " - + instructions.getRouter().toBase64()); - handleTunnel(instructions, expiration, message, priority, sendDirect); - break; - default: - _log.error("Message has instructions that are not yet implemented: mode = " + instructions.getDeliveryMode()); - } - - } - - private void handleLocalRouter(I2NPMessage message, RouterIdentity from, Hash fromHash) { - _log.info("Handle " + message.getClass().getName() + " to a local router - toss it on the inbound network pool"); - InNetMessage msg = new InNetMessage(_context); - msg.setFromRouter(from); - msg.setFromRouterHash(fromHash); - msg.setMessage(message); - _context.inNetMessagePool().add(msg); - } - - private void handleRemoteRouter(I2NPMessage message, DeliveryInstructions instructions, - long expiration, int priority) { - boolean valid = _context.messageValidator().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime()); - if (!valid) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Duplicate / expired message received to remote router [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]"); - _context.messageHistory().droppedOtherMessage(message); - _context.messageHistory().messageProcessingError(message.getUniqueId(), message.getClass().getName(), "Duplicate/expired to remote router"); - return; - } - - if (_log.shouldLog(Log.INFO)) - _log.info("Handle " + message.getClass().getName() + " to a remote router " - + instructions.getRouter().toBase64() + " - fire a SendMessageDirectJob"); - int timeoutMs = (int)(expiration-_context.clock().now()); - SendMessageDirectJob j = new SendMessageDirectJob(_context, message, instructions.getRouter(), timeoutMs, priority); - _context.jobQueue().addJob(j); - } - - private void handleTunnel(DeliveryInstructions instructions, long expiration, I2NPMessage message, int priority, boolean direct) { - Hash to = instructions.getRouter(); - long timeoutMs = expiration - _context.clock().now(); - TunnelId tunnelId = instructions.getTunnelId(); - - if (!_context.routerHash().equals(to)) { - // don't validate locally targetted tunnel messages, since then we'd have to tweak - // around message validation thats already in place for SendMessageDirectJob - boolean valid = _context.messageValidator().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime()); - if (!valid) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Duplicate / expired tunnel message received [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]"); - _context.messageHistory().droppedOtherMessage(message); - _context.messageHistory().messageProcessingError(message.getUniqueId(), - message.getClass().getName(), - "Duplicate/expired"); - return; - } - } - - if (direct) { - if (_log.shouldLog(Log.INFO)) - _log.info("Handle " + message.getClass().getName() + " to send to remote tunnel " - + tunnelId.getTunnelId() + " on router " + to.toBase64()); - TunnelMessage msg = new TunnelMessage(_context); - msg.setData(message.toByteArray()); - msg.setTunnelId(tunnelId); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Placing message of type " + message.getClass().getName() - + " into the new tunnel message bound for " + tunnelId.getTunnelId() - + " on " + to.toBase64()); - _context.jobQueue().addJob(new SendMessageDirectJob(_context, msg, to, (int)timeoutMs, priority)); - - String bodyType = message.getClass().getName(); - _context.messageHistory().wrap(bodyType, message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId()); - } else { - // we received a message with instructions to send it somewhere, but we shouldn't - // expose where we are in the process of honoring it. so, send it out a tunnel - TunnelId outTunnelId = selectOutboundTunnelId(); - if (outTunnelId == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("No outbound tunnels available to forward the message, dropping it"); - return; - } - _context.jobQueue().addJob(new SendTunnelMessageJob(_context, message, outTunnelId, to, tunnelId, - null, null, null, null, timeoutMs, priority)); - } - } - - private TunnelId selectOutboundTunnelId() { - TunnelSelectionCriteria criteria = new TunnelSelectionCriteria(); - criteria.setMinimumTunnelsRequired(1); - criteria.setMaximumTunnelsRequired(1); - List ids = _context.tunnelManager().selectOutboundTunnelIds(criteria); - if ( (ids == null) || (ids.size() <= 0) ) - return null; - else - return (TunnelId)ids.get(0); - } - - private void handleLocalDestination(DeliveryInstructions instructions, I2NPMessage message, Hash fromHash) { - boolean valid = _context.messageValidator().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime()); - if (!valid) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Duplicate / expired client message received [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]"); - _context.messageHistory().droppedOtherMessage(message); - _context.messageHistory().messageProcessingError(message.getUniqueId(), message.getClass().getName(), "Duplicate/expired client message"); - return; - } - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Handle " + message.getClass().getName() - + " to a local destination - build a ClientMessage and pool it"); - ClientMessage msg = new ClientMessage(); - msg.setDestinationHash(instructions.getDestination()); - Payload payload = new Payload(); - payload.setEncryptedData(((DataMessage)message).getData()); - msg.setPayload(payload); - MessageReceptionInfo info = new MessageReceptionInfo(); - info.setFromPeer(fromHash); - msg.setReceptionInfo(info); - _context.messageHistory().receivePayloadMessage(message.getUniqueId()); - _context.clientMessagePool().add(msg); - } -} diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java deleted file mode 100644 index 5731399eb..000000000 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java +++ /dev/null @@ -1,635 +0,0 @@ -package net.i2p.router.message; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; -import java.util.Set; -import java.util.TreeMap; - -import net.i2p.data.Certificate; -import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.data.Lease; -import net.i2p.data.LeaseSet; -import net.i2p.data.Payload; -import net.i2p.data.PublicKey; -import net.i2p.data.SessionKey; -import net.i2p.data.TunnelId; -import net.i2p.data.i2cp.MessageId; -import net.i2p.data.i2np.DataMessage; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.DeliveryStatusMessage; -import net.i2p.data.i2np.GarlicMessage; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.router.ClientMessage; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageSelector; -import net.i2p.router.ReplyJob; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.util.Log; - -/** - * Send a client message, taking into consideration the fact that there may be - * multiple inbound tunnels that the target provides. This job sends it to one - * of them and if it doesnt get a confirmation within a few seconds (getSendTimeout()), - * it tries the next, continuing on until a confirmation is received, the full - * timeout has been reached (60 seconds, or the ms defined in the client's or - * router's "clientMessageTimeout" option). - * - * After sending through all of the leases without success, if there's still - * time left it fails the leaseSet itself, does a new search for that leaseSet, - * and continues sending down any newly found leases. - * - */ -public class OutboundClientMessageJob extends JobImpl { - private Log _log; - private OutboundClientMessageStatus _status; - private NextStepJob _nextStep; - private LookupLeaseSetFailedJob _lookupLeaseSetFailed; - private long _overallExpiration; - private boolean _shouldBundle; - - /** - * final timeout (in milliseconds) that the outbound message will fail in. - * This can be overridden in the router.config or the client's session config - * (the client's session config takes precedence) - */ - public final static String OVERALL_TIMEOUT_MS_PARAM = "clientMessageTimeout"; - private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000; - - /** how long for each send do we allow before going on to the next? */ - private final static long DEFAULT_SEND_PARTIAL_TIMEOUT = 50*1000; - private static final String PROP_SEND_PARTIAL_TIMEOUT = "router.clientPartialSendTimeout"; - - /** priority of messages, that might get honored some day... */ - private final static int SEND_PRIORITY = 500; - - /** dont search for the lease more than 6 times */ - private final static int MAX_LEASE_LOOKUPS = 6; - - /** - * If the client's config specifies shouldBundleReplyInfo=true, messages sent from - * that client to any peers will probabalistically include the sending destination's - * current LeaseSet (allowing the recipient to reply without having to do a full - * netDb lookup). This should improve performance during the initial negotiations, - * but is not necessary for communication that isn't bidirectional. - * - */ - public static final String BUNDLE_REPLY_LEASESET = "shouldBundleReplyInfo"; - /** - * Allow the override of the frequency of bundling the reply info in with a message. - * The client app can specify bundleReplyInfoProbability=80 (for instance) and that - * will cause the router to include the sender's leaseSet with 80% of the messages - * sent to the peer. - * - */ - public static final String BUNDLE_PROBABILITY = "bundleReplyInfoProbability"; - /** - * How often do messages include the reply leaseSet (out of every 100 tries). - * Including it each time is probably overkill, but who knows. - */ - private static final int BUNDLE_PROBABILITY_DEFAULT = 100; - - /** - * Send the sucker - */ - public OutboundClientMessageJob(RouterContext ctx, ClientMessage msg) { - super(ctx); - _log = ctx.logManager().getLog(OutboundClientMessageJob.class); - - ctx.statManager().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.sendAttemptAverage", "How many different tunnels do we have to try when sending a client message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.sendAckTime", "How long does it take to get an ACK back from a message?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.sendsPerFailure", "How many send attempts do we make when they all fail?", "ClientMessages", new long[] { 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.timeoutCongestionTunnel", "How lagged our tunnels are when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.timeoutCongestionMessage", "How fast we process messages locally when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("client.timeoutCongestionInbound", "How much faster we are receiving data than our average bps when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - - long timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT; - - String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM); - if (param == null) - param = ctx.router().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM); - if (param != null) { - try { - timeoutMs = Long.parseLong(param); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Invalid client message timeout specified [" + param - + "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe); - timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT; - } - } - - _overallExpiration = timeoutMs + getContext().clock().now(); - _status = new OutboundClientMessageStatus(ctx, msg); - _nextStep = new NextStepJob(getContext()); - _lookupLeaseSetFailed = new LookupLeaseSetFailedJob(getContext()); - _shouldBundle = getShouldBundle(); - } - - private long getSendTimeout() { - String timeout = getContext().getProperty(PROP_SEND_PARTIAL_TIMEOUT, ""+DEFAULT_SEND_PARTIAL_TIMEOUT); - try { - return Long.parseLong(timeout); - } catch (NumberFormatException nfe) { - return DEFAULT_SEND_PARTIAL_TIMEOUT; - } - } - - public String getName() { return "Outbound client message"; } - - public void runJob() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Send outbound client message job beginning"); - buildClove(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Clove built"); - Hash to = _status.getTo().calculateHash(); - long timeoutMs = _overallExpiration - getContext().clock().now(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job"); - _status.incrementLookups(); - getContext().netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, timeoutMs); - } - - /** - * Continue on sending through the next tunnel - */ - private void sendNext() { - if (_log.shouldLog(Log.DEBUG)) { - _log.debug(getJobId() + ": sendNext() called with " + _status.getNumSent() + " already sent"); - } - - if (_status.getSuccess()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": sendNext() - already successful!"); - return; - } - if (_status.getFailure()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": sendNext() - already failed!"); - return; - } - - long now = getContext().clock().now(); - if (now >= _overallExpiration) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": sendNext() - Expired"); - dieFatal(); - return; - } - - Lease nextLease = getNextLease(); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Send outbound client message - next lease found for [" - + _status.getTo().calculateHash().toBase64() + "] - " - + nextLease); - - if (nextLease == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": No more leases, and we still haven't heard back from the peer" - + ", refetching the leaseSet to try again"); - LeaseSet ls = _status.getLeaseSet(); - _status.setLeaseSet(null); - long remainingMs = _overallExpiration - getContext().clock().now(); - if (_status.getNumLookups() < MAX_LEASE_LOOKUPS) { - _status.incrementLookups(); - Hash to = _status.getMessage().getDestination().calculateHash(); - _status.clearAlreadySent(); // so we can send down old tunnels again - getContext().netDb().fail(to); // so we don't just fetch what we have - getContext().netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, remainingMs); - if (ls != null) - getContext().jobQueue().addJob(new ShortCircuitSearchJob(getContext(), ls)); - return; - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": sendNext() - max # lease lookups exceeded! " - + _status.getNumLookups()); - dieFatal(); - return; - } - } - - getContext().jobQueue().addJob(new SendJob(getContext(), nextLease)); - } - - private static final long MAX_SEARCH_INTERVAL = 10*1000; - /** - * If the netDb refetch isn't going well, lets fall back on the old leaseSet - * anyway - * - */ - private class ShortCircuitSearchJob extends JobImpl { - private LeaseSet _ls; - public ShortCircuitSearchJob(RouterContext enclosingContext, LeaseSet ls) { - super(enclosingContext); - _ls = ls; - ShortCircuitSearchJob.this.getTiming().setStartAfter(getContext().clock().now() + MAX_SEARCH_INTERVAL); - } - public String getName() { return "Short circuit search"; } - public void runJob() { - LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_ls.getDestination().calculateHash()); - if (ls == null) { - try { - markTimeout(); - getContext().netDb().store(_ls.getDestination().calculateHash(), _ls); - } catch (IllegalArgumentException iae) { - // ignore - it expired anyway - } - } - } - } - - /** - * fetch the next lease that we should try sending through, or null if there - * are no remaining leases available (or there weren't any in the first place...). - * This implements the logic to determine which lease should be next by picking a - * random one that has been failing the least (e.g. if there are 3 leases in the leaseSet - * and one has failed, the other two are randomly chosen as the 'next') - * - */ - private Lease getNextLease() { - LeaseSet ls = _status.getLeaseSet(); - if (ls == null) { - ls = getContext().netDb().lookupLeaseSetLocally(_status.getTo().calculateHash()); - if (ls == null) { - if (_log.shouldLog(Log.INFO)) - _log.info(getJobId() + ": Lookup locally didn't find the leaseSet"); - return null; - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Lookup locally DID find the leaseSet"); - } - _status.setLeaseSet(ls); - } - long now = getContext().clock().now(); - - // get the possible leases - List leases = new ArrayList(4); - for (int i = 0; i < ls.getLeaseCount(); i++) { - Lease lease = ls.getLease(i); - if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease); - continue; - } - - if (!_status.alreadySent(lease.getRouterIdentity().getHash(), lease.getTunnelId())) { - leases.add(lease); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": getNextLease() - skipping lease we've already sent it down - " - + lease); - } - } - - if (leases.size() <= 0) { - if (_log.shouldLog(Log.INFO)) - _log.info(getJobId() + ": No leases found, since we've tried them all (so fail it and relookup)"); - return null; - } - - // randomize the ordering (so leases with equal # of failures per next - // sort are randomly ordered) - Collections.shuffle(leases); - - // ordered by lease number of failures - TreeMap orderedLeases = new TreeMap(); - for (Iterator iter = leases.iterator(); iter.hasNext(); ) { - Lease lease = (Lease)iter.next(); - long id = lease.getNumFailure(); - while (orderedLeases.containsKey(new Long(id))) - id++; - orderedLeases.put(new Long(id), lease); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": ranking lease we havent sent it down as " + id); - } - - return (Lease)orderedLeases.get(orderedLeases.firstKey()); - } - - private boolean getShouldBundle() { - Properties opts = _status.getMessage().getSenderConfig().getOptions(); - String wantBundle = opts.getProperty(BUNDLE_REPLY_LEASESET, "true"); - if ("true".equals(wantBundle)) { - int probability = BUNDLE_PROBABILITY_DEFAULT; - String str = opts.getProperty(BUNDLE_PROBABILITY); - try { - if (str != null) - probability = Integer.parseInt(str); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": Bundle leaseSet probability overridden incorrectly [" - + str + "]", nfe); - } - if (probability >= getContext().random().nextInt(100)) - return true; - else - return false; - } else { - return false; - } - } - - /** - * Send the message to the specified tunnel by creating a new garlic message containing - * the (already created) payload clove as well as a new delivery status message. This garlic - * message is sent out one of our tunnels, destined for the lease (tunnel+router) specified, and the delivery - * status message is targetting one of our free inbound tunnels as well. We use a new - * reply selector to keep an eye out for that delivery status message's token - * - */ - private void send(Lease lease) { - long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE); - PublicKey key = _status.getLeaseSet().getEncryptionKey(); - SessionKey sessKey = new SessionKey(); - Set tags = new HashSet(); - LeaseSet replyLeaseSet = null; - if (_shouldBundle) { - replyLeaseSet = getContext().netDb().lookupLeaseSetLocally(_status.getFrom().calculateHash()); - } - - GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token, - _overallExpiration, key, - _status.getClove(), - _status.getTo(), sessKey, - tags, true, replyLeaseSet); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": send(lease) - token expected " + token); - - _status.sent(lease.getRouterIdentity().getHash(), lease.getTunnelId()); - - SendSuccessJob onReply = new SendSuccessJob(getContext(), lease, sessKey, tags); - SendTimeoutJob onFail = new SendTimeoutJob(getContext(), lease); - ReplySelector selector = new ReplySelector(token); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for " - + lease.getTunnelId() + " on " - + lease.getRouterIdentity().getHash().toBase64()); - - TunnelId outTunnelId = selectOutboundTunnel(); - if (outTunnelId != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Sending tunnel message out " + outTunnelId + " to " - + lease.getTunnelId() + " on " - + lease.getRouterIdentity().getHash().toBase64()); - SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId, - lease.getRouterIdentity().getHash(), - lease.getTunnelId(), null, onReply, - onFail, selector, getSendTimeout(), - SEND_PRIORITY); - getContext().jobQueue().addJob(j); - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?"); - dieFatal(); - } - } - - /** - * Pick an arbitrary outbound tunnel to send the message through, or null if - * there aren't any around - * - */ - private TunnelId selectOutboundTunnel() { - TunnelSelectionCriteria crit = new TunnelSelectionCriteria(); - crit.setMaximumTunnelsRequired(1); - crit.setMinimumTunnelsRequired(1); - List tunnelIds = getContext().tunnelManager().selectOutboundTunnelIds(crit); - if (tunnelIds.size() <= 0) - return null; - else - return (TunnelId)tunnelIds.get(0); - } - - /** - * give up the ghost, this message just aint going through. tell the client to fuck off. - * - * this is safe to call multiple times (only tells the client once) - */ - private void dieFatal() { - if (_status.getSuccess()) return; - boolean alreadyFailed = _status.failed(); - long sendTime = getContext().clock().now() - _status.getStart(); - ClientMessage msg = _status.getMessage(); - if (alreadyFailed) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": dieFatal() - already failed sending " + msg.getMessageId() - + ", no need to do it again", new Exception("Duplicate death?")); - return; - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn(getJobId() + ": Failed to send the message " + msg.getMessageId() + " after " - + _status.getNumSent() + " sends and " + _status.getNumLookups() - + " lookups (and " + sendTime + "ms)", - new Exception("Message send failure")); - } - - getContext().messageHistory().sendPayloadMessage(msg.getMessageId().getMessageId(), false, sendTime); - getContext().clientManager().messageDeliveryStatusUpdate(msg.getFromDestination(), msg.getMessageId(), false); - getContext().statManager().updateFrequency("client.sendMessageFailFrequency"); - getContext().statManager().addRateData("client.sendAttemptAverage", _status.getNumSent(), sendTime); - getContext().statManager().addRateData("client.sendsPerFailure", _status.getNumSent(), sendTime); - } - - /** build the payload clove that will be used for all of the messages, placing the clove in the status structure */ - private void buildClove() { - PayloadGarlicConfig clove = new PayloadGarlicConfig(); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION); - instructions.setDestination(_status.getTo().calculateHash()); - - instructions.setDelayRequested(false); - instructions.setDelaySeconds(0); - instructions.setEncrypted(false); - - clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - clove.setDeliveryInstructions(instructions); - clove.setExpiration(_overallExpiration); - clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); - - DataMessage msg = new DataMessage(getContext()); - msg.setData(_status.getMessage().getPayload().getEncryptedData()); - - clove.setPayload(msg); - clove.setRecipientPublicKey(null); - clove.setRequestAck(false); - - _status.setClove(clove); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(getJobId() + ": Built payload clove with id " + clove.getId()); - } - - /** - * a send or netDb lookup timed out, so lets update some stats to help - * determine why - * - */ - private void markTimeout() { - long messageDelay = getContext().throttle().getMessageDelay(); - long tunnelLag = getContext().throttle().getTunnelLag(); - long inboundDelta = (long)getContext().throttle().getInboundRateDelta(); - - getContext().statManager().addRateData("client.timeoutCongestionTunnel", tunnelLag, 1); - getContext().statManager().addRateData("client.timeoutCongestionMessage", messageDelay, 1); - getContext().statManager().addRateData("client.timeoutCongestionInbound", inboundDelta, 1); - } - - /** - * Keep an eye out for any of the delivery status message tokens that have been - * sent down the various tunnels to deliver this message - * - */ - private class ReplySelector implements MessageSelector { - private long _pendingToken; - public ReplySelector(long token) { - _pendingToken = token; - } - - public boolean continueMatching() { return false; } - public long getExpiration() { return _overallExpiration; } - - public boolean isMatch(I2NPMessage inMsg) { - if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) { - return _pendingToken == ((DeliveryStatusMessage)inMsg).getMessageId(); - } else { - return false; - } - } - } - - /** queued by the db lookup success and the send timeout to get us to try the next lease */ - private class NextStepJob extends JobImpl { - public NextStepJob(RouterContext enclosingContext) { - super(enclosingContext); - } - public String getName() { return "Process next step for outbound client message"; } - public void runJob() { sendNext(); } - } - - /** - * we couldn't even find the leaseSet, but try again (or die - * if we've already tried too hard) - * - */ - private class LookupLeaseSetFailedJob extends JobImpl { - public LookupLeaseSetFailedJob(RouterContext enclosingContext) { - super(enclosingContext); - } - public String getName() { return "Lookup for outbound client message failed"; } - public void runJob() { - markTimeout(); - sendNext(); - } - } - - /** send a message to a lease */ - private class SendJob extends JobImpl { - private Lease _lease; - public SendJob(RouterContext enclosingContext, Lease lease) { - super(enclosingContext); - _lease = lease; - } - public String getName() { return "Send outbound client message through the lease"; } - public void runJob() { send(_lease); } - } - - /** - * Called after we get a confirmation that the message was delivered safely - * (hoo-ray!) - * - */ - private class SendSuccessJob extends JobImpl implements ReplyJob { - private Lease _lease; - private SessionKey _key; - private Set _tags; - - /** - * Create a new success job that will be fired when the message encrypted with - * the given session key and bearing the specified tags are confirmed delivered. - * - */ - public SendSuccessJob(RouterContext enclosingContext, Lease lease, SessionKey key, Set tags) { - super(enclosingContext); - _lease = lease; - _key = key; - _tags = tags; - } - - public String getName() { return "Send client message successful to a lease"; } - public void runJob() { - long sendTime = getContext().clock().now() - _status.getStart(); - boolean alreadySuccessful = _status.success(); - MessageId msgId = _status.getMessage().getMessageId(); - if (_log.shouldLog(Log.INFO)) - _log.info(OutboundClientMessageJob.this.getJobId() - + ": SUCCESS! msg " + msgId - + " sent after " + sendTime + "ms after " - + _status.getNumLookups() + " lookups and " - + _status.getNumSent() + " sends"); - - if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) { - LeaseSet ls = _status.getLeaseSet(); - if (ls != null) - getContext().sessionKeyManager().tagsDelivered(ls.getEncryptionKey(), - _key, _tags); - } - - if (alreadySuccessful) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(OutboundClientMessageJob.this.getJobId() - + ": Success is a duplicate for " + _status.getMessage().getMessageId() - + ", dont notify again..."); - return; - } - long dataMsgId = _status.getClove().getId(); - getContext().messageHistory().sendPayloadMessage(dataMsgId, true, sendTime); - getContext().clientManager().messageDeliveryStatusUpdate(_status.getFrom(), msgId, true); - _lease.setNumSuccess(_lease.getNumSuccess()+1); - - getContext().statManager().addRateData("client.sendAckTime", sendTime, 0); - getContext().statManager().addRateData("client.sendMessageSize", _status.getMessage().getPayload().getSize(), sendTime); - getContext().statManager().addRateData("client.sendAttemptAverage", _status.getNumSent(), sendTime); - } - - public void setMessage(I2NPMessage msg) {} - } - - /** - * Fired after the basic timeout for sending through the given tunnel has been reached. - * We'll accept successes later, but won't expect them - * - */ - private class SendTimeoutJob extends JobImpl { - private Lease _lease; - - public SendTimeoutJob(RouterContext enclosingContext, Lease lease) { - super(enclosingContext); - _lease = lease; - } - - public String getName() { return "Send client message timed out through a lease"; } - public void runJob() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(OutboundClientMessageJob.this.getJobId() - + ": Soft timeout through the lease " + _lease); - - markTimeout(); - - _lease.setNumFailure(_lease.getNumFailure()+1); - sendNext(); - } - } -} diff --git a/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java b/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java deleted file mode 100644 index 250bef9cf..000000000 --- a/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java +++ /dev/null @@ -1,623 +0,0 @@ -package net.i2p.router.message; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Date; - -import net.i2p.data.DataFormatException; -import net.i2p.data.DataStructure; -import net.i2p.data.Hash; -import net.i2p.data.Payload; -import net.i2p.data.RouterIdentity; -import net.i2p.data.SessionKey; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DataMessage; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelMessage; -import net.i2p.data.i2np.TunnelVerificationStructure; -import net.i2p.router.ClientMessage; -import net.i2p.router.InNetMessage; -import net.i2p.router.Job; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageReceptionInfo; -import net.i2p.router.MessageSelector; -import net.i2p.router.OutNetMessage; -import net.i2p.router.ReplyJob; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Send a message down a tunnel that we are the gateway for - * - */ -public class SendTunnelMessageJob extends JobImpl { - private Log _log; - private I2NPMessage _message; - private Hash _destRouter; - private TunnelId _tunnelId; - private TunnelId _targetTunnelId; - private Job _onSend; - private ReplyJob _onReply; - private Job _onFailure; - private MessageSelector _selector; - private long _timeout; - private long _expiration; - private int _priority; - private int _state; - - public SendTunnelMessageJob(RouterContext ctx, I2NPMessage msg, TunnelId tunnelId, Job onSend, ReplyJob onReply, Job onFailure, MessageSelector selector, long timeoutMs, int priority) { - this(ctx, msg, tunnelId, null, null, onSend, onReply, onFailure, selector, timeoutMs, priority); - } - - public SendTunnelMessageJob(RouterContext ctx, I2NPMessage msg, TunnelId tunnelId, Hash targetRouter, TunnelId targetTunnelId, Job onSend, ReplyJob onReply, Job onFailure, MessageSelector selector, long timeoutMs, int priority) { - super(ctx); - _state = 0; - _log = ctx.logManager().getLog(SendTunnelMessageJob.class); - if (msg == null) - throw new IllegalArgumentException("wtf, null message? sod off"); - if (tunnelId == null) - throw new IllegalArgumentException("wtf, no tunnelId? nuh uh"); - - _state = 1; - _message = msg; - _destRouter = targetRouter; - _tunnelId = tunnelId; - _targetTunnelId = targetTunnelId; - _onSend = onSend; - _onReply = onReply; - _onFailure = onFailure; - _selector = selector; - _timeout = timeoutMs; - _priority = priority; - - if (timeoutMs < 50*1000) { - if (_log.shouldLog(Log.INFO)) - _log.info("Sending tunnel message to expire in " + timeoutMs - + "ms containing " + msg.getUniqueId() + " (a " - + msg.getClass().getName() + ")", - new Exception("SendTunnel from")); - } - //_log.info("Send tunnel message " + msg.getClass().getName() + " to " + _destRouter + " over " + _tunnelId + " targetting tunnel " + _targetTunnelId, new Exception("SendTunnel from")); - if (timeoutMs < 5*1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Very little time given [" + timeoutMs + "], resetting to 5s", new Exception("stingy bastard")); - _expiration = getContext().clock().now() + 5*1000; - } else { - _expiration = getContext().clock().now() + timeoutMs; - } - _state = 2; - } - - public void runJob() { - _state = 3; - TunnelInfo info = getContext().tunnelManager().getTunnelInfo(_tunnelId); - if (info == null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Message for unknown tunnel [" + _tunnelId - + "] received, forward to " + _destRouter); - if ( (_tunnelId == null) || (_destRouter == null) ) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Someone br0ke us. where is this message supposed to go again?", - getAddedBy()); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - return; - } else { - _state = 4; - forwardToGateway(); - _state = 0; - return; - } - } - - info.messageProcessed(_message.getMessageSize()); - - if (isEndpoint(info)) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel message where we're both the gateway and the endpoint - honor instructions"); - _state = 5; - honorInstructions(info); - _state = 0; - return; - } else if (isGateway(info)) { - _state = 6; - handleAsGateway(info); - _state = 0; - return; - } else { - _state = 7; - handleAsParticipant(info); - _state = 0; - return; - } - } - - /** - * Forward this job's message to the gateway of the tunnel requested - * - */ - private void forwardToGateway() { - _state = 8; - TunnelMessage msg = new TunnelMessage(getContext()); - msg.setData(_message.toByteArray()); - msg.setTunnelId(_tunnelId); - msg.setMessageExpiration(new Date(_expiration)); - getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), msg, - _destRouter, _onSend, - _onReply, _onFailure, - _selector, - (int)(_expiration-getContext().clock().now()), - _priority)); - - String bodyType = _message.getClass().getName(); - getContext().messageHistory().wrap(bodyType, _message.getUniqueId(), - TunnelMessage.class.getName(), msg.getUniqueId()); - _state = 9; - return; - } - - /** - * We are the gateway for the tunnel this message is bound to, - * so wrap it accordingly and send it on its way. - * - */ - private void handleAsGateway(TunnelInfo info) { - _state = 10; - // since we are the gateway, we don't need to verify the data structures - TunnelInfo us = getUs(info); - if (us == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("We are not participating in this /known/ tunnel - was the router reset?"); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 11; - } else { - _state = 12; - // we're the gateway, so sign, encrypt, and forward to info.getNextHop() - TunnelMessage msg = prepareMessage(info); - _state = 66; - if (msg == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("wtf, unable to prepare a tunnel message to the next hop, when we're the gateway and hops remain? tunnel: " + info); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 13; - return; - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel message created: " + msg + " out of encrypted message: " - + _message); - long now = getContext().clock().now(); - if (_expiration < now - Router.CLOCK_FUDGE_FACTOR) { - if (_log.shouldLog(Log.ERROR)) - _log.error("We are the gateway to " + info.getTunnelId().getTunnelId() - + " and the message " + msg.getUniqueId() + " is valid, but it has timed out (" - + (now - _expiration) + "ms ago)"); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 14; - return; - }else if (_expiration < now + 15*1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Adding a tunnel message that will expire shortly [" - + new Date(_expiration) + "]", getAddedBy()); - } - _state = 67; - msg.setMessageExpiration(new Date(_expiration)); - _state = 68; - Job j = new SendMessageDirectJob(getContext(), msg, - info.getNextHop(), _onSend, - _onReply, _onFailure, - _selector, - (int)(_expiration - getContext().clock().now()), - _priority); - _state = 69; - getContext().jobQueue().addJob(j); - _state = 15; - } - } - - /** - * We are the participant in the tunnel, so verify the signature / data and - * forward it to the next hop. - * - */ - private void handleAsParticipant(TunnelInfo info) { - _state = 16; - // SendTunnelMessageJob shouldn't be used for participants! - if (_log.shouldLog(Log.DEBUG)) - _log.debug("SendTunnelMessageJob for a participant... ", getAddedBy()); - - if (!(_message instanceof TunnelMessage)) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Cannot inject non-tunnel messages as a participant!" + _message, getAddedBy()); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 17; - return; - } - - TunnelMessage msg = (TunnelMessage)_message; - - TunnelVerificationStructure struct = msg.getVerificationStructure(); - if ( (info.getVerificationKey() == null) || (info.getVerificationKey().getKey() == null) ) { - if (_log.shouldLog(Log.ERROR)) - _log.error("No verification key for the participant? tunnel: " + info, getAddedBy()); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 18; - return; - } - - boolean ok = struct.verifySignature(getContext(), info.getVerificationKey().getKey()); - _state = 19; - if (!ok) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Failed tunnel verification! Spoofing / tagging attack? " + _message, getAddedBy()); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 20; - return; - } else { - _state = 21; - if (info.getNextHop() != null) { - _state = 22; - if (_log.shouldLog(Log.INFO)) - _log.info("Message for tunnel " + info.getTunnelId().getTunnelId() + " received where we're not the gateway and there are remaining hops, so forward it on to " - + info.getNextHop().toBase64() + " via SendMessageDirectJob"); - SendMessageDirectJob j = new SendMessageDirectJob(getContext(), msg, info.getNextHop(), _onSend, - null, _onFailure, null, - (int)(_message.getMessageExpiration().getTime() - getContext().clock().now()), - _priority); - getContext().jobQueue().addJob(j); - _state = 23; - return; - } else { - _state = 24; - if (_log.shouldLog(Log.ERROR)) - _log.error("Should not be reached - participant, but no more hops?!"); - if (_onFailure != null) - getContext().jobQueue().addJob(_onFailure); - _state = 25; - return; - } - } - } - - - /** find our place in the tunnel */ - private TunnelInfo getUs(TunnelInfo info) { - _state = 26; - Hash us = getContext().routerHash(); - TunnelInfo lastUs = null; - while (info != null) { - if (us.equals(info.getThisHop())) - lastUs = info; - info = info.getNextHopInfo(); - _state = 28; - } - _state = 27; - return lastUs; - } - - /** are we the endpoint for the tunnel? */ - private boolean isEndpoint(TunnelInfo info) { - TunnelInfo us = getUs(info); - _state = 29; - if (us == null) return false; - return (us.getNextHop() == null); - } - - /** are we the gateway for the tunnel? */ - private boolean isGateway(TunnelInfo info) { - TunnelInfo us = getUs(info); - _state = 30; - if (us == null) return false; - return (us.getSigningKey() != null); // only the gateway can sign - } - - private static final int INSTRUCTIONS_PADDING = 32; - private static final int PAYLOAD_PADDING = 32; - - /** - * Build the tunnel message with appropriate instructions for the - * tunnel endpoint, then encrypt and sign it. - * - */ - private TunnelMessage prepareMessage(TunnelInfo info) { - _state = 31; - TunnelMessage msg = new TunnelMessage(getContext()); - - SessionKey key = getContext().keyGenerator().generateSessionKey(); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDelayRequested(false); - instructions.setEncrypted(true); - instructions.setEncryptionKey(key); - - // if we aren't told where to send it, have it be processed locally at the endpoint - // but if we are, have the endpoint forward it appropriately. - // note that this algorithm does not currently support instructing the endpoint to send to a Destination - if (_destRouter != null) { - _state = 32; - instructions.setRouter(_destRouter); - if (_targetTunnelId != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions target tunnel " + _targetTunnelId - + " on router " + _destRouter.calculateHash()); - instructions.setTunnelId(_targetTunnelId); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_TUNNEL); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions target router " + _destRouter.toBase64()); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER); - } - } else { - _state = 33; - if (_message instanceof DataMessage) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions are for local message delivery at the endpoint with a DataMessage to be sent to a Destination"); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Instructions are for local delivery at the endpoint targetting the now-local router"); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); - } - } - - if (info == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel info is null to send message " + _message); - _state = 34; - return null; - } else if ( (info.getEncryptionKey() == null) || (info.getEncryptionKey().getKey() == null) ) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel encryption key is null when we're the gateway?! info: " + info); - _state = 35; - return null; - } - - _state = 36; - byte encryptedInstructions[] = encrypt(instructions, info.getEncryptionKey().getKey(), INSTRUCTIONS_PADDING); - byte encryptedMessage[] = encrypt(_message, key, PAYLOAD_PADDING); - _state = 37; - TunnelVerificationStructure verification = createVerificationStructure(encryptedMessage, info); - - _state = 38; - - String bodyType = _message.getClass().getName(); - getContext().messageHistory().wrap(bodyType, _message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId()); - _state = 39; - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel message prepared: instructions = " + instructions); - - msg.setData(encryptedMessage); - msg.setEncryptedDeliveryInstructions(encryptedInstructions); - msg.setTunnelId(_tunnelId); - msg.setVerificationStructure(verification); - _state = 40; - return msg; - } - - /** - * Create and sign the verification structure, using the tunnel's signing key - * - */ - private TunnelVerificationStructure createVerificationStructure(byte encryptedMessage[], TunnelInfo info) { - _state = 41; - TunnelVerificationStructure struct = new TunnelVerificationStructure(); - struct.setMessageHash(getContext().sha().calculateHash(encryptedMessage)); - struct.sign(getContext(), info.getSigningKey().getKey()); - - _state = 42; - return struct; - } - - /** - * encrypt the structure (the message or instructions) - * - * @param paddedSize minimum size to pad to - */ - private byte[] encrypt(DataStructure struct, SessionKey key, int paddedSize) { - _state = 43; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize); - byte data[] = struct.toByteArray(); - baos.write(data); - - byte iv[] = new byte[16]; - Hash h = getContext().sha().calculateHash(key.getData()); - System.arraycopy(h.getData(), 0, iv, 0, iv.length); - _state = 44; - return getContext().aes().safeEncrypt(baos.toByteArray(), key, iv, paddedSize); - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error writing out data to encrypt", ioe); - } - _state = 45; - return null; - } - - /** - * We are both the endpoint and gateway for the tunnel, so honor - * what was requested of us (processing the message locally, - * forwarding to a router, forwarding to a tunnel, etc) - * - */ - private void honorInstructions(TunnelInfo info) { - _state = 46; - if (_selector != null) - createFakeOutNetMessage(); - - if (_onSend != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Firing onSend as we're honoring the instructions"); - getContext().jobQueue().addJob(_onSend); - _state = 47; - } - - // since we are the gateway, we don't need to decrypt the delivery instructions or the payload - - RouterIdentity ident = getContext().router().getRouterInfo().getIdentity(); - - if (_destRouter != null) { - _state = 48; - honorSendRemote(info, ident); - _state = 49; - } else { - _state = 50; - honorSendLocal(info, ident); - _state = 51; - } - } - - /** - * We are the gateway and endpoint and we have been asked to forward the - * message to a remote location (either a tunnel or a router). - * - */ - private void honorSendRemote(TunnelInfo info, RouterIdentity ident) { - _state = 52; - I2NPMessage msg = null; - if (_targetTunnelId != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Forward " + _message.getClass().getName() - + " message off to remote tunnel " - + _targetTunnelId.getTunnelId() + " on router " - + _destRouter.toBase64()); - TunnelMessage tmsg = new TunnelMessage(getContext()); - tmsg.setEncryptedDeliveryInstructions(null); - tmsg.setTunnelId(_targetTunnelId); - tmsg.setVerificationStructure(null); - byte data[] = _message.toByteArray(); - tmsg.setData(data); - msg = tmsg; - _state = 53; - } else { - _state = 54; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Forward " + _message.getClass().getName() - + " message off to remote router " + _destRouter.toBase64()); - msg = _message; - } - long now = getContext().clock().now(); - //if (_expiration < now) { - //_expiration = now + Router.CLOCK_FUDGE_FACTOR; - //_log.info("Fudging the message send so it expires in the fudge factor..."); - //} - - long timeLeft = _expiration - now; - - if (timeLeft < 10*1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Why are we trying to send a " + _message.getClass().getName() - + " message with " + (_expiration-now) + "ms left?", getAddedBy()); - if (timeLeft + Router.CLOCK_FUDGE_FACTOR < 0) { - _log.error("Timed out honoring request to send a " + _message.getClass().getName() - + " message remotely [" + _message.getUniqueId() + "] expired " - + (0-timeLeft) + "ms ago"); - return; - } - } - - _state = 55; - String bodyType = _message.getClass().getName(); - getContext().messageHistory().wrap(bodyType, _message.getUniqueId(), - TunnelMessage.class.getName(), msg.getUniqueId()); - _state = 56; - - // don't specify a selector, since createFakeOutNetMessage already does that - SendMessageDirectJob j = new SendMessageDirectJob(getContext(), msg, _destRouter, - _onSend, _onReply, _onFailure, - null, (int)(timeLeft), - _priority); - _state = 57; - getContext().jobQueue().addJob(j); - } - - /** - * We are the gateway and endpoint, and the instructions say to forward the - * message to, uh, us. The message may be a normal network message or they - * may be a client DataMessage. - * - */ - private void honorSendLocal(TunnelInfo info, RouterIdentity ident) { - _state = 59; - if ( (info.getDestination() == null) || !(_message instanceof DataMessage) ) { - // its a network message targeting us... - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Destination is null or its not a DataMessage - pass it off to the InNetMessagePool"); - _state = 59; - InNetMessage msg = new InNetMessage(getContext()); - msg.setFromRouter(ident); - msg.setFromRouterHash(ident.getHash()); - msg.setMessage(_message); - getContext().inNetMessagePool().add(msg); - _state = 60; - } else { - _state = 61; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Destination is not null and it is a DataMessage - pop it into the ClientMessagePool"); - DataMessage msg = (DataMessage)_message; - boolean valid = getContext().messageValidator().validateMessage(msg.getUniqueId(), msg.getMessageExpiration().getTime()); - if (!valid) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Duplicate data message received [" + msg.getUniqueId() + " expiring on " + msg.getMessageExpiration() + "]"); - getContext().messageHistory().droppedOtherMessage(msg); - getContext().messageHistory().messageProcessingError(msg.getUniqueId(), msg.getClass().getName(), "Duplicate"); - _state = 62; - return; - } - - Payload payload = new Payload(); - payload.setEncryptedData(msg.getData()); - - MessageReceptionInfo receptionInfo = new MessageReceptionInfo(); - receptionInfo.setFromPeer(ident.getHash()); - receptionInfo.setFromTunnel(_tunnelId); - - ClientMessage clientMessage = new ClientMessage(); - clientMessage.setDestination(info.getDestination()); - clientMessage.setPayload(payload); - clientMessage.setReceptionInfo(receptionInfo); - getContext().clientMessagePool().add(clientMessage); - getContext().messageHistory().receivePayloadMessage(msg.getUniqueId()); - _state = 63; - } - } - - private void createFakeOutNetMessage() { - _state = 64; - // now we create a fake outNetMessage to go onto the registry so we can select - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Registering a fake outNetMessage for the message tunneled locally since we have a selector"); - OutNetMessage outM = new OutNetMessage(getContext()); - outM.setExpiration(_expiration); - outM.setMessage(_message); - outM.setOnFailedReplyJob(_onFailure); - outM.setOnFailedSendJob(_onFailure); - outM.setOnReplyJob(_onReply); - outM.setOnSendJob(_onSend); - outM.setPriority(_priority); - outM.setReplySelector(_selector); - outM.setTarget(getContext().netDb().lookupRouterInfoLocally(_destRouter)); - getContext().messageRegistry().registerPending(outM); - // we dont really need the data - outM.discardData(); - _state = 65; - } - - public String getName() { return "Send Tunnel Message" + (_state == 0 ? "" : ""+_state); } -} diff --git a/router/java/src/net/i2p/router/message/TunnelMessageHandler.java b/router/java/src/net/i2p/router/message/TunnelMessageHandler.java deleted file mode 100644 index 81351b48e..000000000 --- a/router/java/src/net/i2p/router/message/TunnelMessageHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.i2p.router.message; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import net.i2p.data.Hash; -import net.i2p.data.RouterIdentity; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelMessage; -import net.i2p.router.HandlerJobBuilder; -import net.i2p.router.Job; -import net.i2p.router.RouterContext; - -/** - * HandlerJobBuilder to build jobs to handle TunnelMessages - * - */ -public class TunnelMessageHandler implements HandlerJobBuilder { - private RouterContext _context; - - public TunnelMessageHandler(RouterContext context) { - _context = context; - } - public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { - HandleTunnelMessageJob job = new HandleTunnelMessageJob(_context, (TunnelMessage)receivedMessage, from, fromHash); - return job; - } - -} diff --git a/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java new file mode 100644 index 000000000..ffa2c5074 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java @@ -0,0 +1,36 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.util.DecayingBloomFilter; + +/** + * Manage the IV validation for all of the router's tunnels by way of a big + * decaying bloom filter. + * + */ +public class BloomFilterIVValidator implements IVValidator { + private I2PAppContext _context; + private DecayingBloomFilter _filter; + + /** + * After 2*halflife, an entry is completely forgotten from the bloom filter. + * To avoid the issue of overlap within different tunnels, this is set + * higher than it needs to be. + * + */ + private static final int HALFLIFE_MS = 10*60*1000; + public BloomFilterIVValidator(I2PAppContext ctx, int KBps) { + _context = ctx; + _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16); + ctx.statManager().createRateStat("tunnel.duplicateIV", "Note that a duplicate IV was received", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + + } + + public boolean receiveIV(byte[] iv) { + boolean dup = _filter.add(iv); + if (dup) _context.statManager().addRateData("tunnel.duplicateIV", 1, 1); + return !dup; // return true if it is OK, false if it isn't + } + public void destroy() { _filter.stopDecaying(); } +} diff --git a/router/java/src/net/i2p/router/tunnel/DummyValidator.java b/router/java/src/net/i2p/router/tunnel/DummyValidator.java new file mode 100644 index 000000000..22b4cab5e --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/DummyValidator.java @@ -0,0 +1,12 @@ +package net.i2p.router.tunnel; + +/** + * accept everything + */ +class DummyValidator implements IVValidator { + private static final DummyValidator _instance = new DummyValidator(); + public static DummyValidator getInstance() { return _instance; } + private DummyValidator() {} + + public boolean receiveIV(byte[] iv) { return true; } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/HashSetIVValidator.java b/router/java/src/net/i2p/router/tunnel/HashSetIVValidator.java new file mode 100644 index 000000000..708304bad --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/HashSetIVValidator.java @@ -0,0 +1,25 @@ +package net.i2p.router.tunnel; + +import java.util.HashSet; +import net.i2p.data.ByteArray; + +/** + * waste lots of RAM + */ +class HashSetIVValidator implements IVValidator { + private HashSet _received; + + public HashSetIVValidator() { + _received = new HashSet(); + } + public boolean receiveIV(byte[] iv) { + //if (true) // foo! + // return true; + ByteArray ba = new ByteArray(iv); + boolean isNew = false; + synchronized (_received) { + isNew = _received.add(ba); + } + return isNew; + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java b/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java new file mode 100644 index 000000000..b961d6b88 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/InboundGatewayReceiver.java @@ -0,0 +1,59 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.RouterInfo; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; +import net.i2p.router.RouterContext; + +/** + * + */ +public class InboundGatewayReceiver implements TunnelGateway.Receiver { + private RouterContext _context; + private HopConfig _config; + private RouterInfo _target; + + public InboundGatewayReceiver(RouterContext ctx, HopConfig cfg) { + _context = ctx; + _config = cfg; + } + public void receiveEncrypted(byte[] encrypted) { + receiveEncrypted(encrypted, false); + } + public void receiveEncrypted(byte[] encrypted, boolean alreadySearched) { + if (_target == null) { + _target = _context.netDb().lookupRouterInfoLocally(_config.getSendTo()); + if (_target == null) { + ReceiveJob j = null; + if (!alreadySearched) + j = new ReceiveJob(encrypted); + _context.netDb().lookupRouterInfo(_config.getSendTo(), j, j, 5*1000); + return; + } + } + + TunnelDataMessage msg = new TunnelDataMessage(_context); + msg.setData(encrypted); + msg.setTunnelId(_config.getSendTunnel()); + + OutNetMessage out = new OutNetMessage(_context); + out.setMessage(msg); + out.setTarget(_target); + out.setExpiration(msg.getMessageExpiration()); + out.setPriority(400); + _context.outNetMessagePool().add(out); + } + + private class ReceiveJob extends JobImpl { + private byte[] _encrypted; + public ReceiveJob(byte data[]) { + super(_context); + _encrypted = data; + } + public String getName() { return "lookup first hop"; } + public void runJob() { + receiveEncrypted(_encrypted, true); + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java new file mode 100644 index 000000000..d4abda61b --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java @@ -0,0 +1,149 @@ +package net.i2p.router.tunnel; + +import java.util.Date; +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.RouterInfo; +import net.i2p.data.Payload; +import net.i2p.data.i2np.DataMessage; +import net.i2p.data.i2np.DeliveryInstructions; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.GarlicMessage; +import net.i2p.data.i2np.GarlicClove; +import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.router.ClientMessage; +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.GarlicMessageReceiver; +import net.i2p.util.Log; + +/** + * When a message arrives at the inbound tunnel endpoint, this distributor + * honors the instructions (safely) + */ +public class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver { + private RouterContext _context; + private Log _log; + private Hash _client; + private GarlicMessageReceiver _receiver; + + private static final int MAX_DISTRIBUTE_TIME = 10*1000; + + public InboundMessageDistributor(RouterContext ctx, Hash client) { + _context = ctx; + _client = client; + _log = ctx.logManager().getLog(InboundMessageDistributor.class); + _receiver = new GarlicMessageReceiver(ctx, this, client); + } + + public void distribute(I2NPMessage msg, Hash target) { + distribute(msg, target, null); + } + public void distribute(I2NPMessage msg, Hash target, TunnelId tunnel) { + // allow messages on client tunnels even after client disconnection, as it may + // include e.g. test messages, etc. DataMessages will be dropped anyway + /* + if ( (_client != null) && (!_context.clientManager().isLocal(_client)) ) { + if (_log.shouldLog(Log.INFO)) + _log.info("Not distributing a message, as it came down a client's tunnel (" + + _client.toBase64() + ") after the client disconnected: " + msg); + return; + } + */ + + if ( (target == null) || ( (tunnel == null) && (_context.routerHash().equals(target) ) ) ) { + // targetting us either implicitly (no target) or explicitly (no tunnel) + // make sure we don't honor any remote requests directly (garlic instructions, etc) + if (msg.getType() == GarlicMessage.MESSAGE_TYPE) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("received garlic message in the tunnel, parse it out"); + _receiver.receive((GarlicMessage)msg); + } else { + if (_log.shouldLog(Log.INFO)) + _log.info("distributing inbound tunnel message into our inNetMessagePool: " + msg); + _context.inNetMessagePool().add(msg, null, null); + } + } else if (_context.routerHash().equals(target)) { + // the want to send it to a tunnel, except we are also that tunnel's gateway + // dispatch it directly + if (_log.shouldLog(Log.INFO)) + _log.info("distributing inbound tunnel message back out, except we are the gateway"); + TunnelGatewayMessage gw = new TunnelGatewayMessage(_context); + gw.setMessage(msg); + gw.setTunnelId(tunnel); + gw.setMessageExpiration(_context.clock().now()+10*1000); + gw.setUniqueId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE)); + _context.tunnelDispatcher().dispatch(gw); + } else { + // ok, they want us to send it remotely, but that'd bust our anonymity, + // so we send it out a tunnel first + TunnelInfo out = _context.tunnelManager().selectOutboundTunnel(_client); + if (_log.shouldLog(Log.INFO)) + _log.info("distributing inbound tunnel message back out " + out + + " targetting " + target.toBase64().substring(0,4)); + TunnelId outId = out.getSendTunnelId(0); + if (outId == null) { + if (_log.shouldLog(Log.ERROR)) + _log.error("wtf, outbound tunnel has no outboundId? " + out + + " failing to distribute " + msg); + return; + } + _context.tunnelDispatcher().dispatchOutbound(msg, outId, tunnel, target); + } + } + + /** + * Handle a clove removed from the garlic message + * + */ + public void handleClove(DeliveryInstructions instructions, I2NPMessage data) { + switch (instructions.getDeliveryMode()) { + case DeliveryInstructions.DELIVERY_MODE_LOCAL: + if (_log.shouldLog(Log.DEBUG)) + _log.debug("local delivery instructions for clove: " + data.getClass().getName()); + if (data instanceof GarlicMessage) { + _receiver.receive((GarlicMessage)data); + return; + } else { + _context.inNetMessagePool().add(data, null, null); + return; + } + case DeliveryInstructions.DELIVERY_MODE_DESTINATION: + if (!(data instanceof DataMessage)) { + if (_log.shouldLog(Log.ERROR)) + _log.error("cant send a " + data.getClass().getName() + " to a destination"); + } else if ( (_client != null) && (_client.equals(instructions.getDestination())) ) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("data message came down a tunnel for " + + _client.toBase64().substring(0,4)); + DataMessage dm = (DataMessage)data; + Payload payload = new Payload(); + payload.setEncryptedData(dm.getData()); + ClientMessage m = new ClientMessage(); + m.setDestinationHash(_client); + m.setPayload(payload); + _context.clientManager().messageReceived(m); + } else { + if (_log.shouldLog(Log.ERROR)) + _log.error("this data message came down a tunnel for " + + (_client == null ? "no one" : _client.toBase64().substring(0,4)) + + " but targetted " + + instructions.getDestination().toBase64().substring(0,4)); + } + return; + case DeliveryInstructions.DELIVERY_MODE_ROUTER: // fall through + case DeliveryInstructions.DELIVERY_MODE_TUNNEL: + if (_log.shouldLog(Log.INFO)) + _log.info("clove targetted " + instructions.getRouter() + ":" + instructions.getTunnelId() + + ", treat recursively to prevent leakage"); + distribute(data, instructions.getRouter(), instructions.getTunnelId()); + return; + default: + if (_log.shouldLog(Log.ERROR)) + _log.error("Unknown instruction " + instructions.getDeliveryMode() + ": " + instructions); + return; + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java new file mode 100644 index 000000000..c770e8718 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundMessageDistributor.java @@ -0,0 +1,98 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.RouterInfo; +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; + +/** + * When a message arrives at the outbound tunnel endpoint, this distributor + * honors the instructions. + */ +public class OutboundMessageDistributor { + private RouterContext _context; + private Log _log; + + private static final int MAX_DISTRIBUTE_TIME = 10*1000; + + public OutboundMessageDistributor(RouterContext ctx) { + _context = ctx; + _log = ctx.logManager().getLog(OutboundMessageDistributor.class); + } + + public void distribute(I2NPMessage msg, Hash target) { + distribute(msg, target, null); + } + public void distribute(I2NPMessage msg, Hash target, TunnelId tunnel) { + RouterInfo info = _context.netDb().lookupRouterInfoLocally(target); + if (info == null) { + _log.debug("outbound distributor to " + target.toBase64().substring(0,4) + + "." + (tunnel != null ? tunnel.getTunnelId() + "" : "") + + ": no info locally, searching..."); + _context.netDb().lookupRouterInfo(target, new DistributeJob(_context, msg, target, tunnel), null, MAX_DISTRIBUTE_TIME); + return; + } else { + distribute(msg, info, tunnel); + } + } + + public void distribute(I2NPMessage msg, RouterInfo target, TunnelId tunnel) { + I2NPMessage m = msg; + if (tunnel != null) { + TunnelGatewayMessage t = new TunnelGatewayMessage(_context); + t.setMessage(msg); + t.setTunnelId(tunnel); + t.setMessageExpiration(m.getMessageExpiration()); + m = t; + } + + if (_context.routerHash().equals(target.getIdentity().calculateHash())) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("queueing inbound message to ourselves: " + m); + _context.inNetMessagePool().add(m, null, null); + return; + } else { + OutNetMessage out = new OutNetMessage(_context); + out.setExpiration(_context.clock().now() + MAX_DISTRIBUTE_TIME); + out.setTarget(target); + out.setMessage(m); + out.setPriority(400); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("queueing outbound message to " + target.getIdentity().calculateHash().toBase64().substring(0,4)); + _context.outNetMessagePool().add(out); + } + } + + private class DistributeJob extends JobImpl { + private I2NPMessage _message; + private Hash _target; + private TunnelId _tunnel; + public DistributeJob(RouterContext ctx, I2NPMessage msg, Hash target, TunnelId id) { + super(ctx); + _message = msg; + _target = target; + _tunnel = id; + } + public String getName() { return "distribute outbound message"; } + public void runJob() { + RouterInfo info = getContext().netDb().lookupRouterInfoLocally(_target); + if (info != null) { + _log.debug("outbound distributor to " + _target.toBase64().substring(0,4) + + "." + (_tunnel != null ? _tunnel.getTunnelId() + "" : "") + + ": found on search"); + distribute(_message, info, _tunnel); + } else { + _log.error("outbound distributor to " + _target.toBase64().substring(0,4) + + "." + (_tunnel != null ? _tunnel.getTunnelId() + "" : "") + + ": NOT found on search"); + } + } + + } +} diff --git a/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java b/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java new file mode 100644 index 000000000..7e0ddac1e --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundReceiver.java @@ -0,0 +1,84 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.Hash; +import net.i2p.data.RouterInfo; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * Receive the outbound message after it has been preprocessed and encrypted, + * then forward it on to the first hop in the tunnel. + * + */ +class OutboundReceiver implements TunnelGateway.Receiver { + private RouterContext _context; + private Log _log; + private TunnelCreatorConfig _config; + public OutboundReceiver(RouterContext ctx, TunnelCreatorConfig cfg) { + _context = ctx; + _log = ctx.logManager().getLog(OutboundReceiver.class); + _config = cfg; + } + + public void receiveEncrypted(byte encrypted[]) { + TunnelDataMessage msg = new TunnelDataMessage(_context); + msg.setData(encrypted); + msg.setTunnelId(_config.getConfig(0).getSendTunnel()); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("received encrypted, sending out " + _config + ": " + msg); + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getPeer(1)); + if (ri != null) { + send(msg, ri); + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("lookup of " + _config.getPeer(1).toBase64().substring(0,4) + + " required for " + msg); + _context.netDb().lookupRouterInfo(_config.getPeer(1), new SendJob(msg), new FailedJob(), 10*1000); + } + } + + private void send(TunnelDataMessage msg, RouterInfo ri) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("forwarding encrypted data out " + _config + ": " + msg); + OutNetMessage m = new OutNetMessage(_context); + m.setMessage(msg); + m.setExpiration(msg.getMessageExpiration()); + m.setTarget(ri); + m.setPriority(400); + _context.outNetMessagePool().add(m); + _config.incrementProcessedMessages(); + } + + private class SendJob extends JobImpl { + private TunnelDataMessage _msg; + public SendJob(TunnelDataMessage msg) { + super(_context); + _msg = msg; + } + public String getName() { return "forward a tunnel message"; } + public void runJob() { + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getPeer(1)); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("lookup of " + _config.getPeer(1).toBase64().substring(0,4) + + " successful? " + (ri != null)); + if (ri != null) + send(_msg, ri); + } + } + + private class FailedJob extends JobImpl { + public FailedJob() { + super(_context); + } + public String getName() { return "failed looking for our outbound gateway"; } + public void runJob() { + if (_log.shouldLog(Log.ERROR)) + _log.error("lookup of " + _config.getPeer(1).toBase64().substring(0,4) + + " failed for " + _config); + } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/OutboundTunnelEndpoint.java b/router/java/src/net/i2p/router/tunnel/OutboundTunnelEndpoint.java new file mode 100644 index 000000000..2bc8d9e2c --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundTunnelEndpoint.java @@ -0,0 +1,51 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.Hash; +import net.i2p.data.RouterInfo; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelDataMessage; +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; + +/** + * We are the end of an outbound tunnel that we did not create. Gather fragments + * and honor the instructions as received. + * + */ +public class OutboundTunnelEndpoint { + private RouterContext _context; + private Log _log; + private HopConfig _config; + private HopProcessor _processor; + private FragmentHandler _handler; + private OutboundMessageDistributor _outDistributor; + + public OutboundTunnelEndpoint(RouterContext ctx, HopConfig config, HopProcessor processor) { + _context = ctx; + _log = ctx.logManager().getLog(OutboundTunnelEndpoint.class); + _config = config; + _processor = processor; + _handler = new RouterFragmentHandler(ctx, new DefragmentedHandler()); + _outDistributor = new OutboundMessageDistributor(ctx); + } + public void dispatch(TunnelDataMessage msg, Hash recvFrom) { + _config.incrementProcessedMessages(); + _processor.process(msg.getData(), 0, msg.getData().length, recvFrom); + _handler.receiveTunnelMessage(msg.getData(), 0, msg.getData().length); + } + + private class DefragmentedHandler implements FragmentHandler.DefragmentedReceiver { + public void receiveComplete(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("outbound tunnel " + _config + " received a full message: " + msg + + " to be forwarded on to " + + (toRouter != null ? toRouter.toBase64().substring(0,4) : "") + + (toTunnel != null ? toTunnel.getTunnelId() + "" : "")); + _outDistributor.distribute(msg, toRouter, toTunnel); + } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/RouterFragmentHandler.java b/router/java/src/net/i2p/router/tunnel/RouterFragmentHandler.java new file mode 100644 index 000000000..3d712699f --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/RouterFragmentHandler.java @@ -0,0 +1,25 @@ +package net.i2p.router.tunnel; + +import net.i2p.router.RouterContext; + +/** + * Minor extension to allow message history integration + */ +public class RouterFragmentHandler extends FragmentHandler { + private RouterContext _routerContext; + + public RouterFragmentHandler(RouterContext context, DefragmentedReceiver receiver) { + super(context, receiver); + _routerContext = context; + } + + protected void noteReception(long messageId, int fragmentId) { + _routerContext.messageHistory().receiveTunnelFragment(messageId, fragmentId); + } + protected void noteCompletion(long messageId) { + _routerContext.messageHistory().receiveTunnelFragmentComplete(messageId); + } + protected void noteFailure(long messageId) { + _routerContext.messageHistory().droppedFragmentedMessage(messageId); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java new file mode 100644 index 000000000..5c8e76b40 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java @@ -0,0 +1,20 @@ +package net.i2p.router.tunnel; + +import net.i2p.router.RouterContext; + +/** + * Minor extension to track fragmentation + * + */ +public class TrivialRouterPreprocessor extends TrivialPreprocessor { + private RouterContext _routerContext; + + public TrivialRouterPreprocessor(RouterContext ctx) { + super(ctx); + _routerContext = ctx; + } + + protected void notePreprocessing(long messageId, int numFragments) { + _routerContext.messageHistory().fragmentMessage(messageId, numFragments); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java new file mode 100644 index 000000000..492bad8f8 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java @@ -0,0 +1,488 @@ +package net.i2p.router.tunnel; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.i2p.router.RouterContext; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.RouterInfo; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.Service; +import net.i2p.util.Log; + +/** + * Handle the actual processing and forwarding of messages through the + * various tunnels. + * + */ +public class TunnelDispatcher implements Service { + private RouterContext _context; + private Log _log; + private Map _outboundGateways; + private Map _outboundEndpoints; + private Map _participants; + private Map _inboundGateways; + /** id to HopConfig */ + private Map _participatingConfig; + /** what is the date/time on which the last non-locally-created tunnel expires? */ + private long _lastParticipatingExpiration; + private BloomFilterIVValidator _validator; + + /** Creates a new instance of TunnelDispatcher */ + public TunnelDispatcher(RouterContext ctx) { + _context = ctx; + _log = ctx.logManager().getLog(TunnelDispatcher.class); + _outboundGateways = new HashMap(); + _outboundEndpoints = new HashMap(); + _participants = new HashMap(); + _inboundGateways = new HashMap(); + _participatingConfig = new HashMap(); + _lastParticipatingExpiration = 0; + _validator = null; + ctx.statManager().createRateStat("tunnel.participatingTunnels", + "How many tunnels are we participating in?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchOutboundPeer", + "How many messages we send out a tunnel targetting a peer?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchOutboundTunnel", + "How many messages we send out a tunnel targetting a tunnel?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchInbound", + "How many messages we send through our tunnel gateway?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchParticipant", + "How many messages we send through a tunnel we are participating in?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchEndpoint", + "How many messages we receive as the outbound endpoint of a tunnel?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinOutboundGateway", + "How many tunnels we join as the outbound gateway?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinOutboundGatewayZeroHop", + "How many zero hop tunnels we join as the outbound gateway?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinInboundEndpoint", + "How many tunnels we join as the inbound endpoint?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinInboundEndpointZeroHop", + "How many zero hop tunnels we join as the inbound endpoint?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinParticipant", + "How many tunnels we join as a participant?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinOutboundEndpoint", + "How many tunnels we join as the outbound endpoint?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.joinInboundGateway", + "How many tunnels we join as the inbound gateway?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchGatewayTime", + "How long it takes to dispatch a TunnelGatewayMessage", "Tunnels", + new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchDataTime", + "How long it takes to dispatch a TunnelDataMessage", "Tunnels", + new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchOutboundTime", + "How long it takes to dispatch an outbound message", "Tunnels", + new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.dispatchOutboundZeroHopTime", + "How long it takes to dispatch an outbound message through a zero hop tunnel", "Tunnels", + new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.participatingMessageCount", + "How many messages are sent through a participating tunnel?", "Tunnels", + new long[] { 60*10*1000l, 60*60*1000l, 24*60*60*1000l }); + } + + /** + * We are the outbound gateway - we created this tunnel + */ + public void joinOutbound(TunnelCreatorConfig cfg) { + if (_log.shouldLog(Log.INFO)) + _log.info("Outbound built successfully: " + cfg); + if (cfg.getLength() > 1) { + TunnelGateway.QueuePreprocessor preproc = new TrivialRouterPreprocessor(_context); + TunnelGateway.Sender sender = new OutboundSender(_context, cfg); + TunnelGateway.Receiver receiver = new OutboundReceiver(_context, cfg); + TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver); + TunnelId outId = cfg.getConfig(0).getSendTunnel(); + synchronized (_outboundGateways) { + _outboundGateways.put(outId, gw); + } + _context.statManager().addRateData("tunnel.joinOutboundGateway", 1, 0); + } else { + TunnelGatewayZeroHop gw = new TunnelGatewayZeroHop(_context, cfg); + TunnelId outId = cfg.getConfig(0).getSendTunnel(); + synchronized (_outboundGateways) { + _outboundGateways.put(outId, gw); + } + _context.statManager().addRateData("tunnel.joinOutboundGatewayZeroHop", 1, 0); + } + } + /** + * We are the inbound endpoint - we created this tunnel + */ + public void joinInbound(TunnelCreatorConfig cfg) { + if (_log.shouldLog(Log.INFO)) + _log.info("Inbound built successfully: " + cfg); + + if (cfg.getLength() > 1) { + TunnelParticipant participant = new TunnelParticipant(_context, new InboundEndpointProcessor(_context, cfg, _validator)); + TunnelId recvId = cfg.getConfig(cfg.getLength()-1).getReceiveTunnel(); + synchronized (_participants) { + _participants.put(recvId, participant); + } + _context.statManager().addRateData("tunnel.joinInboundEndpoint", 1, 0); + } else { + TunnelGatewayZeroHop gw = new TunnelGatewayZeroHop(_context, cfg); + TunnelId recvId = cfg.getConfig(0).getReceiveTunnel(); + synchronized (_inboundGateways) { + _inboundGateways.put(recvId, gw); + } + _context.statManager().addRateData("tunnel.joinInboundEndpointZeroHop", 1, 0); + } + } + + /** + * We are a participant in this tunnel, but not as the endpoint or gateway + * + */ + public void joinParticipant(HopConfig cfg) { + if (_log.shouldLog(Log.INFO)) + _log.info("Joining as participant: " + cfg); + TunnelId recvId = cfg.getReceiveTunnel(); + TunnelParticipant participant = new TunnelParticipant(_context, cfg, new HopProcessor(_context, cfg, _validator)); + synchronized (_participants) { + _participants.put(recvId, participant); + } + int numParticipants = 0; + synchronized (_participatingConfig) { + _participatingConfig.put(recvId, cfg); + numParticipants = _participatingConfig.size(); + } + _context.statManager().addRateData("tunnel.participatingTunnels", numParticipants, 0); + _context.statManager().addRateData("tunnel.joinParticipant", 1, 0); + if (cfg.getExpiration() > _lastParticipatingExpiration) + _lastParticipatingExpiration = cfg.getExpiration(); + _context.jobQueue().addJob(new LeaveTunnel(_context, cfg)); + } + /** + * We are the outbound endpoint in this tunnel, and did not create it + * + */ + public void joinOutboundEndpoint(HopConfig cfg) { + if (_log.shouldLog(Log.INFO)) + _log.info("Joining as outbound endpoint: " + cfg); + TunnelId recvId = cfg.getReceiveTunnel(); + OutboundTunnelEndpoint endpoint = new OutboundTunnelEndpoint(_context, cfg, new HopProcessor(_context, cfg, _validator)); + synchronized (_outboundEndpoints) { + _outboundEndpoints.put(recvId, endpoint); + } + int numParticipants = 0; + synchronized (_participatingConfig) { + _participatingConfig.put(recvId, cfg); + numParticipants = _participatingConfig.size(); + } + _context.statManager().addRateData("tunnel.participatingTunnels", numParticipants, 0); + _context.statManager().addRateData("tunnel.joinOutboundEndpoint", 1, 0); + + if (cfg.getExpiration() > _lastParticipatingExpiration) + _lastParticipatingExpiration = cfg.getExpiration(); + _context.jobQueue().addJob(new LeaveTunnel(_context, cfg)); + } + + /** + * We are the inbound gateway in this tunnel, and did not create it + * + */ + public void joinInboundGateway(HopConfig cfg) { + if (_log.shouldLog(Log.INFO)) + _log.info("Joining as inbound gateway: " + cfg); + TunnelGateway.QueuePreprocessor preproc = new TrivialRouterPreprocessor(_context); + TunnelGateway.Sender sender = new InboundSender(_context, cfg); + TunnelGateway.Receiver receiver = new InboundGatewayReceiver(_context, cfg); + TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver); + TunnelId recvId = cfg.getReceiveTunnel(); + synchronized (_inboundGateways) { + _inboundGateways.put(recvId, gw); + } + int numParticipants = 0; + synchronized (_participatingConfig) { + _participatingConfig.put(recvId, cfg); + numParticipants = _participatingConfig.size(); + } + _context.statManager().addRateData("tunnel.participatingTunnels", numParticipants, 0); + _context.statManager().addRateData("tunnel.joinInboundGateway", 1, 0); + + if (cfg.getExpiration() > _lastParticipatingExpiration) + _lastParticipatingExpiration = cfg.getExpiration(); + _context.jobQueue().addJob(new LeaveTunnel(_context, cfg)); + } + + public int getParticipatingCount() { + synchronized (_participatingConfig) { + return _participatingConfig.size(); + } + } + + /** what is the date/time on which the last non-locally-created tunnel expires? */ + public long getLastParticipatingExpiration() { return _lastParticipatingExpiration; } + + /** + * We no longer want to participate in this tunnel that we created + */ + public void remove(TunnelCreatorConfig cfg) { + if (cfg.isInbound()) { + TunnelId recvId = cfg.getConfig(cfg.getLength()-1).getReceiveTunnel(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("removing our own inbound " + cfg); + boolean removed = false; + synchronized (_participants) { + removed = (null != _participants.remove(recvId)); + } + if (!removed) { + synchronized (_inboundGateways) { + _inboundGateways.remove(recvId); + } + } + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("removing our own outbound " + cfg); + TunnelId outId = cfg.getConfig(0).getSendTunnel(); + synchronized (_outboundGateways) { + _outboundGateways.remove(outId); + } + } + } + + /** + * No longer participate in the tunnel that someone asked us to be a member of + * + */ + public void remove(HopConfig cfg) { + TunnelId recvId = cfg.getReceiveTunnel(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("removing " + cfg); + + boolean removed = false; + synchronized (_participatingConfig) { + removed = (null != _participatingConfig.remove(recvId)); + } + if (!removed) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Participating tunnel, but no longer listed in participatingConfig? " + cfg); + } + + synchronized (_participants) { + removed = (null != _participants.remove(recvId)); + } + if (removed) return; + synchronized (_inboundGateways) { + removed = (null != _inboundGateways.remove(recvId)); + } + if (removed) return; + synchronized (_outboundEndpoints) { + removed = (null != _outboundEndpoints.remove(recvId)); + } + + _context.statManager().addRateData("tunnel.participatingMessageCount", cfg.getProcessedMessagesCount(), 10*60*1000); + } + + /** + * We are participating in a tunnel (perhaps we're even the endpoint), so + * take the message and do what it says. If there are later hops, that + * means encrypt a layer and forward it on. If there aren't later hops, + * how we handle it depends upon whether we created it or not. If we didn't, + * simply honor the instructions. If we did, unwrap all the layers of + * encryption and honor those instructions (within reason). + * + */ + public void dispatch(TunnelDataMessage msg, Hash recvFrom) { + long before = _context.clock().now(); + TunnelParticipant participant = null; + synchronized (_participants) { + participant = (TunnelParticipant)_participants.get(msg.getTunnelId()); + } + if (participant != null) { + // we are either just a random participant or the inbound endpoint + if (_log.shouldLog(Log.DEBUG)) + _log.debug("dispatch to participant " + participant + ": " + msg.getUniqueId() + " from " + + recvFrom.toBase64().substring(0,4)); + participant.dispatch(msg, recvFrom); + _context.statManager().addRateData("tunnel.dispatchParticipant", 1, 0); + } else { + OutboundTunnelEndpoint endpoint = null; + synchronized (_outboundEndpoints) { + endpoint = (OutboundTunnelEndpoint)_outboundEndpoints.get(msg.getTunnelId()); + } + if (endpoint != null) { + // we are the outobund endpoint + if (_log.shouldLog(Log.DEBUG)) + _log.debug("dispatch where we are the outbound endpoint: " + endpoint + ": " + + msg + " from " + recvFrom.toBase64().substring(0,4)); + endpoint.dispatch(msg, recvFrom); + _context.statManager().addRateData("tunnel.dispatchEndpoint", 1, 0); + } else { + _context.messageHistory().droppedTunnelDataMessageUnknown(msg.getUniqueId(), msg.getTunnelId().getTunnelId()); + if (_log.shouldLog(Log.ERROR)) + _log.error("no matching participant/endpoint for id=" + msg.getTunnelId().getTunnelId() + + ": existing = " + _participants.keySet() + + " / " + _outboundEndpoints.keySet()); + } + } + + long dispatchTime = _context.clock().now() - before; + _context.statManager().addRateData("tunnel.dispatchDataTime", dispatchTime, dispatchTime); + } + + /** + * We are the inbound tunnel gateway, so encrypt it as necessary and forward + * it on. + * + */ + public void dispatch(TunnelGatewayMessage msg) { + long before = _context.clock().now(); + TunnelGateway gw = null; + synchronized (_inboundGateways) { + gw = (TunnelGateway)_inboundGateways.get(msg.getTunnelId()); + } + if (gw != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("dispatch where we are the inbound gateway: " + gw + ": " + msg); + if ( (msg.getMessageExpiration() < before - Router.CLOCK_FUDGE_FACTOR) || (msg.getMessage().getMessageExpiration() < before - Router.CLOCK_FUDGE_FACTOR) ) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Not dispatching a gateway message for tunnel " + msg.getTunnelId().getTunnelId() + + " as the wrapper's expiration is in " + DataHelper.formatDuration(msg.getMessageExpiration()-before) + + " and/or the content's expiration is in " + DataHelper.formatDuration(msg.getMessage().getMessageExpiration()-before) + + " with messageId " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() + " and message type " + + msg.getMessage().getClass().getName()); + return; + } + gw.add(msg); + _context.statManager().addRateData("tunnel.dispatchInbound", 1, 0); + } else { + _context.messageHistory().droppedTunnelGatewayMessageUnknown(msg.getUniqueId(), msg.getTunnelId().getTunnelId()); + if (_log.shouldLog(Log.ERROR)) + _log.error("no matching tunnel for id=" + msg.getTunnelId().getTunnelId() + + ": gateway message expiring in " + + DataHelper.formatDuration(msg.getMessageExpiration()-_context.clock().now()) + + "/" + + DataHelper.formatDuration(msg.getMessage().getMessageExpiration()-_context.clock().now()) + + " messageId " + msg.getUniqueId() + + "/" + msg.getMessage().getUniqueId() + + " messageType: " + msg.getMessage().getClass().getName() + + " existing = " + _inboundGateways.keySet()); + } + + long dispatchTime = _context.clock().now() - before; + _context.statManager().addRateData("tunnel.dispatchGatewayTime", dispatchTime, dispatchTime); + } + + /** + * We are the outbound tunnel gateway (we created it), so wrap up this message + * with instructions to be forwarded to the targetPeer when it reaches the + * endpoint. + * + * @param msg raw message to deliver to the target peer + * @param outboundTunnel tunnel to send the message out + * @param targetPeer peer to receive the message + */ + public void dispatchOutbound(I2NPMessage msg, TunnelId outboundTunnel, Hash targetPeer) { + dispatchOutbound(msg, outboundTunnel, null, targetPeer); + } + /** + * We are the outbound tunnel gateway (we created it), so wrap up this message + * with instructions to be forwarded to the targetTunnel on the targetPeer when + * it reaches the endpoint. + * + * @param msg raw message to deliver to the targetTunnel on the targetPeer + * @param outboundTunnel tunnel to send the message out + * @param targetTunnel tunnel on the targetPeer to deliver the message to + * @param targetPeer gateway to the tunnel to receive the message + */ + public void dispatchOutbound(I2NPMessage msg, TunnelId outboundTunnel, TunnelId targetTunnel, Hash targetPeer) { + if (outboundTunnel == null) throw new IllegalArgumentException("wtf, null outbound tunnel?"); + long before = _context.clock().now(); + TunnelGateway gw = null; + synchronized (_outboundGateways) { + gw = (TunnelGateway)_outboundGateways.get(outboundTunnel); + } + if (gw != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("dispatch outbound through " + outboundTunnel.getTunnelId() + + ": " + msg); + if (msg.getMessageExpiration() < before) { + if (_log.shouldLog(Log.ERROR)) + _log.error("why are you sending a tunnel message that expired " + + (before-msg.getMessageExpiration()) + "ms ago? " + + msg, new Exception("cause")); + return; + } + gw.add(msg, targetPeer, targetTunnel); + if (targetTunnel == null) + _context.statManager().addRateData("tunnel.dispatchOutboundPeer", 1, 0); + else + _context.statManager().addRateData("tunnel.dispatchOutboundTunnel", 1, 0); + } else { + _context.messageHistory().droppedTunnelGatewayMessageUnknown(msg.getUniqueId(), outboundTunnel.getTunnelId()); + + if (_log.shouldLog(Log.ERROR)) + _log.error("no matching outbound tunnel for id=" + outboundTunnel + + ": existing = " + _outboundGateways.keySet()); + } + + long dispatchTime = _context.clock().now() - before; + if (gw instanceof TunnelGatewayZeroHop) + _context.statManager().addRateData("tunnel.dispatchOutboundZeroHopTime", dispatchTime, dispatchTime); + else + _context.statManager().addRateData("tunnel.dispatchOutboundTime", dispatchTime, dispatchTime); + } + + public List listParticipatingTunnels() { + synchronized (_participatingConfig) { + return new ArrayList(_participatingConfig.values()); + } + } + + public void startup() { + // NB: 256 == assume max rate (size adjusted to handle 256 messages per second) + _validator = new BloomFilterIVValidator(_context, 256); + } + public void shutdown() { + if (_validator != null) + _validator.destroy(); + _validator = null; + } + public void restart() { + shutdown(); + startup(); + } + + public void renderStatusHTML(Writer out) throws IOException {} + + private class LeaveTunnel extends JobImpl { + private HopConfig _config; + + public LeaveTunnel(RouterContext ctx, HopConfig config) { + super(ctx); + _config = config; + getTiming().setStartAfter(config.getExpiration() + 2*Router.CLOCK_FUDGE_FACTOR); + } + public String getName() { return "Leave participant"; } + public void runJob() { + remove(_config); + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java new file mode 100644 index 000000000..18ce4d66a --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayZeroHop.java @@ -0,0 +1,70 @@ +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.data.i2np.TunnelGatewayMessage; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; +import net.i2p.util.SimpleTimer; + +/** + * Serve as the gatekeeper for a tunnel with no hops. + * + */ +public class TunnelGatewayZeroHop extends TunnelGateway { + private RouterContext _context; + private Log _log; + private TunnelCreatorConfig _config; + private OutboundMessageDistributor _outDistributor; + private InboundMessageDistributor _inDistributor; + + /** + * + */ + public TunnelGatewayZeroHop(RouterContext context, TunnelCreatorConfig config) { + super(context, null, null, null); + _context = context; + _log = context.logManager().getLog(TunnelGatewayZeroHop.class); + _config = config; + if (config.isInbound()) + _inDistributor = new InboundMessageDistributor(_context, config.getDestination()); + else + _outDistributor = new OutboundMessageDistributor(context); + } + + /** + * Add a message to be sent down the tunnel, where we are the inbound gateway. + * + * @param msg message received to be sent through the tunnel + */ + public void add(TunnelGatewayMessage msg) { + add(msg.getMessage(), null, null); + } + + /** + * Add a message to be sent down the tunnel (immediately forwarding it to the + * {@link InboundMessageDistributor} or {@link OutboundMessageDistributor}, as + * necessary). + * + * @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) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("zero hop gateway: distribute " + (_config.isInbound() ? "inbound " : " outbound ") + + " to " + (toRouter != null ? toRouter.toBase64().substring(0,4) : "" ) + + "." + (toTunnel != null ? toTunnel.getTunnelId() + "" : "") + + ": " + msg); + if (_config.isInbound()) { + _inDistributor.distribute(msg, toRouter, toTunnel); + } else { + _outDistributor.distribute(msg, toRouter, toTunnel); + } + _config.incrementProcessedMessages(); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java new file mode 100644 index 000000000..19ebc5ec3 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java @@ -0,0 +1,157 @@ +package net.i2p.router.tunnel; + +import java.util.Date; + +import net.i2p.data.Hash; +import net.i2p.data.RouterInfo; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * Participate in a tunnel at a location other than the gateway or outbound + * endpoint. This participant should be provided with the necessary processor + * if it is an inbound tunnel endpoint, and that will enable the + * InboundMessageDistributor to receive defragmented and decrypted messages, + * which it will then selectively forward. + */ +public class TunnelParticipant { + private RouterContext _context; + private Log _log; + private HopConfig _config; + private HopProcessor _processor; + private InboundEndpointProcessor _inboundEndpointProcessor; + private InboundMessageDistributor _inboundDistributor; + private FragmentHandler _handler; + + public TunnelParticipant(RouterContext ctx, HopConfig config, HopProcessor processor) { + this(ctx, config, processor, null); + } + public TunnelParticipant(RouterContext ctx, InboundEndpointProcessor inEndProc) { + this(ctx, null, null, inEndProc); + } + private TunnelParticipant(RouterContext ctx, HopConfig config, HopProcessor processor, InboundEndpointProcessor inEndProc) { + _context = ctx; + _log = ctx.logManager().getLog(TunnelParticipant.class); + _config = config; + _processor = processor; + if ( (config == null) || (config.getSendTo() == null) ) + _handler = new RouterFragmentHandler(ctx, new DefragmentedHandler()); + _inboundEndpointProcessor = inEndProc; + if (inEndProc != null) + _inboundDistributor = new InboundMessageDistributor(ctx, inEndProc.getDestination()); + } + + public void dispatch(TunnelDataMessage msg, Hash recvFrom) { + boolean ok = false; + if (_processor != null) + ok = _processor.process(msg.getData(), 0, msg.getData().length, recvFrom); + else if (_inboundEndpointProcessor != null) + ok = _inboundEndpointProcessor.retrievePreprocessedData(msg.getData(), 0, msg.getData().length, recvFrom); + + if (!ok) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Failed to dispatch " + msg + ": processor=" + _processor + + " inboundEndpoint=" + _inboundEndpointProcessor); + return; + } + + if ( (_config != null) && (_config.getSendTo() != null) ) { + _config.incrementProcessedMessages(); + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo()); + if (ri != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Send off to nextHop directly (" + _config.getSendTo().toBase64().substring(0,4) + + " for " + msg); + send(_config, msg, ri); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Lookup the nextHop (" + _config.getSendTo().toBase64().substring(0,4) + + " for " + msg); + _context.netDb().lookupRouterInfo(_config.getSendTo(), new SendJob(msg), new TimeoutJob(msg), 10*1000); + } + } else { + _inboundEndpointProcessor.getConfig().incrementProcessedMessages(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive fragment: on " + _config + ": " + msg); + _handler.receiveTunnelMessage(msg.getData(), 0, msg.getData().length); + } + } + + private class DefragmentedHandler implements FragmentHandler.DefragmentedReceiver { + public void receiveComplete(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive complete: on " + _config + ": " + msg); + _inboundDistributor.distribute(msg, toRouter, toTunnel); + } + + } + + private void send(HopConfig config, TunnelDataMessage msg, RouterInfo ri) { + msg.setUniqueId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE)); + msg.setMessageExpiration(_context.clock().now() + 10*1000); + OutNetMessage m = new OutNetMessage(_context); + msg.setTunnelId(config.getSendTunnel()); + m.setMessage(msg); + m.setExpiration(msg.getMessageExpiration()); + m.setTarget(ri); + m.setPriority(400); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Forward on from " + _config + ": " + msg); + _context.outNetMessagePool().add(m); + } + + private class SendJob extends JobImpl { + private TunnelDataMessage _msg; + public SendJob(TunnelDataMessage msg) { + super(_context); + _msg = msg; + } + public String getName() { return "forward a tunnel message"; } + public void runJob() { + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo()); + if (ri != null) { + send(_config, _msg, ri); + } else { + if (_log.shouldLog(Log.ERROR)) + _log.error("Lookup the nextHop (" + _config.getSendTo().toBase64().substring(0,4) + + " failed! where do we go for " + _config + "? msg dropped: " + _msg); + } + } + } + + private class TimeoutJob extends JobImpl { + private TunnelDataMessage _msg; + public TimeoutJob(TunnelDataMessage msg) { + super(_context); + _msg = msg; + } + public String getName() { return "timeout looking for next hop info"; } + public void runJob() { + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo()); + if (ri != null) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Lookup the nextHop (" + _config.getSendTo().toBase64().substring(0,4) + + " failed, but we found it!! where do we go for " + _config + "? msg dropped: " + _msg); + } else { + if (_log.shouldLog(Log.ERROR)) + _log.error("Lookup the nextHop (" + _config.getSendTo().toBase64().substring(0,4) + + " failed! where do we go for " + _config + "? msg dropped: " + _msg); + } + } + } + + public String toString() { + if (_config != null) { + StringBuffer buf = new StringBuffer(64); + buf.append("participant at ").append(_config.toString()); + return buf.toString(); + } else { + return "inbound endpoint"; + } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java new file mode 100644 index 000000000..4480ab6d2 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java @@ -0,0 +1,32 @@ +package net.i2p.router.tunnel.pool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelPoolSettings; + +/** + * Pick peers randomly out of the fast pool, and put them into tunnels in a + * random order + * + */ +class ClientPeerSelector extends TunnelPeerSelector { + public List selectPeers(RouterContext ctx, TunnelPoolSettings settings) { + int length = getLength(ctx, settings); + if (length < 0) + return null; + HashSet matches = new HashSet(length); + ctx.profileOrganizer().selectFastPeers(length, null, matches); + + matches.remove(ctx.routerHash()); + ArrayList rv = new ArrayList(matches); + Collections.shuffle(rv, ctx.random()); + if (settings.isInbound()) + rv.add(0, ctx.routerHash()); + else + rv.add(ctx.routerHash()); + return rv; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java new file mode 100644 index 000000000..f21aaa4b2 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java @@ -0,0 +1,49 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.router.JobImpl; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.tunnel.TunnelCreatorConfig; + +class ExpireJob extends JobImpl { + private TunnelPool _pool; + private TunnelCreatorConfig _cfg; + private Object _buildToken; + private boolean _leaseUpdated; + public ExpireJob(RouterContext ctx, TunnelCreatorConfig cfg, TunnelPool pool, Object buildToken) { + super(ctx); + _pool = pool; + _cfg = cfg; + _buildToken = buildToken; + _leaseUpdated = false; + // give 'em some extra time before dropping 'em + getTiming().setStartAfter(cfg.getExpiration()); // + Router.CLOCK_FUDGE_FACTOR); + } + public String getName() { + if (_pool.getSettings().isExploratory()) { + if (_pool.getSettings().isInbound()) { + return "Expire exploratory inbound tunnel"; + } else { + return "Expire exploratory outbound tunnel"; + } + } else { + if (_pool.getSettings().isInbound()) { + return "Expire client inbound tunnel"; + } else { + return "Expire client outbound tunnel"; + } + } + } + public void runJob() { + if (!_leaseUpdated) { + _pool.removeTunnel(_cfg); + _leaseUpdated = true; + _pool.refreshLeaseSet(); + requeue(Router.CLOCK_FUDGE_FACTOR); + } else { + // already removed/refreshed, but now lets make it + // so we dont even honor the tunnel anymore + getContext().tunnelDispatcher().remove(_cfg); + } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java new file mode 100644 index 000000000..71cdbd5c8 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java @@ -0,0 +1,32 @@ +package net.i2p.router.tunnel.pool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelPoolSettings; + +/** + * Pick peers randomly out of the not-failing pool, and put them into randomly + * ordered tunnels. + * + */ +class ExploratoryPeerSelector extends TunnelPeerSelector { + public List selectPeers(RouterContext ctx, TunnelPoolSettings settings) { + int length = getLength(ctx, settings); + if (length < 0) + return null; + HashSet matches = new HashSet(length); + ctx.profileOrganizer().selectNotFailingPeers(length, null, matches); + + matches.remove(ctx.routerHash()); + ArrayList rv = new ArrayList(matches); + Collections.shuffle(rv, ctx.random()); + if (settings.isInbound()) + rv.add(0, ctx.routerHash()); + else + rv.add(ctx.routerHash()); + return rv; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java b/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java new file mode 100644 index 000000000..e7b2e7034 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/HandleTunnelCreateMessageJob.java @@ -0,0 +1,164 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.data.Certificate; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.RouterIdentity; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.DeliveryInstructions; +import net.i2p.data.i2np.GarlicMessage; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelCreateMessage; +import net.i2p.data.i2np.TunnelCreateStatusMessage; +import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.router.HandlerJobBuilder; +import net.i2p.router.Job; +import net.i2p.router.JobImpl; +import net.i2p.router.RouterContext; +import net.i2p.router.message.GarlicMessageBuilder; +import net.i2p.router.message.PayloadGarlicConfig; +import net.i2p.router.message.SendMessageDirectJob; +import net.i2p.router.tunnel.HopConfig; +import net.i2p.util.Log; + +/** + * Receive a request to join a tunnel, and if we aren't overloaded (per the + * throttle), join it (updating the tunnelDispatcher), then send back the + * agreement. Even if we are overloaded, send back a reply stating how + * overloaded we are. + * + */ +public class HandleTunnelCreateMessageJob extends JobImpl { + private Log _log; + private TunnelCreateMessage _request; + + /** job builder to redirect all tunnelCreateMessages through this job type */ + static class Builder implements HandlerJobBuilder { + private RouterContext _ctx; + public Builder(RouterContext ctx) { _ctx = ctx; } + public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + return new HandleTunnelCreateMessageJob(_ctx, (TunnelCreateMessage)receivedMessage); + } + } + + + public HandleTunnelCreateMessageJob(RouterContext ctx, TunnelCreateMessage msg) { + super(ctx); + _log = ctx.logManager().getLog(HandleTunnelCreateMessageJob.class); + _request = msg; + } + + public String getName() { return "Handle tunnel join request"; } + public void runJob() { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("handle join request: " + _request); + int status = shouldAccept(); + if (status > 0) { + if (_log.shouldLog(Log.WARN)) + _log.warn("reject(" + status + ") join request: " + _request); + sendRejection(status); + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("accept join request: " + _request); + accept(); + } + } + + private int shouldAccept() { return getContext().throttle().acceptTunnelRequest(_request); } + + private void accept() { + byte recvId[] = new byte[4]; + getContext().random().nextBytes(recvId); + + HopConfig cfg = new HopConfig(); + long expiration = _request.getDurationSeconds()*1000 + getContext().clock().now(); + cfg.setExpiration(expiration); + cfg.setIVKey(_request.getIVKey()); + cfg.setLayerKey(_request.getLayerKey()); + cfg.setOptions(_request.getOptions()); + cfg.setReceiveTunnelId(recvId); + + if (_request.getIsGateway()) { + if (_log.shouldLog(Log.INFO)) + _log.info("join as inbound tunnel gateway pointing at " + + _request.getNextRouter().toBase64().substring(0,4) + ":" + + _request.getNextTunnelId().getTunnelId() + + " (nonce=" + _request.getNonce() + ")"); + // serve as the inbound tunnel gateway + cfg.setSendTo(_request.getNextRouter()); + cfg.setSendTunnelId(DataHelper.toLong(4, _request.getNextTunnelId().getTunnelId())); + getContext().tunnelDispatcher().joinInboundGateway(cfg); + } else if (_request.getNextRouter() == null) { + if (_log.shouldLog(Log.INFO)) + _log.info("join as outbound tunnel endpoint (nonce=" + _request.getNonce() + ")"); + // serve as the outbound tunnel endpoint + getContext().tunnelDispatcher().joinOutboundEndpoint(cfg); + } else { + if (_log.shouldLog(Log.INFO)) + _log.info("join as tunnel participant pointing at " + + _request.getNextRouter().toBase64().substring(0,4) + ":" + + _request.getNextTunnelId().getTunnelId() + + " (nonce=" + _request.getNonce() + ")"); + // serve as a general participant + cfg.setSendTo(_request.getNextRouter()); + cfg.setSendTunnelId(DataHelper.toLong(4, _request.getNextTunnelId().getTunnelId())); + getContext().tunnelDispatcher().joinParticipant(cfg); + } + + sendAcceptance(recvId); + } + + private static final byte[] REJECTION_TUNNEL_ID = new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF }; + private static final int REPLY_TIMEOUT = 30*1000; + private static final int REPLY_PRIORITY = 500; + + private void sendAcceptance(byte recvId[]) { + sendReply(recvId, TunnelCreateStatusMessage.STATUS_SUCCESS); + } + private void sendRejection(int severity) { + sendReply(REJECTION_TUNNEL_ID, severity); + } + private void sendReply(byte recvId[], int status) { + TunnelCreateStatusMessage reply = new TunnelCreateStatusMessage(getContext()); + reply.setNonce(_request.getNonce()); + reply.setReceiveTunnelId(new TunnelId(DataHelper.fromLong(recvId, 0, 4))); + reply.setStatus(status); + + GarlicMessage msg = createReply(reply); + if (msg == null) + throw new RuntimeException("wtf, couldn't create reply? to " + _request); + + TunnelGatewayMessage gw = new TunnelGatewayMessage(getContext()); + gw.setMessage(msg); + gw.setTunnelId(_request.getReplyTunnel()); + gw.setMessageExpiration(msg.getMessageExpiration()); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("sending (" + status + ") to the tunnel " + + _request.getReplyGateway().toBase64().substring(0,4) + ":" + + _request.getReplyTunnel() + " wrt " + _request); + getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), gw, _request.getReplyGateway(), + REPLY_TIMEOUT, REPLY_PRIORITY)); + } + + private GarlicMessage createReply(TunnelCreateStatusMessage reply) { + DeliveryInstructions instructions = new DeliveryInstructions(); + instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); + + PayloadGarlicConfig cfg = new PayloadGarlicConfig(); + cfg.setPayload(reply); + cfg.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + cfg.setDeliveryInstructions(instructions); + cfg.setRequestAck(false); + cfg.setExpiration(getContext().clock().now() + REPLY_TIMEOUT); + cfg.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); + + GarlicMessage msg = GarlicMessageBuilder.buildMessage(getContext(), cfg, + null, // we dont care about the tags + null, // or keys sent + null, // and we don't know what public key to use + _request.getReplyKey(), _request.getReplyTag()); + return msg; + } + +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/OnCreatedJob.java b/router/java/src/net/i2p/router/tunnel/pool/OnCreatedJob.java new file mode 100644 index 000000000..a57b32a9d --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/OnCreatedJob.java @@ -0,0 +1,50 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.router.JobImpl; +import net.i2p.router.RouterContext; +import net.i2p.router.tunnel.TunnelCreatorConfig; +import net.i2p.util.Log; + +/** + * The tunnel is fully built, so now add it to our handler, to the pool, and + * build the necessary test and rebuilding jobs. + * + */ +class OnCreatedJob extends JobImpl { + private Log _log; + private TunnelPool _pool; + private Object _buildToken; + private PooledTunnelCreatorConfig _cfg; + private boolean _fake; + + public OnCreatedJob(RouterContext ctx, TunnelPool pool, PooledTunnelCreatorConfig cfg, boolean fake, Object buildToken) { + super(ctx); + _log = ctx.logManager().getLog(OnCreatedJob.class); + _pool = pool; + _cfg = cfg; + _fake = fake; + _buildToken = buildToken; + } + public String getName() { return "Tunnel built"; } + public void runJob() { + _log.debug("Created successfully: " + _cfg); + if (_cfg.isInbound()) { + getContext().tunnelDispatcher().joinInbound(_cfg); + } else { + getContext().tunnelDispatcher().joinOutbound(_cfg); + } + _pool.addTunnel(_cfg); + TestJob testJob = (_cfg.getLength() > 1 ? new TestJob(getContext(), _cfg, _pool, _buildToken) : null); + RebuildJob rebuildJob = (_fake ? null : new RebuildJob(getContext(), _cfg, _pool, _buildToken)); + ExpireJob expireJob = new ExpireJob(getContext(), _cfg, _pool, _buildToken); + _cfg.setTunnelPool(_pool); + _cfg.setTestJob(testJob); + _cfg.setRebuildJob(rebuildJob); + _cfg.setExpireJob(expireJob); + if (_cfg.getLength() > 1) // no need to test 0 hop tunnels + getContext().jobQueue().addJob(testJob); + if (!_fake) // if we built a 0 hop tunnel in response to a failure, don't rebuild + getContext().jobQueue().addJob(rebuildJob); + getContext().jobQueue().addJob(expireJob); + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java new file mode 100644 index 000000000..91a2b2538 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java @@ -0,0 +1,61 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.data.Hash; +import net.i2p.router.RouterContext; +import net.i2p.router.tunnel.TunnelCreatorConfig; + +/** + * + */ +public class PooledTunnelCreatorConfig extends TunnelCreatorConfig { + private RouterContext _context; + private TunnelPool _pool; + private boolean _failed; + private TestJob _testJob; + private RebuildJob _rebuildJob; + private ExpireJob _expireJob; + + /** Creates a new instance of PooledTunnelCreatorConfig */ + + public PooledTunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound) { + this(ctx, length, isInbound, null); + } + public PooledTunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound, Hash destination) { + super(length, isInbound, destination); + _context = ctx; + _failed = false; + _pool = null; + } + + + public void testSuccessful(int ms) { + if (_testJob != null) { + _testJob.testSuccessful(ms); + } + } + + /** + * The tunnel failed, so stop using it + */ + public void tunnelFailed() { + _failed = true; + // remove us from the pool (but not the dispatcher) so that we aren't + // selected again. _expireJob is left to do its thing, in case there + // are any straggling messages coming down the tunnel + _pool.tunnelFailed(this); + if (_rebuildJob != null) { + // rebuild asap (_rebuildJob will be null if we were just a stopgap) + _rebuildJob.getTiming().setStartAfter(_context.clock().now() + 10*1000); + _context.jobQueue().addJob(_rebuildJob); + } + if (_testJob != null) // just in case... + _context.jobQueue().removeJob(_testJob); + } + public boolean getTunnelFailed() { return _failed; } + public void setTunnelPool(TunnelPool pool) { _pool = pool; } + public TunnelPool getTunnelPool() { return _pool; } + + public void setTestJob(TestJob job) { _testJob = job; } + public void setRebuildJob(RebuildJob job) { _rebuildJob = job; } + public void setExpireJob(ExpireJob job) { _expireJob = job; } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java new file mode 100644 index 000000000..a0c3c00dd --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java @@ -0,0 +1,30 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.router.JobImpl; +import net.i2p.router.RouterContext; +import net.i2p.router.tunnel.TunnelCreatorConfig; + +/** + * Build a new tunnel to replace the existing one before it expires. This job + * should be removed (or scheduled to run immediately) if the tunnel fails. + * + */ +class RebuildJob extends JobImpl { + private TunnelPool _pool; + private Object _buildToken; + private TunnelCreatorConfig _cfg; + + public RebuildJob(RouterContext ctx, TunnelCreatorConfig cfg, TunnelPool pool, Object buildToken) { + super(ctx); + _pool = pool; + _cfg = cfg; + _buildToken = buildToken; + long rebuildOn = cfg.getExpiration() - pool.getSettings().getRebuildPeriod(); + rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod()); + getTiming().setStartAfter(rebuildOn); + } + public String getName() { return "Rebuild tunnel"; } + public void runJob() { + _pool.getBuilder().buildTunnel(getContext(), _pool, _buildToken); + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java new file mode 100644 index 000000000..5a28b8301 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java @@ -0,0 +1,360 @@ +package net.i2p.router.tunnel.pool; + +import java.util.HashSet; +import java.util.Set; + +import net.i2p.data.Certificate; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.PublicKey; +import net.i2p.data.RouterInfo; +import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelCreateMessage; +import net.i2p.data.i2np.TunnelCreateStatusMessage; + +import net.i2p.router.Job; +import net.i2p.router.JobImpl; +import net.i2p.router.MessageSelector; +import net.i2p.router.RouterContext; +import net.i2p.router.ReplyJob; +import net.i2p.router.TunnelInfo; +import net.i2p.router.tunnel.TunnelCreatorConfig; +import net.i2p.router.tunnel.HopConfig; +import net.i2p.router.peermanager.TunnelHistory; +import net.i2p.util.Log; + +/** + * queue up a job to request the endpoint to join the tunnel, which then + * requeues up another job for earlier hops, etc, until it reaches the + * gateway. after the gateway is confirmed, onCreated is fired. + * + */ +public class RequestTunnelJob extends JobImpl { + private Log _log; + private Job _onCreated; + private Job _onFailed; + private int _currentHop; + private RouterInfo _currentPeer; + private HopConfig _currentConfig; + private int _lookups; + private TunnelCreatorConfig _config; + private long _lastSendTime; + private boolean _isFake; + + static final int HOP_REQUEST_TIMEOUT = 30*1000; + private static final int LOOKUP_TIMEOUT = 10*1000; + + public RequestTunnelJob(RouterContext ctx, TunnelCreatorConfig cfg, Job onCreated, Job onFailed, int hop, boolean isFake) { + super(ctx); + _log = ctx.logManager().getLog(RequestTunnelJob.class); + _config = cfg; + _onCreated = onCreated; + _onFailed = onFailed; + _currentHop = hop; + _currentPeer = null; + _lookups = 0; + _lastSendTime = 0; + _isFake = isFake; + + ctx.statManager().createRateStat("tunnel.receiveRejectionProbabalistic", "How often we are rejected probabalistically?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.receiveRejectionTransient", "How often we are rejected due to transient overload?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.receiveRejectionBandwidth", "How often we are rejected due to bandwidth overload?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.receiveRejectionCritical", "How often we are rejected due to critical failure?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildFailure", "How often we fail to build a tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildSuccess", "How often we succeed building a tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Requesting hop " + hop + " in " + cfg); + if (hop < 0) + throw new IllegalArgumentException("invalid endpoint hop [" + hop + "] cfg: " + cfg); + } + + public String getName() { return "Request tunnel participation"; } + public void runJob() { + _currentConfig = _config.getConfig(_currentHop); + Hash peer = _config.getPeer(_currentHop); + if (getContext().routerHash().equals(peer)) { + requestSelf(); + } else { + if (_currentPeer == null) { + _currentPeer = getContext().netDb().lookupRouterInfoLocally(peer); + if (_currentPeer == null) { + _lookups++; + if (_lookups > 1) { + peerFail(0); + return; + } + getContext().netDb().lookupRouterInfo(peer, this, this, LOOKUP_TIMEOUT); + return; + } + } + requestRemote(peer); + } + } + + private void requestSelf() { + if (_config.isInbound()) { + // inbound tunnel, which means we are the first person asked, and if + // it is a zero hop tunnel, then we are also the last person asked + + long id = getContext().random().nextLong(TunnelId.MAX_ID_VALUE); + _currentConfig.setReceiveTunnelId(DataHelper.toLong(4, id)); + if (_config.getLength() > 1) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Requesting ourselves to join an inbound tunnel, receiving on " + + _currentConfig.getReceiveTunnel() + ": " + _config); + // inbound tunnel with more than just ourselves + RequestTunnelJob req = new RequestTunnelJob(getContext(), _config, _onCreated, + _onFailed, _currentHop - 1, _isFake); + if (_isFake) + req.runJob(); + else + getContext().jobQueue().addJob(req); + } else if (_onCreated != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Requesting ourselves to join a 0 hop inbound tunnel, receiving on " + + _currentConfig.getReceiveTunnel() + ": " + _config); + // 0 hop inbound tunnel + if (_onCreated != null) { + if (_isFake) + _onCreated.runJob(); + else + getContext().jobQueue().addJob(_onCreated); + } + getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0); + } + } else { + // outbound tunnel, we're the gateway and hence the last person asked + + if (_config.getLength() <= 1) { + // pick a random tunnelId which we "send" on + byte id[] = new byte[4]; + getContext().random().nextBytes(id); + _config.getConfig(0).setSendTunnelId(id); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Requesting ourselves to join an outbound tunnel, sending on " + + _config.getConfig(0).getSendTunnel() + ": " + _config); + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Requesting ourselves to join an outbound tunnel, sending on " + + _config.getConfig(1).getReceiveTunnel() + ": " + _config); + // send to whatever the first remote hop receives on + _config.getConfig(0).setSendTunnelId(_config.getConfig(1).getReceiveTunnelId()); + if (_config.getConfig(0).getSendTunnelId() == null) { + _log.error("wtf, next hop: " + _config.getConfig(1) + + " didn't give us a tunnel to send to, but they passed on to us?"); + if (_onFailed != null) { + if (_isFake) + _onFailed.runJob(); + else + getContext().jobQueue().addJob(_onFailed); + } + return; + } + + } + // we are the outbound gateway, which is the last hop which is + // asked to participate in the tunnel. as such, fire off the + // onCreated immediately + if (_onCreated != null) { + if (_isFake) + _onCreated.runJob(); + else + getContext().jobQueue().addJob(_onCreated); + getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0); + } + } + } + + private void requestRemote(Hash peer) { + HopConfig nextHop = (_config.getLength() > _currentHop + 1 ? _config.getConfig(_currentHop+1) : null); + Hash nextRouter = (nextHop != null ? _config.getPeer(_currentHop+1) : null); + TunnelId nextTunnel = (nextHop != null ? nextHop.getReceiveTunnel() : null); + + TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel(); + if (replyTunnel == null) { + if (_log.shouldLog(Log.ERROR)) + _log.error("No inbound tunnels to build tunnels with!"); + tunnelFail(); + } + Hash replyGateway = replyTunnel.getPeer(0); + + SessionKey replyKey = getContext().keyGenerator().generateSessionKey(); + SessionTag replyTag = new SessionTag(true); + + TunnelCreateMessage msg = new TunnelCreateMessage(getContext()); + msg.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + msg.setIVKey(_currentConfig.getIVKey()); + msg.setLayerKey(_currentConfig.getLayerKey()); + msg.setNonce(getContext().random().nextLong(TunnelCreateMessage.MAX_NONCE_VALUE)); + msg.setNextRouter(nextRouter); + msg.setNextTunnelId(nextTunnel); + msg.setReplyGateway(replyGateway); + msg.setReplyTunnel(replyTunnel.getReceiveTunnelId(0)); + msg.setReplyKey(replyKey); + msg.setReplyTag(replyTag); + int duration = 10*60; // (int)((_config.getExpiration() - getContext().clock().now())/1000); + msg.setDurationSeconds(duration); + if (_currentHop == 0) + msg.setIsGateway(true); + else + msg.setIsGateway(false); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("** Send remote request to " + peer.toBase64().substring(0,4) + " using nonce " + + msg.getNonce() + " with replies on " + replyTunnel); + + // now make sure we will decrypt the reply properly + HashSet sessionTags = new HashSet(1); + sessionTags.add(replyTag); + getContext().sessionKeyManager().tagsReceived(replyKey, sessionTags); + + HashSet sentTags = new HashSet(); + SessionKey sentKey = new SessionKey(); + ReplySelector selector = new ReplySelector(msg.getNonce()); + ReplyJob onReply = new RequestReplyJob(getContext(), sentKey, sentTags); + Job onTimeout = new RequestTimeoutJob(getContext(), msg.getNonce()); + Job j = new SendGarlicMessageJob(getContext(), msg, _currentPeer, selector, onReply, onTimeout, sentKey, sentTags); + getContext().jobQueue().addJob(j); + _lastSendTime = getContext().clock().now(); + } + + private void peerFail(int howBad) { + if (howBad > 0) { + switch (howBad) { + case TunnelHistory.TUNNEL_REJECT_CRIT: + getContext().statManager().addRateData("tunnel.receiveRejectionCritical", 1, 0); + break; + case TunnelHistory.TUNNEL_REJECT_BANDWIDTH: + getContext().statManager().addRateData("tunnel.receiveRejectionBandwidth", 1, 0); + break; + case TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD: + getContext().statManager().addRateData("tunnel.receiveRejectionTransient", 1, 0); + break; + case TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT: + getContext().statManager().addRateData("tunnel.receiveRejectionProbabalistic", 1, 0); + break; + default: + // ignore + } + + // penalize peer based on their bitchiness level + getContext().profileManager().tunnelRejected(_currentPeer.getIdentity().calculateHash(), + getContext().clock().now() - _lastSendTime, + howBad); + } + if (_log.shouldLog(Log.INFO)) + _log.info("Tunnel request failed w/ cause=" + howBad + " for peer " + + _currentPeer.getIdentity().calculateHash().toBase64().substring(0,4)); + tunnelFail(); + } + + private void tunnelFail() { + if (_log.shouldLog(Log.INFO)) + _log.info("tunnel building failed: " + _config + " at hop " + _currentHop); + if (_onFailed != null) + getContext().jobQueue().addJob(_onFailed); + getContext().statManager().addRateData("tunnel.buildFailure", 1, 0); + } + + private void peerSuccess() { + getContext().profileManager().tunnelJoined(_currentPeer.getIdentity().calculateHash(), + getContext().clock().now() - _lastSendTime); + if (_currentHop > 0) { + RequestTunnelJob j = new RequestTunnelJob(getContext(), _config, _onCreated, _onFailed, _currentHop - 1, _isFake); + getContext().jobQueue().addJob(j); + } else { + if (_onCreated != null) + getContext().jobQueue().addJob(_onCreated); + getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0); + } + } + + private class RequestReplyJob extends JobImpl implements ReplyJob { + private SessionKey _sentKey; + private Set _sentTags; + private TunnelCreateStatusMessage _reply; + + public RequestReplyJob(RouterContext ctx, SessionKey sentKey, Set sentTags) { + super(ctx); + _sentKey = sentKey; + _sentTags = sentTags; + } + public String getName() { return "handle tunnel request reply"; } + public void runJob() { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("reply received: " + _config + " at hop " + _currentHop + ": " + _reply.getStatus()); + + if (_sentTags.size() > 0) { + PublicKey target = _currentPeer.getIdentity().getPublicKey(); + getContext().sessionKeyManager().tagsDelivered(target, _sentKey, _sentTags); + } + + if (_reply.getStatus() == TunnelCreateStatusMessage.STATUS_SUCCESS) { + _currentConfig.setReceiveTunnelId(_reply.getReceiveTunnelId()); + if (_currentHop >= 1) + _config.getConfig(_currentHop-1).setSendTunnelId(_currentConfig.getReceiveTunnelId()); + peerSuccess(); + } else { + peerFail(_reply.getStatus()); + } + } + + public void setMessage(I2NPMessage message) { _reply = (TunnelCreateStatusMessage)message; } + } + + private class RequestTimeoutJob extends JobImpl { + private long _nonce; + public RequestTimeoutJob(RouterContext ctx, long nonce) { + super(ctx); + _nonce = nonce; + } + public String getName() { return "tunnel request timeout"; } + public void runJob() { + if (_log.shouldLog(Log.WARN)) + _log.warn("request timeout: " + _config + " at hop " + _currentHop + + " with nonce " + _nonce); + peerFail(0); + } + } + + private class ReplySelector implements MessageSelector { + private long _nonce; + private boolean _nonceFound; + private long _expiration; + + public ReplySelector(long nonce) { + _nonce = nonce; + _nonceFound = false; + _expiration = getContext().clock().now() + HOP_REQUEST_TIMEOUT; + } + public boolean continueMatching() { + return (!_nonceFound) && (getContext().clock().now() < _expiration); + } + + public long getExpiration() { return _expiration; } + public boolean isMatch(I2NPMessage message) { + if (message instanceof TunnelCreateStatusMessage) { + if (_nonce == ((TunnelCreateStatusMessage)message).getNonce()) { + _nonceFound = true; + return true; + } + } + return false; + } + + public String toString() { + StringBuffer buf = new StringBuffer(64); + buf.append("request "); + buf.append(_currentPeer.getIdentity().calculateHash().toBase64().substring(0,4)); + buf.append(" to join ").append(_config); + buf.append(" (request expired "); + buf.append(DataHelper.formatDuration(_expiration-getContext().clock().now())); + buf.append(" ago)"); + return buf.toString(); + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/SendGarlicMessageJob.java b/router/java/src/net/i2p/router/tunnel/pool/SendGarlicMessageJob.java new file mode 100644 index 000000000..04193937a --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/SendGarlicMessageJob.java @@ -0,0 +1,88 @@ +package net.i2p.router.tunnel.pool; + +import java.util.HashSet; +import java.util.Set; + +import net.i2p.data.Certificate; +import net.i2p.data.Hash; +import net.i2p.data.RouterInfo; +import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.DeliveryInstructions; +import net.i2p.data.i2np.GarlicMessage; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.MessageSelector; +import net.i2p.router.Job; +import net.i2p.router.JobImpl; +import net.i2p.router.OutNetMessage; +import net.i2p.router.ReplyJob; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelInfo; +import net.i2p.router.message.GarlicMessageBuilder; +import net.i2p.router.message.PayloadGarlicConfig; +import net.i2p.util.Log; + +/** + * Wrap the tunnel request in a garlic to the participant, and then send it out + * a tunnel. + * + */ +class SendGarlicMessageJob extends JobImpl { + private Log _log; + private I2NPMessage _payload; + private RouterInfo _target; + private MessageSelector _replySelector; + private ReplyJob _onReply; + private Job _onTimeout; + private SessionKey _sentKey; + private Set _sentTags; + + private static final int TIMEOUT = RequestTunnelJob.HOP_REQUEST_TIMEOUT; + + public SendGarlicMessageJob(RouterContext ctx, I2NPMessage payload, RouterInfo target, MessageSelector selector, ReplyJob onReply, Job onTimeout, SessionKey sentKey, Set sentTags) { + super(ctx); + _log = ctx.logManager().getLog(SendGarlicMessageJob.class); + _payload = payload; + _target = target; + _replySelector = selector; + _onReply = onReply; + _onTimeout = onTimeout; + _sentKey = sentKey; + _sentTags = sentTags; + } + public String getName() { return "build and send request garlic"; } + + public void runJob() { + DeliveryInstructions instructions = new DeliveryInstructions(); + instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); + + PayloadGarlicConfig payload = new PayloadGarlicConfig(); + payload.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + payload.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); + payload.setPayload(_payload); + payload.setRecipient(_target); + payload.setDeliveryInstructions(instructions); + payload.setRequestAck(false); + payload.setExpiration(getContext().clock().now() + RequestTunnelJob.HOP_REQUEST_TIMEOUT); + + GarlicMessage msg = GarlicMessageBuilder.buildMessage(getContext(), payload, _sentKey, _sentTags); + + // so we will look for the reply + OutNetMessage dummyMessage = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, TIMEOUT); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Scheduling timeout job (" + _onTimeout + ") to be run in " + TIMEOUT + "ms"); + + // now find an outbound tunnel and send 'er off + TunnelInfo out = getContext().tunnelManager().selectOutboundTunnel(); + if (out == null) { + if (_onTimeout != null) + getContext().jobQueue().addJob(_onTimeout); + getContext().messageRegistry().unregisterPending(dummyMessage); + } + TunnelId outId = out.getSendTunnelId(0); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Dispatching the garlic request out " + outId + " targetting " + _target.getIdentity().calculateHash().toBase64().substring(0,4)); + getContext().tunnelDispatcher().dispatchOutbound(msg, outId, _target.getIdentity().calculateHash()); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java new file mode 100644 index 000000000..184acc4c0 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java @@ -0,0 +1,206 @@ +package net.i2p.router.tunnel.pool; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import net.i2p.data.Certificate; +import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; +import net.i2p.data.i2np.DeliveryStatusMessage; +import net.i2p.data.i2np.DeliveryInstructions; +import net.i2p.data.i2np.GarlicMessage; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.JobImpl; +import net.i2p.router.MessageSelector; +import net.i2p.router.ReplyJob; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelInfo; +import net.i2p.router.message.PayloadGarlicConfig; +import net.i2p.router.message.GarlicMessageBuilder; +import net.i2p.router.tunnel.TunnelCreatorConfig; +import net.i2p.util.Log; + +class TestJob extends JobImpl { + private Log _log; + private TunnelPool _pool; + private Object _buildToken; + private PooledTunnelCreatorConfig _cfg; + + /** base to randomize the test delay on */ + private static final int TEST_DELAY = 60*1000; + + public TestJob(RouterContext ctx, PooledTunnelCreatorConfig cfg, TunnelPool pool, Object buildToken) { + super(ctx); + _log = ctx.logManager().getLog(TestJob.class); + _pool = pool; + _cfg = cfg; + _buildToken = buildToken; + getTiming().setStartAfter(getDelay() + ctx.clock().now()); + ctx.statManager().createRateStat("tunnel.testFailedTime", "How long did the failure take (max of 60s for full timeout)?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.testSuccessLength", "How long were the tunnels that passed the test?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.testSuccessTime", "How long did tunnel testing take?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.testAborted", "Tunnel test could not occur, since there weren't any tunnels to test with", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + } + public String getName() { return "Test tunnel"; } + public void runJob() { + // note: testing with exploratory tunnels always, even if the tested tunnel + // is a client tunnel (per _cfg.getDestination()) + TunnelInfo replyTunnel = null; + TunnelInfo outTunnel = null; + if (_cfg.isInbound()) { + replyTunnel = _cfg; + outTunnel = getContext().tunnelManager().selectOutboundTunnel(); + } else { + replyTunnel = getContext().tunnelManager().selectInboundTunnel(); + outTunnel = _cfg; + } + + if ( (replyTunnel == null) || (outTunnel == null) ) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Insufficient tunnels to test " + _cfg + " with: " + replyTunnel + " / " + outTunnel); + getContext().statManager().addRateData("tunnel.testAborted", _cfg.getLength(), 0); + scheduleRetest(); + } else { + int testPeriod = getTestPeriod(); + long testExpiration = getContext().clock().now() + testPeriod; + DeliveryStatusMessage m = new DeliveryStatusMessage(getContext()); + m.setArrival(getContext().clock().now()); + m.setMessageExpiration(testExpiration+2*testPeriod); + m.setMessageId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); + // keep an eye out for the message even after we fail the tunnel for another 40s + ReplySelector sel = new ReplySelector(getContext(), m.getMessageId(), testExpiration + 2*testPeriod); + OnTestReply onReply = new OnTestReply(getContext()); + OnTestTimeout onTimeout = new OnTestTimeout(getContext()); + getContext().messageRegistry().registerPending(sel, onReply, onTimeout, 3*testPeriod); + sendTest(m, outTunnel, replyTunnel); + } + } + + private void sendTest(I2NPMessage m, TunnelInfo outTunnel, TunnelInfo replyTunnel) { + if (false) { + getContext().tunnelDispatcher().dispatchOutbound(m, outTunnel.getSendTunnelId(0), + replyTunnel.getReceiveTunnelId(0), + replyTunnel.getPeer(0)); + } else { + // garlic route that DeliveryStatusMessage to ourselves so the endpoints and gateways + // can't tell its a test. to simplify this, we encrypt it with a random key and tag, + // remembering that key+tag so that we can decrypt it later. this means we can do the + // garlic encryption without any ElGamal (yay) + DeliveryInstructions instructions = new DeliveryInstructions(); + instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); + + PayloadGarlicConfig payload = new PayloadGarlicConfig(); + payload.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); + payload.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); + payload.setPayload(m); + payload.setRecipient(getContext().router().getRouterInfo()); + payload.setDeliveryInstructions(instructions); + payload.setRequestAck(false); + payload.setExpiration(m.getMessageExpiration()); + + SessionKey encryptKey = getContext().keyGenerator().generateSessionKey(); + SessionTag encryptTag = new SessionTag(true); + SessionKey sentKey = new SessionKey(); + Set sentTags = null; + GarlicMessage msg = GarlicMessageBuilder.buildMessage(getContext(), payload, sentKey, sentTags, + getContext().keyManager().getPublicKey(), + encryptKey, encryptTag); + + Set encryptTags = new HashSet(1); + encryptTags.add(encryptTag); + getContext().sessionKeyManager().tagsReceived(encryptKey, encryptTags); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Sending garlic test of " + outTunnel + " / " + replyTunnel); + getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), + replyTunnel.getReceiveTunnelId(0), + replyTunnel.getPeer(0)); + } + } + + public void testSuccessful(int ms) { + getContext().statManager().addRateData("tunnel.testSuccessLength", _cfg.getLength(), 0); + getContext().statManager().addRateData("tunnel.testSuccessTime", ms, 0); + scheduleRetest(); + } + + private void testFailed(long timeToFail) { + getContext().statManager().addRateData("tunnel.testFailedTime", timeToFail, timeToFail); + _cfg.tunnelFailed(); + if (_log.shouldLog(Log.WARN)) + _log.warn("Tunnel test failed in " + timeToFail + "ms: " + _cfg); + } + + /** randomized time we should wait before testing */ + private int getDelay() { return TEST_DELAY + getContext().random().nextInt(TEST_DELAY); } + /** how long we allow tests to run for before failing them */ + private int getTestPeriod() { return 20*1000; } + private void scheduleRetest() { + int delay = getDelay(); + if (_cfg.getExpiration() > getContext().clock().now() + delay) + requeue(delay); + } + + private class ReplySelector implements MessageSelector { + private RouterContext _context; + private long _id; + private long _expiration; + private boolean _found; + public ReplySelector(RouterContext ctx, long id, long expiration) { + _context = ctx; + _id = id; + _expiration = expiration; + _found = false; + } + + public boolean continueMatching() { return _found && _context.clock().now() < _expiration; } + public long getExpiration() { return _expiration; } + public boolean isMatch(I2NPMessage message) { + if (message instanceof DeliveryStatusMessage) { + return ((DeliveryStatusMessage)message).getMessageId() == _id; + } + return false; + } + + } + + /** + * Test successfull (w00t) + */ + private class OnTestReply extends JobImpl implements ReplyJob { + private long _successTime; + public OnTestReply(RouterContext ctx) { super(ctx); } + public String getName() { return "Tunnel test success"; } + public void runJob() { + if (_successTime < getTestPeriod()) + testSuccessful((int)_successTime); + else + testFailed(_successTime); + + } + // who cares about the details... + public void setMessage(I2NPMessage message) { + _successTime = getContext().clock().now() - ((DeliveryStatusMessage)message).getArrival(); + } + } + + /** + * Test failed (boo, hiss) + */ + private class OnTestTimeout extends JobImpl { + private long _started; + public OnTestTimeout(RouterContext ctx) { + super(ctx); + _started = ctx.clock().now(); + } + public String getName() { return "Tunnel test timeout"; } + public void runJob() { + testFailed(getContext().clock().now() - _started); + } + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java new file mode 100644 index 000000000..5f8a3cc5a --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java @@ -0,0 +1,122 @@ +package net.i2p.router.tunnel.pool; + +import java.util.ArrayList; +import java.util.List; + +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; +import net.i2p.router.RouterContext; +import net.i2p.router.JobImpl; +import net.i2p.router.tunnel.HopConfig; +import net.i2p.router.tunnel.TunnelCreatorConfig; +import net.i2p.router.tunnel.TunnelGateway; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.util.Log; + +/** + * + */ +public class TunnelBuilder { + /** + * Build a new tunnel per the pool's wishes (using the preferred length, + * peers, ordering, etc). After the tunnel is built, it is added to the + * pool as well as the dispatcher, and the necessary test and maintenance + * jobs are built. This call does not block. + * + */ + public void buildTunnel(RouterContext ctx, TunnelPool pool, Object poolToken) { + buildTunnel(ctx, pool, false, poolToken); + } + public void buildTunnel(RouterContext ctx, TunnelPool pool, boolean fake, Object poolToken) { + if (!pool.keepBuilding(poolToken)) + return; + + // this is probably overkill (ya think?) + pool.refreshSettings(); + + PooledTunnelCreatorConfig cfg = configTunnel(ctx, pool, fake); + if ( (cfg == null) && (!fake) ) { + RetryJob j = new RetryJob(ctx, pool, poolToken); + j.getTiming().setStartAfter(ctx.clock().now() + ctx.random().nextInt(30*1000)); + ctx.jobQueue().addJob(j); + return; + } + OnCreatedJob onCreated = new OnCreatedJob(ctx, pool, cfg, fake, poolToken); + RetryJob onFailed= (fake ? null : new RetryJob(ctx, pool, poolToken)); + // queue up a job to request the endpoint to join the tunnel, which then + // requeues up another job for earlier hops, etc, until it reaches the + // gateway. after the gateway is confirmed, onCreated is fired + RequestTunnelJob req = new RequestTunnelJob(ctx, cfg, onCreated, onFailed, cfg.getLength()-1, fake); + if (fake) // lets get it done inline, as we /need/ it asap + req.runJob(); + else + ctx.jobQueue().addJob(req); + } + + private PooledTunnelCreatorConfig configTunnel(RouterContext ctx, TunnelPool pool, boolean fake) { + Log log = ctx.logManager().getLog(TunnelBuilder.class); + TunnelPoolSettings settings = pool.getSettings(); + long expiration = ctx.clock().now() + settings.getDuration(); + List peers = null; + if (fake) { + peers = new ArrayList(1); + peers.add(ctx.routerHash()); + } else { + peers = pool.getSelector().selectPeers(ctx, settings); + } + if ( (peers == null) || (peers.size() <= 0) ) { + // no inbound or outbound tunnels to send the request through, and + // the pool is refusing 0 hop tunnels + if (peers == null) { + if (log.shouldLog(Log.ERROR)) + log.error("No peers to put in the new tunnel! selectPeers returned null! boo, hiss! fake=" + fake); + } else { + if (log.shouldLog(Log.ERROR)) + log.error("No peers to put in the new tunnel! selectPeers returned an empty list?! fake=" + fake); + } + return null; + } + + PooledTunnelCreatorConfig cfg = new PooledTunnelCreatorConfig(ctx, peers.size(), settings.isInbound(), settings.getDestination()); + // peers[] is ordered endpoint first, but cfg.getPeer() is ordered gateway first + for (int i = 0; i < peers.size(); i++) { + int j = peers.size() - 1 - i; + cfg.setPeer(j, (Hash)peers.get(i)); + HopConfig hop = cfg.getConfig(j); + hop.setExpiration(expiration); + hop.setIVKey(ctx.keyGenerator().generateSessionKey()); + hop.setLayerKey(ctx.keyGenerator().generateSessionKey()); + // tunnelIds will be updated during building, and as the creator, we + // don't need to worry about prev/next hop + } + cfg.setExpiration(expiration); + + Log l = ctx.logManager().getLog(TunnelBuilder.class); + if (l.shouldLog(Log.DEBUG)) + l.debug("Config contains " + peers + ": " + cfg); + return cfg; + } + + /** + * If the building fails, try, try again. + * + */ + private class RetryJob extends JobImpl { + private TunnelPool _pool; + private Object _buildToken; + public RetryJob(RouterContext ctx, TunnelPool pool, Object buildToken) { + super(ctx); + _pool = pool; + _buildToken = buildToken; + } + public String getName() { return "tunnel create failed"; } + public void runJob() { + // yikes, nothing left, lets get some backup (if we're allowed) + if ( (_pool.selectTunnel() == null) && (_pool.getSettings().getAllowZeroHop()) ) + _pool.buildFake(); + + buildTunnel(getContext(), _pool, _buildToken); + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelMessageHandlerBuilder.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelMessageHandlerBuilder.java new file mode 100644 index 000000000..c36d31353 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelMessageHandlerBuilder.java @@ -0,0 +1,48 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.data.Hash; +import net.i2p.data.RouterIdentity; +import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.router.HandlerJobBuilder; +import net.i2p.router.Job; +import net.i2p.router.JobImpl; +import net.i2p.router.RouterContext; + +/** + * + */ +public class TunnelMessageHandlerBuilder implements HandlerJobBuilder { + private RouterContext _context; + + public TunnelMessageHandlerBuilder(RouterContext ctx) { + _context = ctx; + } + + public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { + if ( (fromHash == null) && (from != null) ) + fromHash = from.calculateHash(); + return new HandleJob(_context, receivedMessage, fromHash); + } + + private class HandleJob extends JobImpl { + private I2NPMessage _msg; + private Hash _from; + public HandleJob(RouterContext ctx, I2NPMessage msg, Hash from) { + super(ctx); + _msg = msg; + _from = from; + } + public void runJob() { + if (_msg instanceof TunnelGatewayMessage) { + getContext().tunnelDispatcher().dispatch((TunnelGatewayMessage)_msg); + } else if (_msg instanceof TunnelDataMessage) { + getContext().tunnelDispatcher().dispatch((TunnelDataMessage)_msg, _from); + } + } + + public String getName() { return "Dispatch tunnel message"; } + } + +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java new file mode 100644 index 000000000..c54aab48f --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -0,0 +1,53 @@ +package net.i2p.router.tunnel.pool; + +import java.util.List; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.util.Log; + +/** + * Coordinate the selection of peers to go into a tunnel for one particular + * pool. + */ +abstract class TunnelPeerSelector { + /** + * Which peers should go into the next tunnel for the given settings? + * + * @return ordered list of Hash objects (one per peer) specifying what order + * they should appear in a tunnel (endpoint first). This includes + * the local router in the list. If there are no tunnels or peers + * to build through, and the settings reject 0 hop tunnels, this will + * return null. + */ + public abstract List selectPeers(RouterContext ctx, TunnelPoolSettings settings); + + protected int getLength(RouterContext ctx, TunnelPoolSettings settings) { + int length = settings.getLength(); + if (settings.getLengthVariance() != 0) { + int skew = settings.getLengthVariance(); + if (skew > 0) + length += ctx.random().nextInt(skew); + else { + skew = 0 - skew; + length += ctx.random().nextInt(2*skew) - skew; + } + if (length < 0) + length = 0; + } + if ( (ctx.tunnelManager().getOutboundTunnelCount() <= 0) || + (ctx.tunnelManager().getFreeTunnelCount() <= 0) ) { + Log log = ctx.logManager().getLog(TunnelPeerSelector.class); + // no tunnels to build tunnels with + if (settings.getAllowZeroHop()) { + if (log.shouldLog(Log.INFO)) + log.info("no outbound tunnels or free inbound tunnels, but we do allow zeroHop: " + settings); + return 0; + } else { + if (log.shouldLog(Log.WARN)) + log.warn("no outbound tunnels or free inbound tunnels, and we dont allow zeroHop: " + settings); + return -1; + } + } + return length; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java new file mode 100644 index 000000000..227b8baa3 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -0,0 +1,418 @@ +package net.i2p.router.tunnel.pool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +import net.i2p.data.Hash; +import net.i2p.data.Lease; +import net.i2p.data.LeaseSet; +import net.i2p.data.TunnelId; + +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.router.TunnelInfo; +import net.i2p.util.Log; + +/** + * + */ +public class TunnelPool { + private RouterContext _context; + private Log _log; + private TunnelPoolSettings _settings; + private ArrayList _tunnels; + private TunnelPeerSelector _peerSelector; + private TunnelBuilder _builder; + private TunnelPoolManager _manager; + private boolean _alive; + private long _lifetimeProcessed; + + /** + * list of pool tokens (Object) passed around during building/rebuilding/etc. + * if/when the token is removed from this list, that sequence of building/rebuilding/etc + * should cease (though others may continue). + * + */ + private List _tokens; + + public TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel, TunnelBuilder builder) { + _context = ctx; + _log = ctx.logManager().getLog(TunnelPool.class); + _manager = mgr; + _settings = settings; + _tunnels = new ArrayList(settings.getLength() + settings.getBackupQuantity()); + _peerSelector = sel; + _builder = builder; + _tokens = new ArrayList(settings.getBackupQuantity() + settings.getQuantity()); + _alive = false; + _lifetimeProcessed = 0; + refreshSettings(); + } + + public void startup() { + _alive = true; + int added = refreshBuilders(); + if (added <= 0) { + // we just reconnected and didn't require any new tunnel builders. + // however, we /do/ want a leaseSet, so build one + LeaseSet ls = null; + synchronized (_tunnels) { + if (_settings.isInbound() && (_settings.getDestination() != null) ) + ls = locked_buildNewLeaseSet(); + } + + if (ls != null) + _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + } + } + public void shutdown() { + _alive = false; + synchronized (_tokens) { _tokens.clear(); } + } + + private int refreshBuilders() { + // only start up new build tasks if we need more of 'em + int target = _settings.getQuantity() + _settings.getBackupQuantity(); + int oldTokenCount = 0; + List newTokens = null; + synchronized (_tokens) { + oldTokenCount = _tokens.size(); + while (_tokens.size() > target) + _tokens.remove(0); + if (_tokens.size() < target) { + int wanted = target - _tokens.size(); + newTokens = new ArrayList(wanted); + for (int i = 0; i < wanted; i++) { + Object token = new Object(); + newTokens.add(token); + _tokens.add(token); + } + } + } + + if (newTokens != null) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString() + ": refreshing builders, previously had " + oldTokenCount + + ", want a total of " + target + ", creating " + + newTokens.size() + " new ones."); + for (int i = 0; i < newTokens.size(); i++) { + Object token = newTokens.get(i); + if (_log.shouldLog(Log.DEBUG)) + _log.debug(toString() + ": Building a tunnel with the token " + token); + _builder.buildTunnel(_context, this, token); + } + return newTokens.size(); + } else { + return 0; + } + } + + /** do we still need this sequence of build/rebuild/etc to continue? */ + public boolean keepBuilding(Object token) { + boolean connected = true; + boolean rv = false; + int remaining = 0; + int wanted = _settings.getQuantity() + _settings.getBackupQuantity(); + if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) ) + connected = false; + synchronized (_tokens) { + if (!connected) { + // client disconnected, so stop rebuilding this series + _tokens.remove(token); + rv = false; + } else { + rv = _tokens.contains(token); + } + remaining = _tokens.size(); + } + + if (remaining <= 0) { + _manager.removeTunnels(_settings.getDestination()); + } + + if (!rv) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString() + ": keepBuilding does NOT want building to continue (want " + + wanted + ", have " + remaining); + } + return rv; + } + + void refreshSettings() { + if (_settings.getDestination() != null) { + return; // don't override client specified settings + } else { + if (_settings.isExploratory()) { + Properties props = _context.router().getConfigMap(); + if (_settings.isInbound()) + _settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props); + else + _settings.readFromProperties(TunnelPoolSettings.PREFIX_OUTBOUND_EXPLORATORY, props); + } + } + } + + /** + * Pull a random tunnel out of the pool. If there are none available but + * the pool is configured to allow 0hop tunnels, this builds a fake one + * and returns it. + * + */ + public TunnelInfo selectTunnel() { return selectTunnel(true); } + private TunnelInfo selectTunnel(boolean allowRecurseOnFail) { + synchronized (_tunnels) { + if (_tunnels.size() <= 0) { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": No tunnels to select from"); + } else { + // pick 'em randomly + Collections.shuffle(_tunnels, _context.random()); + for (int i = 0; i < _tunnels.size(); i++) { + TunnelInfo info = (TunnelInfo)_tunnels.get(i); + if (info.getExpiration() > _context.clock().now()) { + //_log.debug("Selecting tunnel: " + info + " - " + _tunnels); + return info; + } + } + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": after " + _tunnels.size() + " tries, no unexpired ones were found: " + _tunnels); + } + } + + if (_alive && _settings.getAllowZeroHop()) + buildFake(); + if (allowRecurseOnFail) + return selectTunnel(false); + else + return null; + } + + public TunnelInfo getTunnel(TunnelId gatewayId) { + synchronized (_tunnels) { + for (int i = 0; i < _tunnels.size(); i++) { + TunnelInfo info = (TunnelInfo)_tunnels.get(i); + if (_settings.isInbound()) { + if (info.getReceiveTunnelId(0).equals(gatewayId)) + return info; + } else { + if (info.getSendTunnelId(0).equals(gatewayId)) + return info; + } + } + } + return null; + } + + /** + * Return a list of tunnels in the pool + * + * @return list of TunnelInfo objects + */ + public List listTunnels() { + synchronized (_tunnels) { + return new ArrayList(_tunnels); + } + } + + public TunnelBuilder getBuilder() { return _builder; } + public TunnelPoolSettings getSettings() { return _settings; } + public void setSettings(TunnelPoolSettings settings) { + _settings = settings; + if (_settings != null) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString() + ": Settings updated on the pool: " + settings); + refreshBuilders(); // to start/stop new sequences, in case the quantities changed + } + } + public TunnelPeerSelector getSelector() { return _peerSelector; } + public boolean isAlive() { return _alive; } + public int size() { + synchronized (_tunnels) { + return _tunnels.size(); + } + } + + public void addTunnel(TunnelInfo info) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug(toString() + ": Adding tunnel " + info); + LeaseSet ls = null; + synchronized (_tunnels) { + _tunnels.add(info); + if (_settings.isInbound() && (_settings.getDestination() != null) ) + ls = locked_buildNewLeaseSet(); + } + + if (ls != null) + _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + } + + public void removeTunnel(TunnelInfo info) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug(toString() + ": Removing tunnel " + info); + int remaining = 0; + LeaseSet ls = null; + synchronized (_tunnels) { + _tunnels.remove(info); + if (_settings.isInbound() && (_settings.getDestination() != null) ) + ls = locked_buildNewLeaseSet(); + remaining = _tunnels.size(); + } + + _lifetimeProcessed += info.getProcessedMessagesCount(); + + if (_settings.isInbound() && (_settings.getDestination() != null) ) { + if (ls != null) { + _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": unable to build a new leaseSet on removal (" + remaining + + " remaining), request a new tunnel"); + if (_settings.getAllowZeroHop()) + buildFake(); + } + } + refreshBuilders(); + } + + public void tunnelFailed(PooledTunnelCreatorConfig cfg) { + if (_log.shouldLog(Log.ERROR)) + _log.error(toString() + ": Tunnel failed: " + cfg, new Exception("failure cause")); + int remaining = 0; + LeaseSet ls = null; + synchronized (_tunnels) { + _tunnels.remove(cfg); + if (_settings.isInbound() && (_settings.getDestination() != null) ) + ls = locked_buildNewLeaseSet(); + remaining = _tunnels.size(); + } + + _lifetimeProcessed += cfg.getProcessedMessagesCount(); + + if (_settings.isInbound() && (_settings.getDestination() != null) ) { + if (ls != null) { + _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": unable to build a new leaseSet on failure (" + remaining + + " remaining), request a new tunnel"); + buildFake(false); + } + } + refreshBuilders(); + } + + void refreshLeaseSet() { + if (_log.shouldLog(Log.DEBUG)) + _log.debug(toString() + ": refreshing leaseSet on tunnel expiration (but prior to grace timeout)"); + int remaining = 0; + LeaseSet ls = null; + if (_settings.isInbound() && (_settings.getDestination() != null) ) { + synchronized (_tunnels) { + ls = locked_buildNewLeaseSet(); + remaining = _tunnels.size(); + } + if (ls != null) { + _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": unable to build a new leaseSet on expire (" + remaining + + " remaining), request a new tunnel"); + if (_settings.getAllowZeroHop()) + buildFake(); + } + } + } + + void buildFake() { buildFake(true); } + void buildFake(boolean zeroHop) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString() + ": building a fake tunnel (allow zeroHop? " + zeroHop + ")"); + Object tempToken = new Object(); + synchronized (_tokens) { + _tokens.add(tempToken); + } + _builder.buildTunnel(_context, this, zeroHop, tempToken); + synchronized (_tokens) { + _tokens.remove(tempToken); + } + } + + /** + * Build a leaseSet with all of the tunnels that aren't about to expire + * + */ + private LeaseSet locked_buildNewLeaseSet() { + long expireAfter = _context.clock().now() + _settings.getRebuildPeriod(); + + LeaseSet ls = new LeaseSet(); + for (int i = 0; i < _tunnels.size(); i++) { + TunnelInfo tunnel = (TunnelInfo)_tunnels.get(i); + if (tunnel.getExpiration() <= expireAfter) + continue; // expires too soon, skip it + + TunnelId inId = tunnel.getReceiveTunnelId(0); + Hash gw = tunnel.getPeer(0); + if ( (inId == null) || (gw == null) ) { + _log.error(toString() + ": wtf, tunnel has no inbound gateway/tunnelId? " + tunnel); + continue; + } + Lease lease = new Lease(); + lease.setEndDate(new Date(tunnel.getExpiration())); + lease.setTunnelId(inId); + lease.setGateway(gw); + ls.addLease(lease); + } + + int wanted = _settings.getQuantity(); + + if (ls.getLeaseCount() < wanted) { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": Not enough leases (" + ls.getLeaseCount() + ", wanted " + wanted + ")"); + return null; + } else { + // linear search to trim down the leaseSet, removing the ones that + // will expire the earliest. cheaper than a tree for this size + while (ls.getLeaseCount() > wanted) { + int earliestIndex = -1; + long earliestExpiration = -1; + for (int i = 0; i < ls.getLeaseCount(); i++) { + Lease cur = ls.getLease(i); + if ( (earliestExpiration < 0) || (cur.getEndDate().getTime() < earliestExpiration) ) { + earliestIndex = i; + earliestExpiration = cur.getEndDate().getTime(); + } + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug(toString() + ": Dropping older lease from the leaseSet: " + earliestIndex + " out of " + ls.getLeaseCount()); + ls.removeLease(earliestIndex); + } + return ls; + } + } + + public long getLifetimeProcessed() { return _lifetimeProcessed; } + + public String toString() { + if (_settings.isExploratory()) { + if (_settings.isInbound()) + return "Inbound exploratory pool"; + else + return "Outbound exploratory pool"; + } else { + StringBuffer rv = new StringBuffer(32); + if (_settings.isInbound()) + rv.append("Inbound client pool for "); + else + rv.append("Outbound client pool for "); + if (_settings.getDestinationNickname() != null) + rv.append(_settings.getDestinationNickname()); + else + rv.append(_settings.getDestination().toBase64().substring(0,4)); + return rv.toString(); + } + + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java new file mode 100644 index 000000000..1aa5f6cec --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -0,0 +1,398 @@ +package net.i2p.router.tunnel.pool; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.data.Hash; +import net.i2p.data.TunnelId; +import net.i2p.data.i2np.TunnelCreateMessage; +import net.i2p.data.i2np.TunnelDataMessage; +import net.i2p.data.i2np.TunnelGatewayMessage; +import net.i2p.stat.RateStat; +import net.i2p.router.ClientTunnelSettings; +import net.i2p.router.HandlerJobBuilder; +import net.i2p.router.JobImpl; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelSelectionCriteria; +import net.i2p.router.TunnelInfo; +import net.i2p.router.TunnelManagerFacade; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.router.tunnel.HopConfig; + +/** + * + */ +public class TunnelPoolManager implements TunnelManagerFacade { + private RouterContext _context; + /** Hash (destination) to TunnelPool */ + private Map _clientInboundPools; + /** Hash (destination) to TunnelPool */ + private Map _clientOutboundPools; + private TunnelPool _inboundExploratory; + private TunnelPool _outboundExploratory; + + public TunnelPoolManager(RouterContext ctx) { + _context = ctx; + + HandlerJobBuilder builder = new HandleTunnelCreateMessageJob.Builder(ctx); + ctx.inNetMessagePool().registerHandlerJobBuilder(TunnelCreateMessage.MESSAGE_TYPE, builder); + //HandlerJobBuilder b = new TunnelMessageHandlerBuilder(ctx); + //ctx.inNetMessagePool().registerHandlerJobBuilder(TunnelGatewayMessage.MESSAGE_TYPE, b); + //ctx.inNetMessagePool().registerHandlerJobBuilder(TunnelDataMessage.MESSAGE_TYPE, b); + + _clientInboundPools = new HashMap(4); + _clientOutboundPools = new HashMap(4); + + ctx.statManager().createRateStat("tunnel.testSuccessTime", + "How long do successful tunnel tests take?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.participatingTunnels", + "How many tunnels are we participating in?", "Tunnels", + new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); + } + + /** pick an inbound tunnel not bound to a particular destination */ + public TunnelInfo selectInboundTunnel() { + TunnelInfo info = _inboundExploratory.selectTunnel(); + if (info == null) { + _inboundExploratory.buildFake(); + // still can be null, but probably not + info = _inboundExploratory.selectTunnel(); + } + return info; + } + + /** pick an inbound tunnel bound to the given destination */ + public TunnelInfo selectInboundTunnel(Hash destination) { + if (destination == null) return selectInboundTunnel(); + TunnelPool pool = null; + synchronized (_clientInboundPools) { + pool = (TunnelPool)_clientInboundPools.get(destination); + } + if (pool != null) { + return pool.selectTunnel(); + } + return null; + } + + /** pick an outbound tunnel not bound to a particular destination */ + public TunnelInfo selectOutboundTunnel() { + TunnelInfo info = _outboundExploratory.selectTunnel(); + if (info == null) { + _outboundExploratory.buildFake(); + // still can be null, but probably not + info = _outboundExploratory.selectTunnel(); + } + return info; + } + + /** pick an outbound tunnel bound to the given destination */ + public TunnelInfo selectOutboundTunnel(Hash destination) { + if (destination == null) return selectOutboundTunnel(); + TunnelPool pool = null; + synchronized (_clientOutboundPools) { + pool = (TunnelPool)_clientOutboundPools.get(destination); + } + if (pool != null) { + return pool.selectTunnel(); + } + return null; + } + + public TunnelInfo getTunnelInfo(TunnelId id) { + TunnelInfo info = null; + synchronized (_clientInboundPools) { + for (Iterator iter = _clientInboundPools.values().iterator(); iter.hasNext(); ) { + TunnelPool pool = (TunnelPool)iter.next(); + info = pool.getTunnel(id); + if (info != null) + return info; + } + } + return null; + } + + public int getFreeTunnelCount() { + if (_inboundExploratory == null) + return 0; + else + return _inboundExploratory.size(); + } + public int getOutboundTunnelCount() { + if (_outboundExploratory == null) + return 0; + else + return _outboundExploratory.size(); + } + public int getParticipatingCount() { return _context.tunnelDispatcher().getParticipatingCount(); } + public long getLastParticipatingExpiration() { return _context.tunnelDispatcher().getLastParticipatingExpiration(); } + + public boolean isInUse(Hash peer) { + // this lets peers that are in our tunnels expire (forcing us to refetch them) + // if the info is old + //!! no, dont. bad. + return true; + } + + public TunnelPoolSettings getInboundSettings() { return _inboundExploratory.getSettings(); } + public TunnelPoolSettings getOutboundSettings() { return _outboundExploratory.getSettings(); } + public void setInboundSettings(TunnelPoolSettings settings) { _inboundExploratory.setSettings(settings); } + public void setOutboundSettings(TunnelPoolSettings settings) { _outboundExploratory.setSettings(settings); } + public TunnelPoolSettings getInboundSettings(Hash client) { + TunnelPool pool = null; + synchronized (_clientInboundPools) { + pool = (TunnelPool)_clientInboundPools.get(client); + } + if (pool != null) + return pool.getSettings(); + else + return null; + } + public TunnelPoolSettings getOutboundSettings(Hash client) { + TunnelPool pool = null; + synchronized (_clientOutboundPools) { + pool = (TunnelPool)_clientOutboundPools.get(client); + } + if (pool != null) + return pool.getSettings(); + else + return null; + } + public void setInboundSettings(Hash client, TunnelPoolSettings settings) { + + TunnelPool pool = null; + synchronized (_clientInboundPools) { + pool = (TunnelPool)_clientInboundPools.get(client); + } + if (pool != null) + pool.setSettings(settings); + } + public void setOutboundSettings(Hash client, TunnelPoolSettings settings) { + + TunnelPool pool = null; + synchronized (_clientOutboundPools) { + pool = (TunnelPool)_clientOutboundPools.get(client); + } + if (pool != null) + pool.setSettings(settings); + } + + public void restart() { + shutdown(); + startup(); + } + + public void buildTunnels(Destination client, ClientTunnelSettings settings) { + Hash dest = client.calculateHash(); + settings.getInboundSettings().setDestination(dest); + settings.getOutboundSettings().setDestination(dest); + TunnelPool inbound = null; + TunnelPool outbound = null; + // should we share the clientPeerSelector across both inbound and outbound? + synchronized (_clientInboundPools) { + inbound = (TunnelPool)_clientInboundPools.get(dest); + if (inbound == null) { + inbound = new TunnelPool(_context, this, settings.getInboundSettings(), + new ClientPeerSelector(), new TunnelBuilder()); + _clientInboundPools.put(dest, inbound); + } else { + inbound.setSettings(settings.getInboundSettings()); + } + } + synchronized (_clientOutboundPools) { + outbound = (TunnelPool)_clientOutboundPools.get(dest); + if (outbound == null) { + outbound = new TunnelPool(_context, this, settings.getOutboundSettings(), + new ClientPeerSelector(), new TunnelBuilder()); + _clientOutboundPools.put(dest, outbound); + } else { + outbound.setSettings(settings.getOutboundSettings()); + } + } + inbound.startup(); + outbound.startup(); + } + + + public void removeTunnels(Hash destination) { + if (destination == null) return; + TunnelPool inbound = null; + TunnelPool outbound = null; + synchronized (_clientInboundPools) { + inbound = (TunnelPool)_clientInboundPools.remove(destination); + } + synchronized (_clientOutboundPools) { + outbound = (TunnelPool)_clientOutboundPools.remove(destination); + } + if (inbound != null) + inbound.shutdown(); + if (outbound != null) + outbound.shutdown(); + } + + public void startup() { + TunnelBuilder builder = new TunnelBuilder(); + ExploratoryPeerSelector selector = new ExploratoryPeerSelector(); + + TunnelPoolSettings inboundSettings = new TunnelPoolSettings(); + inboundSettings.setIsExploratory(true); + inboundSettings.setIsInbound(true); + _inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector, builder); + _inboundExploratory.startup(); + + TunnelPoolSettings outboundSettings = new TunnelPoolSettings(); + outboundSettings.setIsExploratory(true); + outboundSettings.setIsInbound(false); + _outboundExploratory = new TunnelPool(_context, this, outboundSettings, selector, builder); + _outboundExploratory.startup(); + + // try to build up longer tunnels + _context.jobQueue().addJob(new BootstrapPool(_context, _inboundExploratory)); + _context.jobQueue().addJob(new BootstrapPool(_context, _outboundExploratory)); + } + + private class BootstrapPool extends JobImpl { + private TunnelPool _pool; + public BootstrapPool(RouterContext ctx, TunnelPool pool) { + super(ctx); + _pool = pool; + getTiming().setStartAfter(ctx.clock().now() + 30*1000); + } + public String getName() { return "Bootstrap tunnel pool"; } + public void runJob() { + _pool.buildFake(false); + } + } + + public void shutdown() { + if (_inboundExploratory != null) + _inboundExploratory.shutdown(); + if (_outboundExploratory != null) + _outboundExploratory.shutdown(); + } + + public void renderStatusHTML(Writer out) throws IOException { + out.write("

Exploratory tunnels (config):

\n"); + renderPool(out, _inboundExploratory, _outboundExploratory); + + List destinations = null; + synchronized (_clientInboundPools) { + destinations = new ArrayList(_clientInboundPools.keySet()); + } + for (int i = 0; i < destinations.size(); i++) { + Hash client = (Hash)destinations.get(i); + TunnelPool in = null; + TunnelPool outPool = null; + synchronized (_clientInboundPools) { + in = (TunnelPool)_clientInboundPools.get(client); + } + synchronized (_clientOutboundPools) { + outPool = (TunnelPool)_clientOutboundPools.get(client); + } + String name = (in != null ? in.getSettings().getDestinationNickname() : null); + if ( (name == null) && (outPool != null) ) + name = outPool.getSettings().getDestinationNickname(); + if (name == null) + name = client.toBase64().substring(0,4); + out.write("

Client tunnels for " + name + " (config):

\n"); + renderPool(out, in, outPool); + } + + List participating = _context.tunnelDispatcher().listParticipatingTunnels(); + out.write("

Participating tunnels:

\n"); + out.write("" + + "\n"); + long processed = 0; + RateStat rs = _context.statManager().getRate("tunnel.participatingMessageCount"); + if (rs != null) + processed = (long)rs.getRate(10*60*1000).getLifetimeTotalValue(); + for (int i = 0; i < participating.size(); i++) { + HopConfig cfg = (HopConfig)participating.get(i); + out.write(""); + if (cfg.getReceiveTunnel() != null) + out.write(""); + else + out.write(""); + if (cfg.getReceiveFrom() != null) + out.write(""); + else + out.write(""); + if (cfg.getSendTunnel() != null) + out.write(""); + else + out.write(""); + if (cfg.getSendTo() != null) + out.write(""); + else + out.write(""); + long timeLeft = cfg.getExpiration()-_context.clock().now(); + if (timeLeft > 0) + out.write(""); + else + out.write(""); + out.write(""); + out.write("\n"); + processed += cfg.getProcessedMessagesCount(); + } + out.write("
Receive onFrom" + + "Send onToExpirationUsage
" + cfg.getReceiveTunnel().getTunnelId() +"n/a" + cfg.getReceiveFrom().toBase64().substring(0,4) +"n/a" + cfg.getSendTunnel().getTunnelId() +"n/a" + cfg.getSendTo().toBase64().substring(0,4) +"n/a" + DataHelper.formatDuration(timeLeft) + "(grace period)" + cfg.getProcessedMessagesCount() + "KB
\n"); + out.write("Lifetime bandwidth usage: " + processed + "KB
\n"); + } + + private void renderPool(Writer out, TunnelPool in, TunnelPool outPool) throws IOException { + List tunnels = null; + if (in == null) + tunnels = new ArrayList(); + else + tunnels = in.listTunnels(); + if (outPool != null) + tunnels.addAll(outPool.listTunnels()); + + long processedIn = (in != null ? in.getLifetimeProcessed() : 0); + long processedOut = (outPool != null ? outPool.getLifetimeProcessed() : 0); + + out.write("\n"); + int live = 0; + for (int i = 0; i < tunnels.size(); i++) { + TunnelInfo info = (TunnelInfo)tunnels.get(i); + long timeLeft = info.getExpiration()-_context.clock().now(); + if (timeLeft <= 0) + continue; // don't display tunnels in their grace period + live++; + if (info.isInbound()) + out.write(""); + else + out.write(""); + out.write("\n"); + out.write("\n"); + for (int j = 0; j < info.getLength(); j++) { + Hash peer = info.getPeer(j); + TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j)); + if (_context.routerHash().equals(peer)) + out.write(""); + else + out.write(""); + } + out.write("\n"); + + if (info.isInbound()) + processedIn += info.getProcessedMessagesCount(); + else + processedOut += info.getProcessedMessagesCount(); + } + if (live <= 0) + out.write("\n"); + out.write("
DirectionExpirationUsageHops (gateway first)
inbound
outbound" + DataHelper.formatDuration(timeLeft) + "" + info.getProcessedMessagesCount() + "KB" + peer.toBase64().substring(0,4) + (id == null ? "" : ":" + id) + "" + peer.toBase64().substring(0,4) + (id == null ? "" : ":" + id) + "
No tunnels, waiting for the grace period to end
\n"); + out.write("Lifetime bandwidth usage: " + processedIn + "KB in, " + processedOut + "KB out
"); + } +} diff --git a/router/java/src/net/i2p/router/tunnelmanager/ClientLeaseSetManagerJob.java b/router/java/src/net/i2p/router/tunnelmanager/ClientLeaseSetManagerJob.java deleted file mode 100644 index 0a871f74e..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/ClientLeaseSetManagerJob.java +++ /dev/null @@ -1,233 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.TreeMap; - -import net.i2p.data.Lease; -import net.i2p.data.LeaseSet; -import net.i2p.data.RouterInfo; -import net.i2p.data.TunnelId; -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Manage the process of requesting a lease set as necessary for a client based - * on the contents of the tunnel pool. Request a new lease set when: - * - # safe inbound tunnels meets or exceeds the client's minimum and - * - no current leaseSet exists - * or - * - one of the tunnels in the current leaseSet has expired - * or - * - it has been N minutes since the current leaseSet was created - * (where N is based off the clientSettings.getInboundDuration) - * - */ -class ClientLeaseSetManagerJob extends JobImpl { - private Log _log; - private ClientTunnelPool _pool; - private LeaseSet _currentLeaseSet; - private long _lastCreated; - private boolean _forceRequestLease; - - /** - * Recheck the set every 15 seconds - * todo: this should probably be updated dynamically based on expiration dates / etc. - * - */ - private final static long RECHECK_DELAY = 15*1000; - /** - * How long to wait for the client to approve or reject a leaseSet - */ - private final static long REQUEST_LEASE_TIMEOUT = 30*1000; - - public ClientLeaseSetManagerJob(RouterContext context, ClientTunnelPool pool) { - super(context); - _log = context.logManager().getLog(ClientLeaseSetManagerJob.class); - _pool = pool; - _currentLeaseSet = null; - _lastCreated = -1; - context.statManager().createRateStat("client.leaseSetExpired", "How long ago did our leaseSet expire?", "ClientMessages", new long[] { 60*60*1000l, 24*60*60*1000l }); - - } - - public void forceRequestLease() { - _currentLeaseSet = null; - _forceRequestLease = true; - } - - public String getName() { return "Manage Client Lease Set"; } - public void runJob() { - if ((!_forceRequestLease) && (_pool.isStopped()) ) { - if ( (_pool.getInactiveInboundTunnelIds().size() <= 0) && - (_pool.getInboundTunnelIds().size() <= 0) ) { - if (_log.shouldLog(Log.INFO)) - _log.info("No more tunnels and the client has stopped, so no need to manage the leaseSet any more for " - + _pool.getDestination().calculateHash()); - return; - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Client " + _pool.getDestination().calculateHash() - + " is stopped, but they still have some tunnels, so don't stop maintaining the leaseSet"); - requeue(RECHECK_DELAY); - return; - } - } - - int available = _pool.getSafePoolSize(); - if (available >= _pool.getClientSettings().getNumInboundTunnels()) { - if (_forceRequestLease) { - if (_log.shouldLog(Log.INFO)) - _log.info("Forced to request a new lease (reconnected client perhaps?)"); - _forceRequestLease = false; - requestNewLeaseSet(); - } else if (_currentLeaseSet == null) { - if (_log.shouldLog(Log.INFO)) - _log.info("No leaseSet is known - request a new one"); - requestNewLeaseSet(); - } else if (tunnelsChanged()) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnels changed from the old leaseSet - request a new one: [pool = " - + _pool.getInboundTunnelIds() + " old leaseSet: " + _currentLeaseSet); - requestNewLeaseSet(); - } else if (getContext().clock().now() > _lastCreated + _pool.getClientSettings().getInboundDuration()) { - if (_log.shouldLog(Log.INFO)) - _log.info("We've exceeded the client's requested duration (limit = " - + new Date(_lastCreated + _pool.getClientSettings().getInboundDuration()) - + " / " + _pool.getClientSettings().getInboundDuration() - + ") - request a new leaseSet"); - requestNewLeaseSet(); - } else { - _log.debug("The current LeaseSet is fine, noop"); - } - } else { - _log.warn("Insufficient safe inbound tunnels exist for the client (" + available - + " available, " + _pool.getClientSettings().getNumInboundTunnels() - + " required) - no leaseSet requested"); - if (_currentLeaseSet != null) { - long howOld = getContext().clock().now() - _currentLeaseSet.getEarliestLeaseDate(); - if (howOld > 0) { - // expired - getContext().statManager().addRateData("client.leaseSetExpired", howOld, 0); - } - } - } - requeue(RECHECK_DELAY); - } - /** - * Determine if the tunnels in the current leaseSet are the same as the - * currently available free tunnels - * - * @return true if the tunnels are /not/ the same, else false if they are the same - */ - private boolean tunnelsChanged() { - long furthestInFuture = 0; - Set currentIds = new HashSet(_currentLeaseSet.getLeaseCount()); - for (int i = 0; i < _currentLeaseSet.getLeaseCount(); i++) { - Lease lease = (Lease)_currentLeaseSet.getLease(i); - currentIds.add(lease.getTunnelId()); - if (lease.getEndDate().getTime() > furthestInFuture) - furthestInFuture = lease.getEndDate().getTime(); - } - Set avail = _pool.getInboundTunnelIds(); - avail.removeAll(currentIds); - // check to see if newer ones exist in the available pool - for (Iterator iter = avail.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getInboundTunnel(id); - // we need to check this in case the tunnel was deleted since 6 lines up - if ( (id != null) && (info != null) && (info.getSettings() != null) ) { - // if something available but not in the currently published lease will be - // around longer than any of the published leases, we want that tunnel to - // be added to our published lease - if (info.getSettings().getExpiration() > furthestInFuture) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel " + id.getTunnelId() + " expires " - + (info.getSettings().getExpiration()-furthestInFuture) - + "ms after any of the existing ones do"); - return true; - } - } - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("None of the available tunnels expire after the existing lease set's tunnels"); - return false; - } - - /** - * Request a new leaseSet based off the currently available safe tunnels - */ - private void requestNewLeaseSet() { - LeaseSet proposed = buildNewLeaseSet(); - getContext().clientManager().requestLeaseSet(_pool.getDestination(), proposed, - REQUEST_LEASE_TIMEOUT, new LeaseSetCreatedJob(getContext()), - null); - } - - /** - * Create a new proposed leaseSet with all inbound tunnels - */ - private LeaseSet buildNewLeaseSet() { - LeaseSet ls = new LeaseSet(); - TreeMap tunnels = new TreeMap(); - long now = getContext().clock().now(); - for (Iterator iter = _pool.getInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getInboundTunnel(id); - - if (!info.getIsReady()) - continue; - long exp = info.getSettings().getExpiration(); - if (now + RECHECK_DELAY + REQUEST_LEASE_TIMEOUT > exp) - continue; - RouterInfo ri = getContext().netDb().lookupRouterInfoLocally(info.getThisHop()); - if (ri == null) - continue; - - Lease lease = new Lease(); - lease.setEndDate(new Date(exp)); - lease.setRouterIdentity(ri.getIdentity()); - lease.setTunnelId(id); - tunnels.put(new Long(0-exp), lease); - } - - // now pick the N tunnels with the longest time remaining (n = # tunnels the client requested) - // place tunnels.size() - N into the inactive pool - int selected = 0; - int wanted = _pool.getClientSettings().getNumInboundTunnels(); - for (Iterator iter = tunnels.values().iterator(); iter.hasNext(); ) { - Lease lease = (Lease)iter.next(); - if (selected < wanted) { - ls.addLease(lease); - selected++; - } else { - _pool.moveToInactive(lease.getTunnelId()); - } - } - ls.setDestination(_pool.getDestination()); - return ls; - } - - private class LeaseSetCreatedJob extends JobImpl { - public LeaseSetCreatedJob(RouterContext enclosingContext) { - super(enclosingContext); - } - public String getName() { return "LeaseSet created"; } - public void runJob() { - RouterContext ctx = ClientLeaseSetManagerJob.this.getContext(); - LeaseSet ls = ctx.netDb().lookupLeaseSetLocally(_pool.getDestination().calculateHash()); - if (ls != null) { - _log.info("New leaseSet completely created"); - _lastCreated = ctx.clock().now(); - _currentLeaseSet = ls; - } else { - _log.error("New lease set created, but not found locally? wtf?! client=" - + _pool.getDestination().calculateHash().toBase64()); - } - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPool.java b/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPool.java deleted file mode 100644 index 784b19db6..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPool.java +++ /dev/null @@ -1,210 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import net.i2p.data.Destination; -import net.i2p.data.TunnelId; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -class ClientTunnelPool { - private Log _log; - private Destination _dest; - private ClientTunnelSettings _settings; - private TunnelPool _pool; - private Map _inboundTunnels; // TunnelId --> TunnelInfo for inbound tunnels - private Map _inactiveInboundTunnels; // TunnelId --> TunnelInfo for inbound tunnels no longer in use (but not expired) - private ClientTunnelPoolManagerJob _mgrJob; - private ClientLeaseSetManagerJob _leaseMgrJob; - private ClientTunnelPoolExpirationJob _tunnelExpirationJob; - private boolean _isStopped; - private static int __poolId; - private int _poolId; - private RouterContext _context; - - public ClientTunnelPool(RouterContext ctx, Destination dest, ClientTunnelSettings settings, - TunnelPool pool) { - _context = ctx; - _log = ctx.logManager().getLog(ClientTunnelPool.class); - _dest = dest; - _settings = settings; - _pool = pool; - _inboundTunnels = new HashMap(); - _inactiveInboundTunnels = new HashMap(); - _isStopped = true; - _poolId = ++__poolId; - } - - public void startPool() { - //if (!_isStopped) { - // if (_log.shouldLog(Log.ERROR)) - // _log.error("Pool " + _poolId +": Not starting the pool /again/ (its already running)"); - // return; - //} else { - if (_log.shouldLog(Log.INFO)) - _log.info("Pool " + _poolId +": Starting up the pool "); - //} - _isStopped = false; - - if (_mgrJob == null) { - _mgrJob = new ClientTunnelPoolManagerJob(_context, _pool, this); - _context.jobQueue().addJob(_mgrJob); - } else { - _mgrJob.getTiming().setStartAfter(_context.clock().now()); - _context.jobQueue().addJob(_mgrJob); - } - - if (_leaseMgrJob == null) { - _leaseMgrJob = new ClientLeaseSetManagerJob(_context, this); - _context.jobQueue().addJob(_leaseMgrJob); - } else { - // we just restarted, so make sure we ask for a new leaseSet ASAP - if (_log.shouldLog(Log.DEBUG)) - _log.debug("restarting the client pool and requesting a new leaseSet"); - _leaseMgrJob.forceRequestLease(); - _leaseMgrJob.getTiming().setStartAfter(_context.clock().now()); - _context.jobQueue().addJob(_leaseMgrJob); - } - - if (_tunnelExpirationJob == null) { - _tunnelExpirationJob = new ClientTunnelPoolExpirationJob(_context, this, _pool); - _context.jobQueue().addJob(_tunnelExpirationJob); - } else { - _tunnelExpirationJob.getTiming().setStartAfter(_context.clock().now()); - _context.jobQueue().addJob(_tunnelExpirationJob); - } - } - public void stopPool() { _isStopped = true; } - public boolean isStopped() { return _isStopped; } - - public void setClientSettings(ClientTunnelSettings settings) { - _settings = settings; - if (settings != null) { - _log.info("Client settings specified - the client may have reconnected, so restart the pool"); - startPool(); - } - } - public ClientTunnelSettings getClientSettings() { return _settings; } - - public Destination getDestination() { return _dest; } - - public void moveToInactive(TunnelId id) { - TunnelInfo info = removeInboundTunnel(id); - if (info != null) { - _context.messageHistory().tunnelJoined("inactive inbound", info); - synchronized (_inactiveInboundTunnels) { - _inactiveInboundTunnels.put(id, info); - } - _log.info("Marking tunnel " + id + " as inactive"); - } - } - - void setActiveTunnels(Set activeTunnels) { - for (Iterator iter = activeTunnels.iterator(); iter.hasNext(); ) { - TunnelInfo info = (TunnelInfo)iter.next(); - _context.messageHistory().tunnelJoined("active inbound", info); - synchronized (_inboundTunnels) { - _inboundTunnels.put(info.getTunnelId(), info); - } - } - } - void setInactiveTunnels(Set inactiveTunnels) { - for (Iterator iter = inactiveTunnels.iterator(); iter.hasNext(); ) { - TunnelInfo info = (TunnelInfo)iter.next(); - _context.messageHistory().tunnelJoined("inactive inbound", info); - synchronized (_inactiveInboundTunnels) { - _inactiveInboundTunnels.put(info.getTunnelId(), info); - } - } - } - - /** - * Go through all of the client's inbound tunnels and determine how many are safe for - * use over the next period, either as part of a LeaseSet or as the target for a reply / etc. - * - */ - public int getSafePoolSize() { - return getSafePoolSize(2*60*1000); - } - /** - * Get the safe # pools at some point in the future - * - * @param futureMs number of milliseconds in the future that we want to check safety for - */ - public int getSafePoolSize(long futureMs) { - int numSafe = 0; - long expireAfter = _context.clock().now() + futureMs; - for (Iterator iter = getInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = getInboundTunnel(id); - if ( (info != null) && (info.getIsReady()) && (info.getSettings().getExpiration() > expireAfter) ) - numSafe++; - } - return numSafe; - } - - /** - * Set of tunnelIds of inbound tunnels - * - */ - public Set getInboundTunnelIds() { - synchronized (_inboundTunnels) { - return new HashSet(_inboundTunnels.keySet()); - } - } - public boolean isInboundTunnel(TunnelId id) { - synchronized (_inboundTunnels) { - return _inboundTunnels.containsKey(id); - } - } - public TunnelInfo getInboundTunnel(TunnelId id) { - synchronized (_inboundTunnels) { - return (TunnelInfo)_inboundTunnels.get(id); - } - } - public void addInboundTunnel(TunnelInfo tunnel) { - _context.messageHistory().tunnelJoined("active inbound", tunnel); - synchronized (_inboundTunnels) { - _inboundTunnels.put(tunnel.getTunnelId(), tunnel); - } - } - public TunnelInfo removeInboundTunnel(TunnelId id) { - TunnelInfo info = null; - synchronized (_inboundTunnels) { - info = (TunnelInfo)_inboundTunnels.remove(id); - } - _pool.addTunnelStats(id, info); - return info; - } - - public Set getInactiveInboundTunnelIds() { - synchronized (_inactiveInboundTunnels) { - return new HashSet(_inactiveInboundTunnels.keySet()); - } - } - public boolean isInactiveInboundTunnel(TunnelId id) { - synchronized (_inactiveInboundTunnels) { - return _inactiveInboundTunnels.containsKey(id); - } - } - public TunnelInfo getInactiveInboundTunnel(TunnelId id) { - synchronized (_inactiveInboundTunnels) { - return (TunnelInfo)_inactiveInboundTunnels.get(id); - } - } - public TunnelInfo removeInactiveInboundTunnel(TunnelId id) { - TunnelInfo info = null; - synchronized (_inactiveInboundTunnels) { - info = (TunnelInfo)_inactiveInboundTunnels.remove(id); - } - _pool.addTunnelStats(id, info); - return info; - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolExpirationJob.java b/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolExpirationJob.java deleted file mode 100644 index f7e997f7b..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolExpirationJob.java +++ /dev/null @@ -1,116 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.Date; -import java.util.Iterator; - -import net.i2p.data.TunnelId; -import net.i2p.router.JobImpl; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Periodically go through all of the tunnels assigned to this client and mark - * them as no longer ready and/or drop them (as appropriate) - * - */ -class ClientTunnelPoolExpirationJob extends JobImpl { - private Log _log; - private ClientTunnelPool _pool; - private TunnelPool _tunnelPool; - - /** expire tunnels as necessary every 30 seconds */ - private final static long EXPIRE_POOL_DELAY = 30*1000; - - /** - * don't hard expire a tunnel until its later than expiration + buffer - */ - private final static long EXPIRE_BUFFER = 30*1000; - - public ClientTunnelPoolExpirationJob(RouterContext context, ClientTunnelPool pool, TunnelPool tunnelPool) { - super(context); - _log = context.logManager().getLog(ClientTunnelPoolExpirationJob.class); - _pool = pool; - _tunnelPool = tunnelPool; - getTiming().setStartAfter(getContext().clock().now() + EXPIRE_POOL_DELAY); - } - public String getName() { return "Expire Pooled Client Tunnels"; } - public void runJob() { - if (_pool.isStopped()) { - if ( (_pool.getInactiveInboundTunnelIds().size() <= 0) && - (_pool.getInboundTunnelIds().size() <= 0) ) { - // this may get called twice - once here, and once by the ClientTunnelPoolManagerJob - // but its safe to do, and passing around messages would be overkill. - _tunnelPool.removeClientPool(_pool.getDestination()); - if (_log.shouldLog(Log.INFO)) - _log.info("No more tunnels to expire in the client tunnel pool for the stopped client " + _pool.getDestination().calculateHash()); - return; - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Client " + _pool.getDestination().calculateHash() - + " is stopped, but they still have some tunnels, so don't stop expiring"); - } - } - - expireInactiveTunnels(); - expireActiveTunnels(); - - requeue(EXPIRE_POOL_DELAY); - } - - /** - * Drop all inactive tunnels that are expired or are close enough to - * being expired that using them would suck. - * - */ - public void expireInactiveTunnels() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - 2*Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getInactiveInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getInactiveInboundTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring inactive tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) + "]"); - _pool.removeInactiveInboundTunnel(id); - } else if (info.getSettings().getExpiration() < now) { - _log.info("It is past the expiration for inactive tunnel " + id - + " but not yet the buffer, mark it as no longer ready"); - info.setIsReady(false); - } - } - } - } - - /** - * Drop all active tunnels that are expired or are close enough to - * being expired that using them would suck. - * - */ - public void expireActiveTunnels() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - 2*Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getInboundTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring active tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) + "]"); - _pool.removeInboundTunnel(id); - } else if (info.getSettings().getExpiration() < now) { - if (_log.shouldLog(Log.INFO)) - _log.info("It is past the expiration for active tunnel " + id - + " but not yet the buffer, mark it as no longer ready"); - info.setIsReady(false); - } - } - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolManagerJob.java b/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolManagerJob.java deleted file mode 100644 index cf2066226..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/ClientTunnelPoolManagerJob.java +++ /dev/null @@ -1,265 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.TreeMap; - -import net.i2p.data.TunnelId; -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * refill the client tunnel pool as necessary, either from the TunnelPool's free - * inbound set or by requesting custom tunnels via the RequestInboundTunnelJob. - * - */ -class ClientTunnelPoolManagerJob extends JobImpl { - private Log _log; - private ClientTunnelPool _clientPool; - private TunnelPool _tunnelPool; - private TunnelBuilder _tunnelBuilder; - - /** check the pool every 30 seconds to make sure it has enough tunnels */ - private final static long POOL_CHECK_DELAY = 30*1000; - - public ClientTunnelPoolManagerJob(RouterContext ctx, TunnelPool pool, ClientTunnelPool clientPool) { - super(ctx); - _log = ctx.logManager().getLog(ClientTunnelPoolManagerJob.class); - _clientPool = clientPool; - _tunnelPool = pool; - _tunnelBuilder = new TunnelBuilder(ctx); - } - public String getName() { return "Manage Client Tunnel Pool"; } - public void runJob() { - try { - if (_clientPool.isStopped()) { - handleStopped(); - return; - } - - if (!getContext().clientManager().isLocal(_clientPool.getDestination())) { - if (_log.shouldLog(Log.INFO)) - _log.info("Client " + _clientPool.getDestination().calculateHash() - + " is no longer connected, stop the pool"); - _clientPool.stopPool(); - requeue(POOL_CHECK_DELAY); - return; - } - int requestedPoolSize = _clientPool.getClientSettings().getNumInboundTunnels(); - int safePoolSize = _clientPool.getSafePoolSize(2*60*1000 + POOL_CHECK_DELAY); - if (safePoolSize < requestedPoolSize) { - requestMoreTunnels(requestedPoolSize-safePoolSize); - } - } catch (Exception t) { - _log.log(Log.CRIT, "Unhandled exception managing the client tunnel pool", t); - } - requeue(POOL_CHECK_DELAY); - } - - /** - * The pool is stopped, so lets see if we should keep doing anything - */ - private void handleStopped() { - if (getContext().clientManager().isLocal(_clientPool.getDestination())) { - // it was stopped, but they've reconnected, so boot 'er up again - if (_log.shouldLog(Log.INFO)) - _log.info("Client " + _clientPool.getDestination().calculateHash().toBase64() - + " was stopped, but reconnected! restarting it"); - _clientPool.startPool(); - // we return directly, since it'll queue up jobs again, etc - } else { - // not currently connected - check to see whether all of the tunnels have expired - if ((_clientPool.getInactiveInboundTunnelIds().size() > 0) || - (_clientPool.getInboundTunnelIds().size() > 0) ) { - // there are tunnels left, requeue until later (in case the client reconnects - if (_log.shouldLog(Log.DEBUG)) - _log.debug("There are tunnels left, though the client is still disconnected: " - + _clientPool.getDestination().calculateHash()); - requeue(POOL_CHECK_DELAY); - } else { - // no tunnels left and the client is still disconnected, screw the pool - if (_log.shouldLog(Log.INFO)) - _log.info("No more tunnels left and the client has disconnected: " - + _clientPool.getDestination().calculateHash()); - _tunnelPool.removeClientPool(_clientPool.getDestination()); - } - } - } - - /** - * Request num more inbound tunnels - either from the free pool or custom built ones - * - */ - private void requestMoreTunnels(int numTunnels) { - if (_clientPool.getClientSettings().getDepthInbound() < 1) { - // the client wants 0-hop tunnels, so don't waste longer tunnels on them - if (_log.shouldLog(Log.DEBUG)) - _log.debug("0 hop tunnels wanted - create custom ones"); - requestCustomTunnels(numTunnels); - return; - } - - int allocated = allocateExisting(numTunnels); - - if (allocated < numTunnels) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("insufficient tunnels available (wanted: " + numTunnels - + ", allocated: " + allocated + ", requesting custom ones"); - requestCustomTunnels(numTunnels - allocated); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sufficient tunnels exist in the client pool for " - + _clientPool.getDestination().calculateHash() + " w3wt"); - // done! w00t - } - } - - /** - * Grab any existing tunnels that can be allocated to the client and do so - * - * @return number of tunnels allocated - */ - private int allocateExisting(int numTunnels) { - int allocated = 0; - Iterator iter = selectGoodEnough(numTunnels).iterator(); - // good enough tunnels, ordered with the longest from now duration first - while (iter.hasNext() && allocated < numTunnels) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _tunnelPool.getTunnelInfo(id); - if (info.getLength() < _clientPool.getClientSettings().getDepthInbound()) { - // this aint good 'nuff... - continue; - } - boolean ok = _tunnelPool.allocateTunnel(id, _clientPool); - if (ok) { - allocated++; - } - } - return allocated; - } - - /** - * Find up to the specified number of existing free inbound tunnels that meet - * the client's conditions. - * - * @return list of TunnelId values of qualified tunnels - */ - private List selectGoodEnough(int numTunnels) { - TreeMap rv = new TreeMap(); - int maxLength = _tunnelPool.getLongestTunnelLength(); - Iterator iter = _tunnelPool.getFreeTunnels().iterator(); - while(iter.hasNext() && rv.size() < numTunnels) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _tunnelPool.getFreeTunnel(id); - if (info != null) { - if (isGoodEnough(info, maxLength)) { - rv.put(new Long(0 - info.getSettings().getExpiration()), id); - } - } - } - return new ArrayList(rv.values()); - } - - /** - * Determine if the tunnel will meet the client's requirements. - * - */ - private boolean isGoodEnough(TunnelInfo info, int max) { - if (!info.getIsReady()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Refusing tunnel " + info.getTunnelId() + " because it isn't ready"); - return false; - } - - // (furthest in the future) - (rebuild buffer time) - long expireAfter = getContext().clock().now() + _tunnelPool.getPoolSettings().getInboundDuration() - - POOL_CHECK_DELAY - _tunnelPool.getTunnelCreationTimeout()*2; - if (info.getSettings().getExpiration() <= expireAfter) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Refusing tunnel " + info.getTunnelId() + " because it is going to expire soon (" - + new Date(info.getSettings().getExpiration()) - + ", before " + new Date(expireAfter) + ")"); - return false; - } - - int length = info.getLength() - 1; // -1 because .getLength() includes us - if (_clientPool.getClientSettings().getEnforceStrictMinimumLength()) { - if (length < _clientPool.getClientSettings().getDepthInbound()) { - // we will require at least the client's length, but they dont meet it - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Strictly refusing tunnel " + info.getTunnelId() - + " because it is too short (length = " + length - + ", wanted = " + _clientPool.getClientSettings().getDepthInbound() - + ")"); - return false; - } else { - // its long enough. w00t - } - } else { - if (length < _clientPool.getClientSettings().getDepthInbound() && (length < max)) { - // while we will still strive to meet the client's needs, we will be satisfied with - // the best we have on hand (which may be less that their requested length) - // this tunnel however meets neither criteria - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Loosely refusing tunnel " + info.getTunnelId() - + " because it is too short (length = " + length - + ", wanted = " + _clientPool.getClientSettings().getDepthInbound() - + ")"); - return false; - } else { - // either its long enough, or its the longest we have. - // if we want to be strict, specify tunnels.enforceStrictMinimumLength either - // in the JVM environment via - // -Dtunnels.enforceStrictMinimumLength=true or in the router.config - // (tunnels.enforceStrictMinimumLength=true) - } - } - - if (info.getDestination() != null) { - if (!_clientPool.getDestination().equals(info.getDestination())) { - if (_log.shouldLog(Log.INFO)) - _log.info("Refusing tunnel " + info.getTunnelId() - + " because it was requested specifically for another destination [" - + info.getDestination().calculateHash() + "]"); - return false; - } - } - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Accepting tunnel for length=" + _clientPool.getClientSettings().getDepthInbound() + - " and dest=" + _clientPool.getDestination().calculateHash().toBase64().substring(0,6) - + " for " + info.getTunnelId()); - return true; - } - - /** - * Request numTunnels more tunnels (the free pool doesnt have enough satisfactory ones). - * This fires off a series of RequestCustomTunnelJobs - */ - private void requestCustomTunnels(int numTunnels) { - for (int i = 0; i < numTunnels; i++) { - getContext().jobQueue().addJob(new RequestCustomTunnelJob(getContext())); - } - } - - /** - * Request a new tunnel specifically to the client's requirements, marked as for them so other - * ClientTunnelPool's wont take it - * - */ - private class RequestCustomTunnelJob extends JobImpl { - public RequestCustomTunnelJob(RouterContext enclosingContext) { - super(enclosingContext); - } - public String getName() { return "Request Custom Client Tunnel"; } - public void runJob() { - TunnelInfo tunnelGateway = _tunnelBuilder.configureInboundTunnel(_clientPool.getDestination(), _clientPool.getClientSettings()); - RequestTunnelJob reqJob = new RequestTunnelJob(RequestCustomTunnelJob.this.getContext(), _tunnelPool, tunnelGateway, true, _tunnelPool.getTunnelCreationTimeout()); - RequestCustomTunnelJob.this.getContext().jobQueue().addJob(reqJob); - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/HandleTunnelCreateMessageJob.java b/router/java/src/net/i2p/router/tunnelmanager/HandleTunnelCreateMessageJob.java deleted file mode 100644 index fa9deb60e..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/HandleTunnelCreateMessageJob.java +++ /dev/null @@ -1,274 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.Date; -import java.util.List; - -import net.i2p.data.Certificate; -import net.i2p.data.Hash; -import net.i2p.data.RouterIdentity; -import net.i2p.data.RouterInfo; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.GarlicMessage; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelCreateMessage; -import net.i2p.data.i2np.TunnelCreateStatusMessage; -import net.i2p.router.Job; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageSelector; -import net.i2p.router.ReplyJob; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.router.TunnelSettings; -import net.i2p.router.message.BuildTestMessageJob; -import net.i2p.router.message.GarlicConfig; -import net.i2p.router.message.GarlicMessageBuilder; -import net.i2p.router.message.PayloadGarlicConfig; -import net.i2p.router.message.SendTunnelMessageJob; -import net.i2p.util.Log; - -public class HandleTunnelCreateMessageJob extends JobImpl { - private Log _log; - private TunnelCreateMessage _message; - private RouterIdentity _from; - private Hash _fromHash; - - private final static long TIMEOUT = 30*1000; // 30 secs to contact a peer that will be our next hop - private final static int PRIORITY = 123; - - HandleTunnelCreateMessageJob(RouterContext ctx, TunnelCreateMessage receivedMessage, - RouterIdentity from, Hash fromHash) { - super(ctx); - _log = ctx.logManager().getLog(HandleTunnelCreateMessageJob.class); - ctx.statManager().createRateStat("tunnel.rejectOverloaded", "How many tunnels did we deny due to throttling?", "Tunnels", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _message = receivedMessage; - _from = from; - _fromHash = fromHash; - } - - public void runJob() { - if (_log.shouldLog(Log.DEBUG)) _log.debug("Handling tunnel create"); - if (isOverloaded()) { - sendReply(false); - return; - } - TunnelInfo info = new TunnelInfo(getContext()); - info.setConfigurationKey(_message.getConfigurationKey()); - info.setEncryptionKey(_message.getTunnelKey()); - info.setNextHop(_message.getNextRouter()); - info.setNextHopId(_message.getNextTunnelId()); - - TunnelSettings settings = new TunnelSettings(getContext()); - settings.setBytesPerMinuteAverage(_message.getMaxAvgBytesPerMin()); - settings.setBytesPerMinutePeak(_message.getMaxPeakBytesPerMin()); - settings.setMessagesPerMinuteAverage(_message.getMaxAvgMessagesPerMin()); - settings.setMessagesPerMinutePeak(_message.getMaxPeakMessagesPerMin()); - settings.setExpiration(_message.getTunnelDurationSeconds()*1000+getContext().clock().now()); - settings.setIncludeDummy(_message.getIncludeDummyTraffic()); - settings.setReorder(_message.getReorderMessages()); - info.setSettings(settings); - - info.setSigningKey(_message.getVerificationPrivateKey()); - info.setThisHop(getContext().routerHash()); - info.setTunnelId(_message.getTunnelId()); - info.setVerificationKey(_message.getVerificationPublicKey()); - - info.getTunnelId().setType(TunnelId.TYPE_PARTICIPANT); - - if (_message.getNextRouter() == null) { - if (_log.shouldLog(Log.DEBUG)) _log.debug("We're the endpoint, don't test the \"next\" peer [duh]"); - boolean ok = getContext().tunnelManager().joinTunnel(info); - sendReply(ok); - } else { - getContext().netDb().lookupRouterInfo(info.getNextHop(), new TestJob(getContext(), info), new JoinJob(getContext(), info, false), TIMEOUT); - } - } - - private boolean isOverloaded() { - boolean shouldAccept = getContext().throttle().acceptTunnelRequest(_message); - if (!shouldAccept) { - getContext().statManager().addRateData("tunnel.rejectOverloaded", 1, 1); - if (_log.shouldLog(Log.INFO)) - _log.info("Refusing tunnel request due to overload"); - } - return !shouldAccept; - } - - private class TestJob extends JobImpl { - private TunnelInfo _target; - public TestJob(RouterContext enclosingContext, TunnelInfo target) { - super(enclosingContext); - _target = target; - } - - public String getName() { return "Run a test for peer reachability"; } - public void runJob() { - RouterInfo info = TestJob.this.getContext().netDb().lookupRouterInfoLocally(_target.getNextHop()); - if (info == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error - unable to look up peer " + _target.toBase64() + ", even though we were queued up via onSuccess??"); - return; - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Lookup successful for tested peer " + _target.toBase64() + ", now continue with the test"); - Hash peer = TestJob.this.getContext().routerHash(); - JoinJob success = new JoinJob(getContext(), _target, true); - JoinJob failure = new JoinJob(getContext(), _target, false); - BuildTestMessageJob test = new BuildTestMessageJob(TestJob.this.getContext(), info, peer, success, failure, TIMEOUT, PRIORITY); - TestJob.this.getContext().jobQueue().addJob(test); - } - } - } - - private void sendReply(boolean ok) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending reply to a tunnel create of id " + _message.getTunnelId() - + " with ok (" + ok + ") to tunnel " + _message.getReplyTunnel() - + " on router " + _message.getReplyPeer()); - - getContext().messageHistory().receiveTunnelCreate(_message.getTunnelId(), _message.getNextRouter(), - new Date(getContext().clock().now() + 1000*_message.getTunnelDurationSeconds()), - ok, _message.getReplyPeer()); - - TunnelCreateStatusMessage msg = new TunnelCreateStatusMessage(getContext()); - msg.setFromHash(getContext().routerHash()); - msg.setTunnelId(_message.getTunnelId()); - if (ok) { - msg.setStatus(TunnelCreateStatusMessage.STATUS_SUCCESS); - } else { - // since we don't actually check anything, this is a catch all - msg.setStatus(TunnelCreateStatusMessage.STATUS_FAILED_OVERLOADED); - } - msg.setMessageExpiration(new Date(getContext().clock().now()+TIMEOUT)); - - // put that message into a garlic - GarlicMessage reply = createReply(msg); - - TunnelId outTunnelId = selectReplyTunnel(); - if (outTunnelId == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("No tunnel to send reply through"); - return; - } - SendTunnelMessageJob job = new SendTunnelMessageJob(getContext(), reply, outTunnelId, - _message.getReplyPeer(), - _message.getReplyTunnel(), - (Job)null, (ReplyJob)null, - (Job)null, (MessageSelector)null, - TIMEOUT, PRIORITY); - getContext().jobQueue().addJob(job); - } - - private GarlicMessage createReply(TunnelCreateStatusMessage body) { - GarlicConfig cfg = createReplyConfig(body); - return GarlicMessageBuilder.buildMessage(getContext(), cfg, null, null, null, - _message.getReplyKey(), _message.getReplyTag()); - } - - private GarlicConfig createReplyConfig(TunnelCreateStatusMessage body) { - GarlicConfig config = new GarlicConfig(); - - PayloadGarlicConfig replyClove = buildReplyClove(body); - config.addClove(replyClove); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); - instructions.setDelayRequested(false); - instructions.setDelaySeconds(0); - instructions.setEncrypted(false); - instructions.setEncryptionKey(null); - instructions.setRouter(null); - instructions.setTunnelId(null); - - config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - config.setDeliveryInstructions(instructions); - config.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); - config.setExpiration(TIMEOUT+getContext().clock().now()); - config.setRecipient(null); - config.setRequestAck(false); - - return config; - } - - /** - * Build a clove that sends the tunnel create reply - */ - private PayloadGarlicConfig buildReplyClove(TunnelCreateStatusMessage body) { - PayloadGarlicConfig replyClove = new PayloadGarlicConfig(); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); - instructions.setRouter(null); - instructions.setDelayRequested(false); - instructions.setDelaySeconds(0); - instructions.setEncrypted(false); - - replyClove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - replyClove.setDeliveryInstructions(instructions); - replyClove.setExpiration(TIMEOUT+getContext().clock().now()); - replyClove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); - replyClove.setPayload(body); - replyClove.setRecipient(null); - replyClove.setRequestAck(false); - - return replyClove; - } - - - private TunnelId selectReplyTunnel() { - TunnelSelectionCriteria crit = new TunnelSelectionCriteria(); - crit.setMinimumTunnelsRequired(1); - crit.setMaximumTunnelsRequired(1); - List ids = getContext().tunnelManager().selectOutboundTunnelIds(crit); - if ( (ids != null) && (ids.size() > 0) ) - return (TunnelId)ids.get(0); - else - return null; - } - - public String getName() { return "Handle Tunnel Create Message"; } - - private class JoinJob extends JobImpl { - private TunnelInfo _info; - private boolean _isReachable; - public JoinJob(RouterContext enclosingContext, TunnelInfo info, boolean isReachable) { - super(enclosingContext); - _info = info; - _isReachable = isReachable; - } - - public void runJob() { - if (!_isReachable) { - long before = JoinJob.this.getContext().clock().now(); - sendReply(false); - long after = JoinJob.this.getContext().clock().now(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("JoinJob .refuse took " + (after-before) + "ms to refuse " + _info); - } else { - long before = JoinJob.this.getContext().clock().now(); - boolean ok = JoinJob.this.getContext().tunnelManager().joinTunnel(_info); - long afterJoin = JoinJob.this.getContext().clock().now(); - sendReply(ok); - long after = JoinJob.this.getContext().clock().now(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("JoinJob .joinTunnel took " + (afterJoin-before) + "ms and sendReply took " + (after-afterJoin) + "ms"); - } - } - public String getName() { return "Process the tunnel join after testing the nextHop"; } - } - - public void dropped() { - getContext().messageHistory().messageProcessingError(_message.getUniqueId(), - _message.getClass().getName(), - "Dropped due to overload"); - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelManagerFacade.java b/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelManagerFacade.java deleted file mode 100644 index cf7ff349c..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelManagerFacade.java +++ /dev/null @@ -1,248 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.io.IOException; -import java.io.Writer; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.TunnelCreateMessage; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelManagerFacade; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.util.Log; - -/** - * Main interface to the pool - * - */ -public class PoolingTunnelManagerFacade implements TunnelManagerFacade { - private Log _log; - private TunnelPool _pool; - private TunnelTestManager _testManager; - private RouterContext _context; - private PoolingTunnelSelector _selector; - - public PoolingTunnelManagerFacade(RouterContext context) { - if (context == null) throw new IllegalArgumentException("Null routerContext is not supported"); - _context = context; - _log = context.logManager().getLog(PoolingTunnelManagerFacade.class); - _context.statManager().createFrequencyStat("tunnel.acceptRequestFrequency", "How often do we accept requests to join a tunnel?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createFrequencyStat("tunnel.rejectRequestFrequency", "How often do we reject requests to join a tunnel?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.participatingTunnels", "How many tunnels are we participating in?", "Tunnels", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.inNetMessagePool().registerHandlerJobBuilder(TunnelCreateMessage.MESSAGE_TYPE, new TunnelCreateMessageHandler(_context)); - _selector = new PoolingTunnelSelector(context); - } - - public void startup() { - if (_pool == null) { - _pool = new TunnelPool(_context); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": New tunnel pool created: " + _pool.toString()); - } - _pool.startup(); - _testManager = new TunnelTestManager(_context, _pool); - } - - public void shutdown() { - _pool.shutdown(); - _testManager.stopTesting(); - _testManager = null; - } - - public void restart() { - _pool.restart(); - } - - /** - * React to a request to join the specified tunnel. - * - * @return true if the router will accept participation, else false. - */ - public boolean joinTunnel(TunnelInfo info) { - if (info == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Null tunnel", new Exception("Null tunnel")); - _context.statManager().updateFrequency("tunnel.rejectRequestFrequency"); - return false; - } - if (info.getSettings() == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Null settings!", new Exception("settings are null")); - _context.statManager().updateFrequency("tunnel.rejectRequestFrequency"); - return false; - } - if (info.getSettings().getExpiration() == 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("No expiration for tunnel " + info.getTunnelId().getTunnelId(), - new Exception("No expiration")); - _context.statManager().updateFrequency("tunnel.rejectRequestFrequency"); - return false; - } else { - if (info.getSettings().getExpiration() < _context.clock().now()) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Already expired - " + new Date(info.getSettings().getExpiration()), - new Exception("Already expired")); - _context.statManager().updateFrequency("tunnel.rejectRequestFrequency"); - return false; - } - } - - boolean ok = _pool.addParticipatingTunnel(info); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Joining tunnel (" + ok + "): " + info); - if (!ok) - _context.statManager().updateFrequency("tunnel.rejectRequestFrequency"); - else - _context.statManager().updateFrequency("tunnel.acceptRequestFrequency"); - _context.statManager().addRateData("tunnel.participatingTunnels", _pool.getParticipatingTunnelCount(), 0); - return ok; - } - /** - * Retrieve the information related to a particular tunnel - * - */ - public TunnelInfo getTunnelInfo(TunnelId id) { - return _pool.getTunnelInfo(id); - } - /** - * Retrieve a set of tunnels from the existing ones for various purposes - */ - public List selectOutboundTunnelIds(TunnelSelectionCriteria criteria) { - return _selector.selectOutboundTunnelIds(_pool, criteria); - } - /** - * Retrieve a set of tunnels from the existing ones for various purposes - */ - public List selectInboundTunnelIds(TunnelSelectionCriteria criteria) { - return _selector.selectInboundTunnelIds(_pool, criteria); - } - - /** - * Make sure appropriate outbound tunnels are in place, builds requested - * inbound tunnels, then fire off a job to ask the ClientManagerFacade to - * validate the leaseSet, then publish it in the network database. - * - */ - public void createTunnels(Destination destination, ClientTunnelSettings clientSettings, long timeoutMs) { - ClientTunnelPool pool = _pool.getClientPool(destination); - if (pool != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("createTunnels for destination " + destination.calculateHash().toBase64() + " where the client pool exists"); - pool.setClientSettings(clientSettings); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("createTunnels for destination " + destination.calculateHash().toBase64() + " where the client pool does NOT exist"); - _pool.createClientPool(destination, clientSettings); - } - } - - /** - * Called when a peer becomes unreachable - go through all of the current - * tunnels and rebuild them if we can, or drop them if we can't. - * - */ - public void peerFailed(Hash peer) { - if (_pool == null) return; // just initialized - int numFailed = 0; - boolean shouldKill = false; - for (Iterator iter = _pool.getManagedTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = (TunnelInfo)_pool.getTunnelInfo(id); - if (isParticipant(info, peer)) { - _log.info("Peer " + peer.toBase64() + " failed and they participate in tunnel " - + id.getTunnelId() + ". Marking the tunnel as not ready!"); - if (shouldKill) { - info.setIsReady(false); - long lifetime = _context.clock().now() - info.getCreated(); - _context.statManager().addRateData("tunnel.failAfterTime", lifetime, lifetime); - } - numFailed++; - } - } - - if (_log.shouldLog(Log.INFO)) - _log.info("On peer " + peer.toBase64() + " failure, " + numFailed + " tunnels were " - + (shouldKill ? "" : "NOT ") + "killed"); - } - - private boolean isParticipant(TunnelInfo info, Hash peer) { - if ( (info == null) || (peer == null) ) return false; - TunnelInfo cur = info; - while (cur != null) { - if (peer.equals(cur.getThisHop())) return true; - if (peer.equals(cur.getNextHop())) return true; - cur = cur.getNextHopInfo(); - } - return false; - } - - /** - * True if the peer currently part of a tunnel - * - */ - public boolean isInUse(Hash peer) { - if (isInUse(peer, _pool.getManagedTunnelIds())) { - if (_log.shouldLog(Log.INFO)) - _log.debug("Peer is in a managed tunnel: " + peer.toBase64()); - return true; - } - if (isInUse(peer, _pool.getPendingTunnels())) { - if (_log.shouldLog(Log.INFO)) - _log.debug("Peer is in a pending tunnel: " + peer.toBase64()); - return true; - } - if (isInUse(peer, _pool.getParticipatingTunnels())) { - if (_log.shouldLog(Log.INFO)) - _log.debug("Peer is in a participating tunnel: " + peer.toBase64()); - return true; - } - return false; - } - - private boolean isInUse(Hash peer, Set tunnelIds) { - for (Iterator iter = tunnelIds.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getTunnelInfo(id); - if (isParticipant(info, peer)) - return true; - } - return false; - } - - public int getParticipatingCount() { - return _pool.getParticipatingTunnelCount(); - } - public int getFreeTunnelCount() { - return _pool.getFreeTunnelCount(); - } - public int getOutboundTunnelCount() { - return _pool.getOutboundTunnelCount(); - } - - /** - * Aint she pretty? - * - */ - public void renderStatusHTML(Writer out) throws IOException { - if (_pool != null) - _pool.renderStatusHTML(out); - } - - public long getLastParticipatingExpiration() { - long last = -1; - for (Iterator iter = _pool.getParticipatingTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getParticipatingTunnel(id); - if ( (info != null) && (info.getSettings().getExpiration() > last) ) - last = info.getSettings().getExpiration(); - } - return last; - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelSelector.java b/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelSelector.java deleted file mode 100644 index e5266f69e..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/PoolingTunnelSelector.java +++ /dev/null @@ -1,167 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import net.i2p.data.TunnelId; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.util.Log; - -/** - * Implement the tunnel selection algorithms - * - */ -class PoolingTunnelSelector { - private Log _log; - private RouterContext _context; - /** don't use a tunnel thats about to expire */ - private static long POOL_USE_SAFETY_MARGIN = 10*1000; - - public PoolingTunnelSelector(RouterContext context) { - _context = context; - _log = context.logManager().getLog(PoolingTunnelSelector.class); - } - - public List selectOutboundTunnelIds(TunnelPool pool, TunnelSelectionCriteria criteria) { - return selectOutboundTunnelIds(pool, criteria, true); - } - public List selectOutboundTunnelIds(TunnelPool pool, TunnelSelectionCriteria criteria, boolean recurse) { - List tunnelIds = new ArrayList(criteria.getMinimumTunnelsRequired()); - - Set outIds = pool.getOutboundTunnels(); - for (Iterator iter = outIds.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getOutboundTunnel(id); - if ( (info != null) && (info.getIsReady()) ) { - if (isAlmostExpired(pool, id, POOL_USE_SAFETY_MARGIN)) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel " + id + " is almost expired"); - } else { - tunnelIds.add(id); - } - } else { - if (info == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Outbound tunnel " + id + " was not found?! expire race perhaps?"); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Outbound tunnel " + id + " was not ready?! " + new Date(info.getSettings().getExpiration())); - } - } - } - - boolean rebuilt = false; - for (int i = outIds.size(); i < criteria.getMinimumTunnelsRequired(); i++) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Building fake tunnels because the outbound tunnels weren't sufficient"); - pool.buildFakeTunnels(true); - rebuilt = true; - } - if (rebuilt && recurse) - return selectOutboundTunnelIds(pool, criteria, false); - - List ordered = randomize(pool, tunnelIds); - List rv = new ArrayList(criteria.getMinimumTunnelsRequired()); - for (Iterator iter = ordered.iterator(); iter.hasNext() && (rv.size() < criteria.getMinimumTunnelsRequired()); ) { - rv.add(iter.next()); - } - if (_log.shouldLog(Log.INFO)) - _log.info("Selecting outbound tunnelIds [all outbound tunnels: " + outIds.size() - + ", tunnelIds ready: " + ordered.size() + ", rv: " + rv + "]"); - return rv; - } - - public List selectInboundTunnelIds(TunnelPool pool, TunnelSelectionCriteria criteria) { - return selectInboundTunnelIds(pool, criteria, true); - } - public List selectInboundTunnelIds(TunnelPool pool, TunnelSelectionCriteria criteria, boolean recurse) { - List tunnels = new ArrayList(criteria.getMinimumTunnelsRequired()); - - for (Iterator iter = pool.getFreeTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getFreeTunnel(id); - if (info == null) continue; - if (info.getIsReady()) { - if (isAlmostExpired(pool, id, POOL_USE_SAFETY_MARGIN)) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel " + id + " is almost expired"); - } else { - tunnels.add(id); - } - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Inbound tunnel " + id + " is not ready?! " - + new Date(info.getSettings().getExpiration())); - } - } - - boolean rebuilt = false; - for (int i = tunnels.size(); i < criteria.getMinimumTunnelsRequired(); i++) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Building fake tunnels because the inbound tunnels weren't sufficient"); - pool.buildFakeTunnels(true); - rebuilt = true; - } - if (rebuilt && recurse) - return selectInboundTunnelIds(pool, criteria, false); - - List ordered = randomize(pool, tunnels); - List rv = new ArrayList(criteria.getMinimumTunnelsRequired()); - for (Iterator iter = ordered.iterator(); iter.hasNext() && (rv.size() < criteria.getMinimumTunnelsRequired()); ) { - rv.add(iter.next()); - } - if (_log.shouldLog(Log.INFO)) - _log.info("Selecting inbound tunnelIds [tunnelIds ready: " - + tunnels.size() + ", rv: " + rv + "]"); - return rv; - } - - //// - // helpers - //// - - - private List randomize(TunnelPool pool, List tunnelIds) { - List rv = new ArrayList(tunnelIds.size()); - for (Iterator iter = tunnelIds.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - rv.add(id); - } - Collections.shuffle(rv, _context.random()); - return rv; - } - - private boolean isAlmostExpired(TunnelPool pool, TunnelId id, long safetyMargin) { - TunnelInfo info = pool.getTunnelInfo(id); - if (info == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Tunnel " + id.getTunnelId() + " is not known"); - return true; - } - if (info.getSettings() == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Tunnel " + id.getTunnelId() + " has no settings"); - return true; - } - if (info.getSettings().getExpiration() <= 0) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Tunnel " + id.getTunnelId() + " has no expiration"); - return true; - } - if (info.getSettings().getExpiration() - safetyMargin <= _context.clock().now()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Expiration of tunnel " + id.getTunnelId() - + " has almost been reached [" - + new Date(info.getSettings().getExpiration()) + "]"); - return true; - } else { - return false; - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/RequestInboundTunnelJob.java b/router/java/src/net/i2p/router/tunnelmanager/RequestInboundTunnelJob.java deleted file mode 100644 index a433d3aac..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/RequestInboundTunnelJob.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -class RequestInboundTunnelJob extends JobImpl { - private Log _log; - private TunnelPool _pool; - private boolean _useFake; - private TunnelBuilder _builder; - - public RequestInboundTunnelJob(RouterContext context, TunnelPool pool) { - this(context, pool, false); - } - public RequestInboundTunnelJob(RouterContext context, TunnelPool pool, boolean useFake) { - super(context); - _log = context.logManager().getLog(RequestInboundTunnelJob.class); - _pool = pool; - _useFake = useFake; - _builder = new TunnelBuilder(context); - } - - public String getName() { return "Request Inbound Tunnel"; } - public void runJob() { - _log.debug("Client pool settings: " + _pool.getPoolSettings().toString()); - TunnelInfo tunnelGateway = _builder.configureInboundTunnel(null, _pool.getPoolSettings(), _useFake); - RequestTunnelJob reqJob = new RequestTunnelJob(getContext(), _pool, tunnelGateway, true, _pool.getTunnelCreationTimeout()); - getContext().jobQueue().addJob(reqJob); - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/RequestOutboundTunnelJob.java b/router/java/src/net/i2p/router/tunnelmanager/RequestOutboundTunnelJob.java deleted file mode 100644 index 1be263b50..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/RequestOutboundTunnelJob.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; - -class RequestOutboundTunnelJob extends JobImpl { - private TunnelPool _pool; - private boolean _useFake; - private TunnelBuilder _builder; - - public RequestOutboundTunnelJob(RouterContext context, TunnelPool pool, boolean useFake) { - super(context); - _pool = pool; - _useFake = useFake; - _builder = new TunnelBuilder(context); - } - - public String getName() { return "Request Outbound Tunnel"; } - public void runJob() { - TunnelInfo tunnelGateway = _builder.configureOutboundTunnel(_pool.getPoolSettings(), _useFake); - RequestTunnelJob reqJob = new RequestTunnelJob(getContext(), _pool, tunnelGateway, false, _pool.getTunnelCreationTimeout()); - getContext().jobQueue().addJob(reqJob); - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnelmanager/RequestTunnelJob.java deleted file mode 100644 index 22b81949b..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/RequestTunnelJob.java +++ /dev/null @@ -1,748 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import net.i2p.data.Certificate; -import net.i2p.data.PublicKey; -import net.i2p.data.RouterInfo; -import net.i2p.data.SessionKey; -import net.i2p.data.SessionTag; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DeliveryInstructions; -import net.i2p.data.i2np.DeliveryStatusMessage; -import net.i2p.data.i2np.GarlicMessage; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelCreateMessage; -import net.i2p.data.i2np.TunnelCreateStatusMessage; -import net.i2p.router.Job; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageHistory; -import net.i2p.router.MessageSelector; -import net.i2p.router.ReplyJob; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.router.message.GarlicConfig; -import net.i2p.router.message.GarlicMessageBuilder; -import net.i2p.router.message.PayloadGarlicConfig; -import net.i2p.router.message.SendTunnelMessageJob; -import net.i2p.util.Log; - -/** - * Request the creation of a new tunnel - * - */ -public class RequestTunnelJob extends JobImpl { - private Log _log; - private TunnelPool _pool; - private boolean _complete; - private long _timeoutMs; - private long _expiration; - private TunnelInfo _tunnelGateway; - private List _toBeRequested; // list of participants, from endpoint to gateway - private Set _failedTunnelParticipants; // set of Hash of the RouterIdentity of participants who timed out or rejected - private boolean _isInbound; - - private final static int PRIORITY = 300; // high since we are creating tunnels for a client - - RequestTunnelJob(RouterContext context, TunnelPool pool, TunnelInfo tunnelGateway, boolean isInbound, long timeoutMs) { - super(context); - _log = context.logManager().getLog(RequestTunnelJob.class); - context.statManager().createFrequencyStat("tunnel.buildFrequency", "How often does the router build a tunnel?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - context.statManager().createFrequencyStat("tunnel.buildFailFrequency", "How often does a peer in the tunnel fail to join??", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - - _pool = pool; - _tunnelGateway = tunnelGateway; - _timeoutMs = timeoutMs; - _expiration = -1; - _isInbound = isInbound; - _failedTunnelParticipants = new HashSet(); - _complete = false; - - List participants = new ArrayList(); - TunnelInfo cur = _tunnelGateway; - while (cur != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel " + cur.getTunnelId() + " includes " + cur.getThisHop().toBase64()); - participants.add(cur); - cur = cur.getNextHopInfo(); - } - if (isInbound) { - if (_log.shouldLog(Log.INFO)) - _log.info("Requesting inbound tunnel " + _tunnelGateway.getTunnelId() + " with " - + participants.size() + " participants in it"); - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Requesting outbound tunnel " + _tunnelGateway.getTunnelId() + " with " + participants.size() + " participants in it"); - } - - // work backwards (end point, then the router pointing at the endpoint, then the router pointing at that, etc, until the gateway - _toBeRequested = new ArrayList(participants.size()); - for (int i = participants.size()-1; i >= 0; i--) { - TunnelInfo peer = (TunnelInfo)participants.get(i); - if (null != getContext().netDb().lookupRouterInfoLocally(peer.getThisHop())) { - _toBeRequested.add(participants.get(i)); - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn("ok who the fuck requested someone we don't know about? (dont answer that"); - } - } - - // since we request serially, we need to up the timeout serially - // change this once we go parallel - //_timeoutMs *= participants.size()+1; - _expiration = (_timeoutMs * _toBeRequested.size()) + getContext().clock().now(); - } - - public String getName() { return "Request Tunnel"; } - public void runJob() { - if (getContext().clock().now() > _expiration) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Timeout reached building tunnel (timeout = " + _timeoutMs + " expiration = " + new Date(_expiration) + ")"); - fail(); - return; - } - - TunnelInfo peer = null; - synchronized (_toBeRequested) { - if (_toBeRequested.size() > 0) { - _pool.addPendingTunnel(_tunnelGateway); - - peer = (TunnelInfo)_toBeRequested.remove(0); - if ( (peer == null) || (peer.getThisHop() == null) ) { - return; - } else { - // jump out of the synchronized block to request - } - } - } - if (peer != null) - requestParticipation(peer); - } - - private void requestParticipation(TunnelInfo participant) { - // find the info about who we're looking for - RouterInfo target = getContext().netDb().lookupRouterInfoLocally(participant.getThisHop()); - if (target == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error - no db info known for participant " + participant.getThisHop()); - fail(); - return; - } - - if (target.getIdentity().getHash().equals(getContext().routerHash())) { - // short circuit the ok - okLocalParticipation(participant); - return; - } - - // select send method [outbound tunnel or garlic through peers] - TunnelId outboundTunnel = selectOutboundTunnel(); - if (outboundTunnel == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("No outbound tunnels! unable to request a new tunnel!"); - fail(); - return; - } - - // select inbound tunnel gateway - TunnelGateway inboundGateway = selectInboundGateway(participant); - if (inboundGateway == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Unable to find an inbound gateway"); - fail(); - return; - } - - SessionKey wrappedKey = new SessionKey(); - Set wrappedTags = new HashSet(64); - PublicKey wrappedTo = new PublicKey(); - - RequestState state = new RequestState(wrappedKey, wrappedTags, wrappedTo, - participant, inboundGateway, - outboundTunnel, target); - Request r = new Request(getContext(), state); - getContext().jobQueue().addJob(r); - } - - /** - * The request job steps through the RequestState, pushing it along one pass - * at a time, all with each pass occurring as a seperate sequential job. This - * is useful since the RequestTunnelJob can otherwise take upwards of 3+ seconds, - * since the various steps may involve full ElGamal encryption (for source route - * blocks, the garlic, etc). - */ - public class Request extends JobImpl { - private RequestState _state; - Request(RouterContext enclosingContext, RequestState state) { - super(enclosingContext); - _state = state; - } - - public void runJob() { - boolean needsMore = _state.doNext(); - if (needsMore) { - requeue(0); - } else { - MessageHistory hist = Request.this.getContext().messageHistory(); - hist.requestTunnelCreate(_tunnelGateway.getTunnelId(), - _state.getOutboundTunnel(), - _state.getParticipant().getThisHop(), - _state.getParticipant().getNextHop(), - _state.getInboundGateway().getTunnelId(), - _state.getInboundGateway().getGateway()); - } - } - - public String getName() { return "Request Tunnel (partial)"; } - } - - /** - * Contain the partial state for preparing the request - doNext starts by - * building a TunnelCreateMessage, and on the next pass it builds a - * DeliveryStatusMessage, and on the pass after that, it builds a GarlicMessage - * containing those two, and on its final pass, it sends everything out through - * a tunnel with appropriate handling jobs - * - */ - private class RequestState { - private SessionKey _wrappedKey; - private Set _wrappedTags; - private PublicKey _wrappedTo; - private TunnelCreateMessage _createMsg; - private GarlicMessage _garlicMessage; - private TunnelInfo _participant; - private TunnelGateway _inboundGateway; - private TunnelId _outboundTunnel; - private RouterInfo _target; - - public RequestState(SessionKey wrappedKey, Set wrappedTags, PublicKey wrappedTo, - TunnelInfo participant, TunnelGateway inboundGateway, - TunnelId outboundTunnel, RouterInfo target) { - _wrappedKey = wrappedKey; - _wrappedTags = wrappedTags; - _wrappedTo = wrappedTo; - _participant = participant; - _inboundGateway = inboundGateway; - _outboundTunnel = outboundTunnel; - _target = target; - } - - public TunnelId getOutboundTunnel() { return _outboundTunnel; } - public TunnelInfo getParticipant() { return _participant; } - public TunnelGateway getInboundGateway() { return _inboundGateway; } - - public boolean doNext() { - if (_createMsg == null) { - _createMsg = buildTunnelCreate(_participant, _inboundGateway); - return true; - } else if (_garlicMessage == null) { - _garlicMessage = buildGarlicMessage(_createMsg, _inboundGateway, _target, - _wrappedKey, _wrappedTags, _wrappedTo); - return true; - } else { - // send the GarlicMessage - if (_log.shouldLog(Log.INFO)) - _log.info("Sending tunnel create to " + _target.getIdentity().getHash().toBase64() + - " to inbound gateway " + _inboundGateway.getGateway().toBase64() + - " : " + _inboundGateway.getTunnelId().getTunnelId()); - ReplyJob onReply = new Success(getContext(), _participant, _wrappedKey, _wrappedTags, _wrappedTo, _inboundGateway.getTunnelId(), _outboundTunnel); - Job onFail = new Failure(getContext(), _participant, _inboundGateway.getTunnelId(), _outboundTunnel); - MessageSelector selector = new Selector(_participant); - SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), _garlicMessage, - _outboundTunnel, _target.getIdentity().getHash(), - null, null, onReply, onFail, - selector, _timeoutMs, PRIORITY); - getContext().jobQueue().addJob(j); - return false; - } - } - } - - /** - * Handle the "will you participate" request that we would send to ourselves in a special case (aka fast) manner, - * as, chances are, we'll always agree ;) - * - */ - private void okLocalParticipation(TunnelInfo info) { - if (_log.shouldLog(Log.INFO)) - _log.info("Short circuiting the local join to tunnel " + info.getTunnelId()); - peerSuccess(info); - } - - /** - * Select an outbound tunnel for sending the tunnel create status message - * - */ - private TunnelId selectOutboundTunnel() { - TunnelSelectionCriteria crit = new TunnelSelectionCriteria(); - crit.setMaximumTunnelsRequired(1); - crit.setMinimumTunnelsRequired(1); - crit.setAnonymityPriority(50); // arbitrary - crit.setLatencyPriority(50); // arbitrary - crit.setReliabilityPriority(50); // arbitrary - - List tunnelIds = getContext().tunnelManager().selectOutboundTunnelIds(crit); - TunnelId id = null; - if (tunnelIds.size() > 0) - id = (TunnelId)tunnelIds.get(0); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Outbound tunnel selected: " + id); - return id; - } - - /** - * Select an inbound tunnel to receive replies and acks from the participant - * - */ - private TunnelGateway selectInboundGateway(TunnelInfo participant) { - TunnelSelectionCriteria criteria = new TunnelSelectionCriteria(); - criteria.setAnonymityPriority(66); - criteria.setReliabilityPriority(66); - criteria.setLatencyPriority(33); - criteria.setMaximumTunnelsRequired(1); - criteria.setMinimumTunnelsRequired(1); - List ids = getContext().tunnelManager().selectInboundTunnelIds(criteria); - if (ids.size() <= 0) { - if (_log.shouldLog(Log.ERROR)) - _log.error("No inbound tunnels to receive the tunnel create messages. Argh", - new Exception("Tunnels suck. whats up?")); - return null; - } else { - TunnelInfo gateway = null; - TunnelId id = null; - for (int i = 0; i < ids.size(); i++) { - id = (TunnelId)ids.get(i); - gateway = getContext().tunnelManager().getTunnelInfo(id); - if (gateway != null) - break; - } - if (gateway != null) { - TunnelGateway gw = new TunnelGateway(id, gateway.getThisHop()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Inbound tunnel gateway: " + id + " on router " + gateway.getThisHop()); - return gw; - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("No gateway found?!", new Exception("No gateway")); - return null; - } - } - } - - /** - * Build a TunnelCreateMessage to the participant - */ - private TunnelCreateMessage buildTunnelCreate(TunnelInfo participant, TunnelGateway replyGateway) { - TunnelCreateMessage msg = new TunnelCreateMessage(getContext()); - msg.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - msg.setConfigurationKey(participant.getConfigurationKey()); - msg.setIncludeDummyTraffic(participant.getSettings().getIncludeDummy()); - msg.setMaxAvgBytesPerMin(participant.getSettings().getBytesPerMinuteAverage()); - msg.setMaxAvgMessagesPerMin(participant.getSettings().getMessagesPerMinuteAverage()); - msg.setMaxPeakBytesPerMin(participant.getSettings().getBytesPerMinutePeak()); - msg.setMaxPeakMessagesPerMin(participant.getSettings().getMessagesPerMinutePeak()); - msg.setNextRouter(participant.getNextHop()); - // TODO: update the TunnelInfo structure so we can have the tunnel contain - // different tunnelIds per hop - msg.setNextTunnelId(participant.getTunnelId()); - if (participant.getNextHop() == null) - msg.setParticipantType(TunnelCreateMessage.PARTICIPANT_TYPE_ENDPOINT); - else if (participant.getSigningKey() != null) - msg.setParticipantType(TunnelCreateMessage.PARTICIPANT_TYPE_GATEWAY); - else - msg.setParticipantType(TunnelCreateMessage.PARTICIPANT_TYPE_OTHER); - msg.setReorderMessages(participant.getSettings().getReorder()); - - - SessionKey replySessionKey = getContext().keyGenerator().generateSessionKey(); - SessionTag tag = new SessionTag(true); - Set tags = new HashSet(); - tags.add(tag); - // make it so we'll read the session tag correctly and use the right session key - getContext().sessionKeyManager().tagsReceived(replySessionKey, tags); - - msg.setReplyPeer(replyGateway.getGateway()); - msg.setReplyTunnel(replyGateway.getTunnelId()); - msg.setReplyKey(replySessionKey); - msg.setReplyTag(tag); - - long duration = participant.getSettings().getExpiration() - getContext().clock().now(); - if (duration == 0) duration = 1; - msg.setTunnelDurationSeconds(duration/1000); - msg.setTunnelId(participant.getTunnelId()); - msg.setTunnelKey(participant.getEncryptionKey()); - msg.setVerificationPrivateKey(participant.getSigningKey()); - msg.setVerificationPublicKey(participant.getVerificationKey()); - - return msg; - } - - /** - * Build a garlic message wrapping the data and status as cloves with both to be routed - * through the target, where the data is destined. - * - */ - private GarlicMessage buildGarlicMessage(I2NPMessage data, - TunnelGateway replyTunnel, - RouterInfo target, SessionKey wrappedKey, - Set wrappedTags, PublicKey wrappedTo) { - GarlicConfig config = buildGarlicConfig(data, replyTunnel, target); - - PublicKey rcptKey = config.getRecipientPublicKey(); - if (rcptKey == null) { - if (config.getRecipient() == null) { - throw new IllegalArgumentException("Null recipient specified"); - } else if (config.getRecipient().getIdentity() == null) { - throw new IllegalArgumentException("Null recipient.identity specified"); - } else if (config.getRecipient().getIdentity().getPublicKey() == null) { - throw new IllegalArgumentException("Null recipient.identity.publicKey specified"); - } else - rcptKey = config.getRecipient().getIdentity().getPublicKey(); - } - - if (wrappedTo != null) - wrappedTo.setData(rcptKey.getData()); - - long start = getContext().clock().now(); - GarlicMessage message = GarlicMessageBuilder.buildMessage(getContext(), config, wrappedKey, wrappedTags, 10); - long end = getContext().clock().now(); - if ( (end - start) > 1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Took more than a second (" + (end-start) + "ms) to create the garlic for the tunnel"); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Took LESS than a second (" + (end-start) + "ms) to create the garlic for the tunnel!"); - } - return message; - } - - private GarlicConfig buildGarlicConfig(I2NPMessage data, - TunnelGateway replyTunnel, RouterInfo target) { - GarlicConfig config = new GarlicConfig(); - - long garlicExpiration = getContext().clock().now() + _timeoutMs; - PayloadGarlicConfig dataClove = buildDataClove(data, target, garlicExpiration); - config.addClove(dataClove); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER); - instructions.setDelayRequested(false); - instructions.setDelaySeconds(0); - instructions.setEncrypted(false); - instructions.setEncryptionKey(null); - instructions.setRouter(target.getIdentity().getHash()); - instructions.setTunnelId(null); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Setting the expiration on the garlic config to " + (new Date(garlicExpiration))); - - config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - config.setDeliveryInstructions(instructions); - config.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); - config.setExpiration(garlicExpiration); - config.setRecipientPublicKey(target.getIdentity().getPublicKey()); - - return config; - } - - /** - * Build a clove that sends the data to the target (which is local) - */ - private PayloadGarlicConfig buildDataClove(I2NPMessage data, RouterInfo target, long expiration) { - PayloadGarlicConfig clove = new PayloadGarlicConfig(); - - DeliveryInstructions instructions = new DeliveryInstructions(); - instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL); - instructions.setRouter(target.getIdentity().getHash()); - instructions.setTunnelId(null); - instructions.setDelayRequested(false); - instructions.setDelaySeconds(0); - instructions.setEncrypted(false); - - clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - clove.setDeliveryInstructions(instructions); - clove.setExpiration(expiration); - clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE)); - clove.setPayload(data); - clove.setRecipientPublicKey(null); - clove.setRequestAck(false); - - return clove; - } - - private void fail() { - if (_complete) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Build tunnel failed via " + _tunnelGateway.getThisHop().toBase64() - + ", but we've already completed, so fuck off: " + _tunnelGateway, - new Exception("Fail aborted")); - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn("Build tunnel " + _tunnelGateway.getTunnelId().getTunnelId() - + " with gateway " + _tunnelGateway.getThisHop().toBase64() - + " FAILED: " + _failedTunnelParticipants + " - " + _tunnelGateway, - new Exception("Why did we fail building?")); - synchronized (_toBeRequested) { - _toBeRequested.clear(); - } - synchronized (_failedTunnelParticipants) { - _failedTunnelParticipants.clear(); - } - _complete = true; - } - } - private void peerSuccess(TunnelInfo peer) { - int numLeft = 0; - synchronized (_toBeRequested) { - numLeft = _toBeRequested.size(); - } - if (numLeft <= 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("Peer (" + peer.getThisHop().toBase64() - + ") successful: mark the tunnel as completely ready [inbound? " - + _isInbound + "]"); - _complete = true; - if (_isInbound) - _pool.addFreeTunnel(_tunnelGateway); - else - _pool.addOutboundTunnel(_tunnelGateway); - _tunnelGateway.setIsReady(true); - getContext().statManager().updateFrequency("tunnel.buildFrequency"); - } else { - if (_log.shouldLog(Log.DEBUG)) { - StringBuffer buf = new StringBuffer(128); - buf.append("Hop to ").append(peer.getThisHop().toBase64()); - buf.append(" successful for tunnel ").append(peer.getTunnelId().getTunnelId()); - buf.append(", but ").append(numLeft).append(" are pending"); - _log.debug(buf.toString()); - } - getContext().jobQueue().addJob(this); - } - } - - - public void dropped() { - _pool.buildFakeTunnels(); - if (_log.shouldLog(Log.WARN)) - _log.warn("Dropping request to create a new tunnel, so we may have manually created a new fake inbound and a new fake outbound, just in case we needed that..."); - } - - - private class Success extends JobImpl implements ReplyJob { - private TunnelInfo _tunnel; - private List _messages; - private boolean _successCompleted; - private SessionKey _wrappedKey; - private Set _wrappedTags; - private PublicKey _wrappedTo; - private TunnelId _replyTunnelId; - private TunnelId _outboundTunnelId; - private long _started; - - public Success(RouterContext enclosingContext, TunnelInfo tunnel, SessionKey wrappedKey, Set wrappedTags, PublicKey wrappedTo, TunnelId replyTunnelId, TunnelId outboundTunnelId) { - super(enclosingContext); - _tunnel = tunnel; - _messages = new LinkedList(); - _successCompleted = false; - _wrappedKey = wrappedKey; - _wrappedTags = wrappedTags; - _wrappedTo = wrappedTo; - _replyTunnelId = replyTunnelId; - _outboundTunnelId = outboundTunnelId; - _started = getContext().clock().now(); - } - - public String getName() { return "Create Tunnel Status Received"; } - public void runJob() { - List toProc = null; - synchronized (_messages) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("# messages received for successs: " + _messages.size()); - toProc = new ArrayList(_messages); - _messages.clear(); - } - - long responseTime = getContext().clock().now() - _started; - for (Iterator iter = toProc.iterator(); iter.hasNext(); ) { - I2NPMessage msg = (I2NPMessage)iter.next(); - process(msg, responseTime); - } - } - - private void process(I2NPMessage message, long responseTime) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Running success status job (tunnel = " + _tunnel + " msg = " + message + ")"); - if (message.getType() == DeliveryStatusMessage.MESSAGE_TYPE) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel creation message acknowledged for tunnel " + _tunnel.getTunnelId() - + " at router " + _tunnel.getThisHop().toBase64()); - } else { - TunnelCreateStatusMessage msg = (TunnelCreateStatusMessage)message; - if (_successCompleted) { - _log.info("Already completed in the Success task [skipping " + msg.getStatus() + "]"); - return; - } - switch (msg.getStatus()) { - case TunnelCreateStatusMessage.STATUS_FAILED_CERTIFICATE: - case TunnelCreateStatusMessage.STATUS_FAILED_DELETED: - case TunnelCreateStatusMessage.STATUS_FAILED_DUPLICATE_ID: - case TunnelCreateStatusMessage.STATUS_FAILED_OVERLOADED: - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel creation failed for tunnel " + _tunnel.getTunnelId() - + " at router " + _tunnel.getThisHop().toBase64() - + " with status " + msg.getStatus()); - getContext().profileManager().tunnelRejected(_tunnel.getThisHop(), responseTime, true); - Success.this.getContext().messageHistory().tunnelRejected(_tunnel.getThisHop(), - _tunnel.getTunnelId(), - null, "refused"); - fail(); - _successCompleted = true; - break; - case TunnelCreateStatusMessage.STATUS_SUCCESS: - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel creation succeeded for tunnel " + _tunnel.getTunnelId() - + " at router " + _tunnel.getThisHop().toBase64()); - - if ( (_wrappedKey != null) && (_wrappedKey.getData() != null) && - (_wrappedTags != null) && (_wrappedTags.size() > 0) && - (_wrappedTo != null) ) { - Success.this.getContext().sessionKeyManager().tagsDelivered(_wrappedTo, _wrappedKey, _wrappedTags); - if (_log.shouldLog(Log.INFO)) - _log.info("Delivered tags successfully to " + _tunnel.getThisHop().toBase64() - + "! # tags: " + _wrappedTags.size()); - } - - _tunnel.setIsReady(true); - getContext().profileManager().tunnelJoined(_tunnel.getThisHop(), responseTime); - peerSuccess(_tunnel); - _successCompleted = true; - break; - } - } - } - - public void setMessage(I2NPMessage message) { - synchronized (_messages) { - _messages.add(message); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Reply message " + _messages.size() + " received " - + message.getClass().getName(), new Exception("Received from")); - } - } - } - - private class Failure extends JobImpl { - private TunnelInfo _tunnel; - private TunnelId _outboundTunnelId; - private TunnelId _replyTunnelId; - private long _started; - public Failure(RouterContext enclosingContext, TunnelInfo tunnel, TunnelId replyTunnelId, TunnelId outboundTunnelId) { - super(enclosingContext); - _tunnel = tunnel; - _replyTunnelId = replyTunnelId; - _outboundTunnelId = outboundTunnelId; - _started = getContext().clock().now(); - } - - public String getName() { return "Create Tunnel Failed"; } - public void runJob() { - // update the tunnel so its known to be not working - if (_log.shouldLog(Log.WARN)) { - _log.warn("Tunnel creation timed out for tunnel " + _tunnel.getTunnelId() + " at router " - + _tunnel.getThisHop().toBase64() + " from router " - + getContext().routerHash().toBase64() + " after waiting " - + (getContext().clock().now()-_started) + "ms"); - _log.warn("Added by", Failure.this.getAddedBy()); - } - synchronized (_failedTunnelParticipants) { - _failedTunnelParticipants.add(_tunnel.getThisHop()); - } - Failure.this.getContext().messageHistory().tunnelRequestTimedOut(_tunnel.getThisHop(), _tunnel.getTunnelId()); - long responseTime = getContext().clock().now() - _started; - // perhaps not an explicit reject, but an implicit one (due to dropped messages, tunnel failure, etc) - getContext().profileManager().tunnelRejected(_tunnel.getThisHop(), responseTime, false); - getContext().profileManager().messageFailed(_tunnel.getThisHop()); - - // one (or both) of the tunnels used to send the request / receive - // a reply failed, or the peer failed, or the peer's tunnels failed - //_pool.tunnelFailed(_replyTunnelId); - //_pool.tunnelFailed(_outboundTunnelId); - - Failure.this.getContext().statManager().updateFrequency("tunnel.buildFailFrequency"); - fail(); - } - } - - private class Selector implements MessageSelector { - private TunnelInfo _tunnel; - private boolean _statusFound; - private long _attemptExpiration; - - public Selector(TunnelInfo tunnel) { - _tunnel = tunnel; - _statusFound = false; - _attemptExpiration = getContext().clock().now() + _timeoutMs; - } - - public boolean continueMatching() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("ContinueMatching looking for tunnel " + _tunnel.getTunnelId().getTunnelId() - + " from " + _tunnel.getThisHop().toBase64() + ": found? " + _statusFound); - return !_statusFound; - } - public long getExpiration() { return _attemptExpiration; } - public boolean isMatch(I2NPMessage message) { - if (message.getType() == TunnelCreateStatusMessage.MESSAGE_TYPE) { - TunnelCreateStatusMessage msg = (TunnelCreateStatusMessage)message; - if (_tunnel.getThisHop().equals(msg.getFromHash())) { - if (_tunnel.getTunnelId().equals(msg.getTunnelId())) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Matches the tunnel create status message"); - _statusFound = true; - return true; - } else { - // hmm another tunnel through the peer... - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Status message from peer [" + msg.getFromHash().toBase64() - + "], with wrong tunnelId [" + msg.getTunnelId() - + "] not [" + _tunnel.getTunnelId().getTunnelId() + "]"); - return false; - } - } else { - // status message but from the wrong peer - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Status message from the wrong peer [" - + msg.getFromHash().toBase64() + "], not [" - + _tunnel.getThisHop().toBase64() + "]"); - return false; - } - } else { - //_log.debug("Message " + message.getClass().getName() - // + " is not a delivery status or tunnel create status message [waiting for ok for tunnel " - // + _tunnel.getTunnelId() + " so we can fire " + _onCreated + "]"); - return false; - } - } - - public String toString() { - return "Build Tunnel Job Selector for tunnel " + _tunnel.getTunnelId().getTunnelId() - + " at " + _tunnel.getThisHop().toBase64() + " [found=" + _statusFound + "] (@" - + (new Date(getExpiration())) + ")"; - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TestTunnelJob.java b/router/java/src/net/i2p/router/tunnelmanager/TestTunnelJob.java deleted file mode 100644 index f3e0f0fbb..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TestTunnelJob.java +++ /dev/null @@ -1,388 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.Date; -import java.util.List; - -import net.i2p.data.Hash; -import net.i2p.data.TunnelId; -import net.i2p.data.i2np.DeliveryStatusMessage; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.router.JobImpl; -import net.i2p.router.MessageSelector; -import net.i2p.router.ReplyJob; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSelectionCriteria; -import net.i2p.router.message.SendTunnelMessageJob; -import net.i2p.stat.RateStat; -import net.i2p.stat.Rate; -import net.i2p.util.Log; - -class TestTunnelJob extends JobImpl { - private Log _log; - /** tunnel that we want to test */ - private TunnelId _primaryId; - /** tunnel that is used to help test the primary id */ - private TunnelId _secondaryId; - private TunnelPool _pool; - private long _nonce; - - public TestTunnelJob(RouterContext ctx, TunnelId id, TunnelPool pool) { - super(ctx); - _log = ctx.logManager().getLog(TestTunnelJob.class); - _primaryId = id; - _pool = pool; - _nonce = ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE); - } - public String getName() { return "Test Tunnel"; } - public void runJob() { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Testing tunnel " + _primaryId.getTunnelId()); - TunnelInfo info = _pool.getTunnelInfo(_primaryId); - if (info == null) { - _log.error("wtf, why are we testing a tunnel that we do not know about? [" - + _primaryId.getTunnelId() + "]", getAddedBy()); - return; - } - - // mark it as something we're testing - info.setLastTested(getContext().clock().now()); - if (isOutbound(info)) { - testOutbound(info); - } else { - testInbound(info); - } - } - - private boolean isOutbound(TunnelInfo info) { - if (info == null) { - _log.error("wtf, null info?", new Exception("Who checked a null tunnel info?")); - return false; - } - if (getContext().routerHash().equals(info.getThisHop())) - return true; - else - return false; - } - - private final static long DEFAULT_TEST_TIMEOUT = 10*1000; // 10 seconds for a test to succeed - private final static long DEFAULT_MINIMUM_TEST_TIMEOUT = 10*1000; // 5 second min - private final static long MAXIMUM_TEST_TIMEOUT = 60*1000; // 60 second max - private final static int TEST_PRIORITY = 100; - - /** - * how long should we let tunnel tests go on for? - */ - private long getTunnelTestTimeout() { - long rv = DEFAULT_TEST_TIMEOUT; - RateStat rs = getContext().statManager().getRate("tunnel.testSuccessTime"); - if (rs != null) { - Rate r = rs.getRate(10*60*1000); - if (r != null) { - if (r.getLifetimeEventCount() > 10) { - if (r.getLastEventCount() <= 0) - rv = (long)(r.getLifetimeAverageValue() * getTunnelTestDeviationLimit()); - else - rv = (long)(r.getAverageValue() * getTunnelTestDeviationLimit()); - } - } - } - - // lets back off if we're failing - rs = getContext().statManager().getRate("tunnel.failAfterTime"); - if (rs != null) { - Rate r = rs.getRate(5*60*1000); - if (r != null) { - long failures = r.getLastEventCount() + r.getCurrentEventCount(); - if (failures > 0) { - rv <<= failures; - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnels are failing (" + failures + "), so set the timeout to " + rv); - } - } - } - - if (rv > MAXIMUM_TEST_TIMEOUT) { - rv = MAXIMUM_TEST_TIMEOUT; - } else { - long min = getMinimumTestTimeout(); - if (rv < min) - rv = min; - } - return rv; - } - - /** - * How much greater than the current average tunnel test time should we accept? - */ - private double getTunnelTestDeviationLimit() { - try { - return Double.parseDouble(getContext().getProperty("router.tunnelTestDeviation", "2.0")); - } catch (NumberFormatException nfe) { - return 2.0; - } - } - - private long getMinimumTestTimeout() { - String timeout = getContext().getProperty("router.tunnelTestMinimum", ""+DEFAULT_MINIMUM_TEST_TIMEOUT); - if (timeout != null) { - try { - return Long.parseLong(timeout); - } catch (NumberFormatException nfe) { - return DEFAULT_MINIMUM_TEST_TIMEOUT; - } - } else { - return DEFAULT_MINIMUM_TEST_TIMEOUT; - } - } - - /** - * Send a message out the tunnel with instructions to send the message back - * to ourselves and wait for it to arrive. - */ - private void testOutbound(TunnelInfo info) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Testing outbound tunnel " + info); - DeliveryStatusMessage msg = new DeliveryStatusMessage(getContext()); - msg.setArrival(new Date(getContext().clock().now())); - msg.setMessageId(_nonce); - Hash us = getContext().routerHash(); - _secondaryId = getReplyTunnel(); - if (_secondaryId == null) { - getContext().jobQueue().addJob(new TestFailedJob(getContext())); - return; - } - - TunnelInfo inboundInfo = _pool.getTunnelInfo(_secondaryId); - inboundInfo.setLastTested(getContext().clock().now()); - - long timeout = getTunnelTestTimeout(); - TestFailedJob failureJob = new TestFailedJob(getContext()); - MessageSelector selector = new TestMessageSelector(msg.getMessageId(), info.getTunnelId().getTunnelId(), timeout); - SendTunnelMessageJob testJob = new SendTunnelMessageJob(getContext(), msg, info.getTunnelId(), us, _secondaryId, null, new TestSuccessfulJob(getContext(), timeout), failureJob, selector, timeout, TEST_PRIORITY); - getContext().jobQueue().addJob(testJob); - } - - /** - * Send a message to the gateway and wait for it to arrive. - */ - private void testInbound(TunnelInfo info) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Testing inbound tunnel " + info); - DeliveryStatusMessage msg = new DeliveryStatusMessage(getContext()); - msg.setArrival(new Date(getContext().clock().now())); - msg.setMessageId(_nonce); - - _secondaryId = getOutboundTunnel(); - if (_secondaryId == null) { - getContext().jobQueue().addJob(new TestFailedJob(getContext())); - return; - } - - TunnelInfo outboundInfo = _pool.getTunnelInfo(_secondaryId); - outboundInfo.setLastTested(getContext().clock().now()); - - long timeout = getTunnelTestTimeout(); - TestFailedJob failureJob = new TestFailedJob(getContext()); - MessageSelector selector = new TestMessageSelector(msg.getMessageId(), info.getTunnelId().getTunnelId(), timeout); - SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, _secondaryId, info.getThisHop(), info.getTunnelId(), null, new TestSuccessfulJob(getContext(), timeout), failureJob, selector, timeout, TEST_PRIORITY); - getContext().jobQueue().addJob(j); - } - - /** - * Get the tunnel for replies to be sent down when testing outbound tunnels - * - */ - private TunnelId getReplyTunnel() { - TunnelSelectionCriteria crit = new TunnelSelectionCriteria(); - crit.setMinimumTunnelsRequired(2); - crit.setMaximumTunnelsRequired(2); - // arbitrary priorities - crit.setAnonymityPriority(50); - crit.setLatencyPriority(50); - crit.setReliabilityPriority(50); - List tunnelIds = getContext().tunnelManager().selectInboundTunnelIds(crit); - - for (int i = 0; i < tunnelIds.size(); i++) { - TunnelId id = (TunnelId)tunnelIds.get(i); - if (id.equals(_primaryId)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Not testing a tunnel with itself [duh]"); - } else { - return id; - } - } - - _log.error("Unable to test tunnel " + _primaryId + ", since there are NO OTHER INBOUND TUNNELS to receive the ack through"); - return null; - } - - /** - * Get the tunnel to send thte message out when testing inbound tunnels - * - */ - private TunnelId getOutboundTunnel() { - TunnelSelectionCriteria crit = new TunnelSelectionCriteria(); - crit.setMinimumTunnelsRequired(2); - crit.setMaximumTunnelsRequired(2); - // arbitrary priorities - crit.setAnonymityPriority(50); - crit.setLatencyPriority(50); - crit.setReliabilityPriority(50); - List tunnelIds = getContext().tunnelManager().selectOutboundTunnelIds(crit); - - for (int i = 0; i < tunnelIds.size(); i++) { - TunnelId id = (TunnelId)tunnelIds.get(i); - if (id.equals(_primaryId)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Not testing a tunnel with itself [duh]"); - } else { - return id; - } - } - - _log.error("Unable to test tunnel " + _primaryId + ", since there are NO OTHER OUTBOUND TUNNELS to send the ack through"); - return null; - } - - private class TestFailedJob extends JobImpl { - public TestFailedJob(RouterContext enclosingContext) { - super(enclosingContext); - } - - public String getName() { return "Tunnel Test Failed"; } - public void runJob() { - if (_log.shouldLog(Log.WARN)) - _log.warn("Test of tunnel " + _primaryId.getTunnelId() - + " failed while waiting for nonce " + _nonce + ": " - + _pool.getTunnelInfo(_primaryId), getAddedBy()); - _pool.tunnelFailed(_primaryId); - if (_secondaryId != null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Secondary test of tunnel " + _secondaryId.getTunnelId() - + " failed while waiting for nonce " + _nonce + ": " - + _pool.getTunnelInfo(_secondaryId), getAddedBy()); - //_pool.tunnelFailed(_secondaryId); - } - } - } - - private class TestSuccessfulJob extends JobImpl implements ReplyJob { - private DeliveryStatusMessage _msg; - private long _timeout; - public TestSuccessfulJob(RouterContext enclosingContext, long timeout) { - super(enclosingContext); - _msg = null; - _timeout = timeout; - } - - public String getName() { return "Tunnel Test Successful"; } - public void runJob() { - long time = (getContext().clock().now() - _msg.getArrival().getTime()); - if (_log.shouldLog(Log.INFO)) - _log.info("Test of tunnel " + _primaryId+ " successfull after " - + time + "ms waiting for " + _nonce); - - if (time > _timeout) { - return; // the test failed job should already have run - } - - TunnelInfo info = _pool.getTunnelInfo(_primaryId); - if (info != null) { - TestTunnelJob.this.getContext().messageHistory().tunnelValid(info, time); - updateProfiles(info, time); - } - - info = _pool.getTunnelInfo(_secondaryId); - if (info != null) { - TestTunnelJob.this.getContext().messageHistory().tunnelValid(info, time); - updateProfiles(info, time); - } - getContext().statManager().addRateData("tunnel.testSuccessTime", time, time); - } - - private void updateProfiles(TunnelInfo info, long time) { - TunnelInfo cur = info; - while (cur != null) { - Hash peer = cur.getThisHop(); - if ( (peer != null) && (!getContext().routerHash().equals(peer)) ) - getContext().profileManager().tunnelTestSucceeded(peer, time); - cur = cur.getNextHopInfo(); - } - } - - public void setMessage(I2NPMessage message) { - _msg = (DeliveryStatusMessage)message; - } - } - - private class TestMessageSelector implements MessageSelector { - private long _id; - private long _tunnelId; - private boolean _found; - private long _expiration; - public TestMessageSelector(long id, long tunnelId, long timeoutMs) { - _id = id; - _tunnelId = tunnelId; - _found = false; - _expiration = getContext().clock().now() + timeoutMs; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("the expiration while testing tunnel " + tunnelId - + " waiting for nonce " + id + ": " + new Date(_expiration)); - } - public boolean continueMatching() { - if (!_found) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Continue matching while looking for nonce for tunnel " + _tunnelId); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Don't continue matching for tunnel " + _tunnelId + " / " + _id); - } - return !_found; - } - public long getExpiration() { - if (_expiration < getContext().clock().now()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("EXPIRED while looking for nonce " + _id + " for tunnel " + _tunnelId); - } - return _expiration; - } - public boolean isMatch(I2NPMessage message) { - if ( (message != null) && (message instanceof DeliveryStatusMessage) ) { - DeliveryStatusMessage msg = (DeliveryStatusMessage)message; - if (msg.getMessageId() == _id) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Found successful test of tunnel " + _tunnelId + " after " - + (getContext().clock().now() - msg.getArrival().getTime()) - + "ms waiting for " + _id); - _found = true; - return true; - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Found a delivery status message, but it contains nonce " - + msg.getMessageId() + " and not " + _id); - } - } else { - //_log.debug("Not a match while looking to test tunnel " + _tunnelId + " with nonce " + _id + " (" + message + ")"); - } - return false; - } - public String toString() { - StringBuffer buf = new StringBuffer(256); - buf.append(super.toString()); - buf.append(": TestMessageSelector: tunnel ").append(_tunnelId); - buf.append(" looking for ").append(_id).append(" expiring in "); - buf.append(_expiration - getContext().clock().now()); - buf.append("ms"); - return buf.toString(); - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelBuilder.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelBuilder.java deleted file mode 100644 index b7b599f81..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelBuilder.java +++ /dev/null @@ -1,404 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.data.SessionKey; -import net.i2p.data.SigningPrivateKey; -import net.i2p.data.SigningPublicKey; -import net.i2p.data.TunnelId; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.PeerSelectionCriteria; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.router.TunnelSettings; -import net.i2p.util.Log; - -class TunnelBuilder { - private Log _log; - private RouterContext _context; - - private final static long DEFAULT_TUNNEL_DURATION = 10*60*1000; // 10 minutes - /** - * Chance that the tunnel build will be 0 hop, on a PROBABILITY_LOCAL_SCALE - */ - private final static int PROBABILITY_LOCAL = -1; - private final static int PROBABILITY_LOCAL_SCALE = 10; - - public TunnelBuilder(RouterContext context) { - _context = context; - _log = context.logManager().getLog(TunnelBuilder.class); - } - - public TunnelInfo configureInboundTunnel(Destination dest, ClientTunnelSettings settings) { - return configureInboundTunnel(dest, settings, false); - } - public TunnelInfo configureInboundTunnel(Destination dest, ClientTunnelSettings settings, boolean useFake) { - boolean randFake = (_context.random().nextInt(PROBABILITY_LOCAL_SCALE) <= PROBABILITY_LOCAL); - List peerLists = null; - if (useFake || randFake) { - peerLists = new ArrayList(0); - } else { - List peerHashes = selectInboundPeers(1, settings.getDepthInbound()); - peerLists = randomizeLists(peerHashes, 1, settings.getDepthInbound()); - } - if (peerLists.size() <= 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("Configuring local inbound tunnel"); - return configureInboundTunnel(dest, settings, new ArrayList()); - } else { - List peerHashList = (List)peerLists.get(0); - return configureInboundTunnel(dest, settings, peerHashList); - } - } - - public TunnelInfo configureOutboundTunnel(ClientTunnelSettings settings) { - return configureOutboundTunnel(settings, false); - } - public TunnelInfo configureOutboundTunnel(ClientTunnelSettings settings, boolean useFake) { - boolean randFake = (_context.random().nextInt(PROBABILITY_LOCAL_SCALE) <= PROBABILITY_LOCAL); - List peerLists = null; - if (useFake || randFake) { - peerLists = new ArrayList(0); - } else { - List peerHashes = selectOutboundPeers(1, settings.getDepthOutbound()); - peerLists = randomizeLists(peerHashes, 1, settings.getDepthOutbound()); - } - if (peerLists.size() <= 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("Configuring local outbound tunnel"); - return configureOutboundTunnel(settings, new ArrayList()); - } else { - List peerHashList = (List)peerLists.get(0); - return configureOutboundTunnel(settings, peerHashList); - } - } - - /** - * Select a series of participants for the inbound tunnel, define each of - * their operating characteristics, and return them as a chain of TunnelInfo - * structures. The first TunnelInfo in each chain is the inbound gateway - * to which the lease should be attached, and the last is the local router. - * - * @return set of TunnelInfo structures, where each value is the gateway of - * a different tunnel (and these TunnelInfo structures are chained - * via getNextHopInfo()) - */ - public Set configureInboundTunnels(Destination dest, ClientTunnelSettings settings) { - return configureInboundTunnels(dest, settings, false); - } - /** - * @param useFake if true, make this tunnel include no remote peers (so it'll always succeed) - * - */ - public Set configureInboundTunnels(Destination dest, ClientTunnelSettings settings, boolean useFake) { - Set tunnels = new HashSet(); - int numIn = settings.getNumInboundTunnels(); - if (numIn <= 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("No inbound tunnels requested, but we're creating one anyway"); - numIn = 1; - } - List peerLists = null; - if (!useFake) { - List peerHashes = selectInboundPeers(numIn, settings.getDepthInbound()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Peer hashes selected: " + peerHashes.size()); - peerLists = randomizeLists(peerHashes, settings.getNumInboundTunnels(), settings.getDepthInbound()); - } else { - peerLists = new ArrayList(0); - } - if (peerLists.size() <= 0) { - for (int i = 0; i < numIn; i++) { - TunnelInfo tunnel = configureInboundTunnel(dest, settings, new ArrayList()); - tunnels.add(tunnel); - if (_log.shouldLog(Log.INFO)) - _log.info("Dummy inbound tunnel " + tunnel.getTunnelId() + " configured (" + tunnel + ")"); - } - } else { - for (Iterator iter = peerLists.iterator(); iter.hasNext();) { - List peerList = (List)iter.next(); - TunnelInfo tunnel = configureInboundTunnel(dest, settings, peerList); - tunnels.add(tunnel); - if (_log.shouldLog(Log.INFO)) - _log.info("Real inbound tunnel " + tunnel.getTunnelId() + " configured (" + tunnel + ")"); - } - } - - return tunnels; - } - - public Set configureOutboundTunnels(ClientTunnelSettings settings) { - return configureOutboundTunnels(settings, false); - } - /** - * @param useFake if true, make this tunnel include no remote peers (so it'll always succeed) - * - */ - public Set configureOutboundTunnels(ClientTunnelSettings settings, boolean useFake) { - Set tunnels = new HashSet(); - - List peerLists = null; - if (!useFake) { - List peerHashes = selectOutboundPeers(settings.getNumOutboundTunnels(), settings.getDepthOutbound()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Peer hashes selected: " + peerHashes.size()); - peerLists = randomizeLists(peerHashes, settings.getNumOutboundTunnels(), settings.getDepthOutbound()); - } else { - peerLists = new ArrayList(0); - } - if (peerLists.size() <= 0) { - for (int i = 0; i < settings.getNumOutboundTunnels(); i++) { - TunnelInfo tunnel = configureOutboundTunnel(settings, new ArrayList()); - tunnels.add(tunnel); - if (_log.shouldLog(Log.INFO)) - _log.info("Dummy outbound tunnel " + tunnel.getTunnelId() + " configured (" + tunnel + ")"); - } - } else { - for (Iterator iter = peerLists.iterator(); iter.hasNext();) { - List peerList = (List)iter.next(); - TunnelInfo tunnel = configureOutboundTunnel(settings, peerList); - tunnels.add(tunnel); - if (_log.shouldLog(Log.INFO)) - _log.info("Real outbound tunnel " + tunnel.getTunnelId() + " configured (" + tunnel + ")"); - } - } - return tunnels; - } - - private List selectInboundPeers(int numTunnels, int numPerTunnel) { - return selectPeers(numTunnels, numPerTunnel); - } - - private List selectOutboundPeers(int numTunnels, int numPerTunnel) { - return selectPeers(numTunnels, numPerTunnel); - } - - /** - * Retrieve a list of Hash structures (from RouterIdentity) for routers that - * should be used for the tunnels. A sufficient number should be retrieved so - * that there are enough for the specified numTunnels where each tunnel has numPerTunnel - * hops in it. - * - */ - private List selectPeers(int numTunnels, int numPerTunnel) { - PeerSelectionCriteria criteria = new PeerSelectionCriteria(); - int maxNeeded = numTunnels * numPerTunnel; - int minNeeded = numPerTunnel; - criteria.setMaximumRequired(maxNeeded); - criteria.setMinimumRequired(minNeeded); - criteria.setPurpose(PeerSelectionCriteria.PURPOSE_TUNNEL); - - List peers = _context.peerManager().selectPeers(criteria); - List rv = new ArrayList(peers.size()); - for (Iterator iter = peers.iterator(); iter.hasNext(); ) { - Hash peer = (Hash)iter.next(); - if (null != _context.netDb().lookupRouterInfoLocally(peer)) - rv.add(peer); - else { - if (_log.shouldLog(Log.WARN)) - _log.warn("peer manager selected a peer we don't know about - drop it"); - } - } - return rv; - } - - /** - * Take the router hashes and organize them into numTunnels lists where each - * list contains numPerTunnel hashes. - * - * @return Set of List of Hash objects, where the each list contains an ordered - * group of routers to participate in the tunnel. Note that these lists - * do NOT include the local router at the end, so numPerTunnel = 0 (and - * hence, an empty list) is a valid (albeit insecure) length - */ - private List randomizeLists(List peerHashes, int numTunnels, int numPerTunnel) { - List tunnels = new ArrayList(numTunnels); - - if (peerHashes.size() == 0) { - if (_log.shouldLog(Log.INFO)) - _log.info("No peer hashes provided"); - return tunnels; - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("# peers randomizing: " + peerHashes + " into " + numTunnels + " tunnels"); - } - - for (int i = 0; i < numTunnels; i++) { - int startOn = _context.random().nextInt(peerHashes.size()); - List peers = new ArrayList(); - for (int j = 0; j < numPerTunnel; j++) { - int k = (j + startOn) % peerHashes.size(); - Hash peer = (Hash)peerHashes.get(k); - if (!peers.contains(peer)) - peers.add(peer); - } - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel " + i + " [" + numPerTunnel + "/(" + startOn+ ")]: " + peers); - tunnels.add(peers); - } - - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnels: " + tunnels); - - return tunnels; - } - - /** - * Create a chain of TunnelInfo structures with the appropriate settings using - * the supplied routers for each hop, as well as a final hop ending with the current - * router - */ - private TunnelInfo configureInboundTunnel(Destination dest, ClientTunnelSettings settings, List peerHashList) { - SessionKey encryptionKey = _context.keyGenerator().generateSessionKey(); - Object kp[] = _context.keyGenerator().generateSigningKeypair(); - SigningPublicKey pubkey = (SigningPublicKey)kp[0]; - SigningPrivateKey privkey = (SigningPrivateKey)kp[1]; - - long duration = settings.getInboundDuration(); - if (duration <= 0) - duration = DEFAULT_TUNNEL_DURATION; - long expiration = _context.clock().now() + duration; - - TunnelSettings tunnelSettings = new TunnelSettings(_context); - tunnelSettings.setBytesPerMinuteAverage(settings.getBytesPerMinuteInboundAverage()); - tunnelSettings.setBytesPerMinutePeak(settings.getBytesPerMinuteInboundPeak()); - tunnelSettings.setDepth(peerHashList.size()+1); - tunnelSettings.setExpiration(expiration); - tunnelSettings.setIncludeDummy(settings.getIncludeDummyInbound()); - tunnelSettings.setMessagesPerMinuteAverage(settings.getMessagesPerMinuteInboundAverage()); - tunnelSettings.setMessagesPerMinutePeak(settings.getMessagesPerMinuteInboundPeak()); - tunnelSettings.setReorder(settings.getReorderInbound()); - - TunnelId id = new TunnelId(); - id.setTunnelId(_context.random().nextLong(TunnelId.MAX_ID_VALUE)); - id.setType(TunnelId.TYPE_INBOUND); - - TunnelInfo first = null; - TunnelInfo prev = null; - for (int i = 0; i < peerHashList.size(); i++) { - Hash peer = (Hash)peerHashList.get(i); - TunnelInfo cur = new TunnelInfo(_context); - cur.setThisHop(peer); - cur.setConfigurationKey(_context.keyGenerator().generateSessionKey()); - cur.setDestination(null); - if (i == 0) { - // gateway - cur.setEncryptionKey(encryptionKey); - cur.setSigningKey(privkey); - } - cur.setSettings(tunnelSettings); - cur.setTunnelId(id); - cur.setVerificationKey(pubkey); - - if (prev != null) { - prev.setNextHop(peer); - prev.setNextHopInfo(cur); - prev.setNextHopId(cur.getTunnelId()); - } else { - first = cur; - } - prev = cur; - } - - TunnelInfo last = new TunnelInfo(_context); - last.setThisHop(_context.routerHash()); - last.setDestination(dest); - last.setEncryptionKey(encryptionKey); - last.setSettings(tunnelSettings); - last.setTunnelId(id); - last.setVerificationKey(pubkey); - last.setSigningKey(privkey); - last.setConfigurationKey(_context.keyGenerator().generateSessionKey()); - - TunnelInfo cur = first; - if (cur == null) { - first = last; - } else { - while (cur.getNextHopInfo() != null) - cur = cur.getNextHopInfo(); - cur.setNextHop(last.getThisHop()); - cur.setNextHopInfo(last); - cur.setNextHopId(last.getTunnelId()); - } - - return first; - } - - - /** - * Create a chain of TunnelInfo structures with the appropriate settings using - * the supplied routers for each hop, starting with the current router - */ - private TunnelInfo configureOutboundTunnel(ClientTunnelSettings settings, List peerHashList) { - SessionKey encryptionKey = _context.keyGenerator().generateSessionKey(); - Object kp[] = _context.keyGenerator().generateSigningKeypair(); - SigningPublicKey pubkey = (SigningPublicKey)kp[0]; - SigningPrivateKey privkey = (SigningPrivateKey)kp[1]; - - long duration = settings.getInboundDuration(); // uses inbound duration for symmetry - if (duration <= 0) - duration = DEFAULT_TUNNEL_DURATION; - long expiration = _context.clock().now() + duration; - - TunnelSettings tunnelSettings = new TunnelSettings(_context); - tunnelSettings.setBytesPerMinuteAverage(settings.getBytesPerMinuteInboundAverage()); - tunnelSettings.setBytesPerMinutePeak(settings.getBytesPerMinuteInboundPeak()); - tunnelSettings.setDepth(peerHashList.size()+1); - tunnelSettings.setExpiration(expiration); - tunnelSettings.setIncludeDummy(settings.getIncludeDummyInbound()); - tunnelSettings.setMessagesPerMinuteAverage(settings.getMessagesPerMinuteInboundAverage()); - tunnelSettings.setMessagesPerMinutePeak(settings.getMessagesPerMinuteInboundPeak()); - tunnelSettings.setReorder(settings.getReorderInbound()); - - TunnelId id = new TunnelId(); - id.setTunnelId(_context.random().nextLong(TunnelId.MAX_ID_VALUE)); - id.setType(TunnelId.TYPE_OUTBOUND); - - TunnelInfo first = new TunnelInfo(_context); - first.setThisHop(_context.routerHash()); - first.setDestination(null); - first.setEncryptionKey(encryptionKey); - first.setSettings(tunnelSettings); - first.setTunnelId(id); - first.setVerificationKey(pubkey); - first.setSigningKey(privkey); - first.setConfigurationKey(_context.keyGenerator().generateSessionKey()); - - TunnelInfo prev = first; - for (int i = 0; i < peerHashList.size(); i++) { - Hash peer = (Hash)peerHashList.get(i); - TunnelInfo cur = new TunnelInfo(_context); - cur.setThisHop(peer); - cur.setConfigurationKey(_context.keyGenerator().generateSessionKey()); - cur.setDestination(null); - if (i == peerHashList.size() -1) { - // endpoint - cur.setEncryptionKey(encryptionKey); - } - cur.setSettings(tunnelSettings); - cur.setTunnelId(id); - cur.setVerificationKey(pubkey); - - prev.setNextHop(peer); - prev.setNextHopInfo(cur); - prev.setNextHopId(cur.getTunnelId()); - prev = cur; - } - - return first; - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelCreateMessageHandler.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelCreateMessageHandler.java deleted file mode 100644 index 8e13decd8..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelCreateMessageHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import net.i2p.data.Hash; -import net.i2p.data.RouterIdentity; -import net.i2p.data.i2np.I2NPMessage; -import net.i2p.data.i2np.TunnelCreateMessage; -import net.i2p.router.HandlerJobBuilder; -import net.i2p.router.Job; -import net.i2p.router.RouterContext; - -class TunnelCreateMessageHandler implements HandlerJobBuilder { - private RouterContext _context; - public TunnelCreateMessageHandler(RouterContext context) { - _context = context; - } - public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) { - return new HandleTunnelCreateMessageJob(_context, (TunnelCreateMessage)receivedMessage, from, fromHash); - } - -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelGateway.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelGateway.java deleted file mode 100644 index 1c91ed4d4..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelGateway.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import net.i2p.data.Hash; -import net.i2p.data.TunnelId; - -class TunnelGateway { - private TunnelId _tunnel; - private Hash _gateway; - public TunnelGateway(TunnelId id, Hash gateway) { - _tunnel = id; - _gateway = gateway; - } - public TunnelId getTunnelId() { return _tunnel; } - public Hash getGateway() { return _gateway; } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelPool.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelPool.java deleted file mode 100644 index abbc0a03f..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelPool.java +++ /dev/null @@ -1,794 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.data.TunnelId; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Store the data for free inbound, outbound, and client pooled tunnels, and serve - * as the central coordination point - * - */ -class TunnelPool { - private Log _log; - private RouterContext _context; - /** TunnelId --> TunnelInfo of outbound tunnels */ - private Map _outboundTunnels; - /** TunnelId --> TunnelInfo of free inbound tunnels */ - private Map _freeInboundTunnels; - /** Destination --> ClientTunnelPool */ - private Map _clientPools; - /** TunnelId --> TunnelInfo structures of non-local tunnels we're participating in */ - private Map _participatingTunnels; - /** TunnelId --> TunnelInfo of tunnels being built (but not ready yet) */ - private Map _pendingTunnels; - /** defines pool settings: # inbound / outbound, length, etc */ - private ClientTunnelSettings _poolSettings; - private TunnelPoolPersistenceHelper _persistenceHelper; - /** how long will each tunnel create take? */ - private long _tunnelCreationTimeout; - /** how many clients should we stock the pool in support of */ - private int _targetClients; - /** active or has it been shutdown? */ - private boolean _isLive; - private TunnelBuilder _tunnelBuilder; - - /** write out the current state every 60 seconds */ - private final static long WRITE_POOL_DELAY = 60*1000; - - /** allow the tunnel create timeout to be overridden, default is 60 seconds [but really slow computers should be larger] */ - public final static String TUNNEL_CREATION_TIMEOUT_PARAM = "tunnel.creationTimeoutMs"; - public final static long TUNNEL_CREATION_TIMEOUT_DEFAULT = 60*1000; - - public final static String TARGET_CLIENTS_PARAM = "router.targetClients"; - public final static int TARGET_CLIENTS_DEFAULT = 3; - - public TunnelPool(RouterContext ctx) { - _context = ctx; - _log = ctx.logManager().getLog(TunnelPool.class); - - _context.statManager().createFrequencyStat("tunnel.failFrequency", "How often do tunnels prematurely fail (after being successfully built)?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.failAfterTime", "How long do tunnels that fail prematurely last before failing?", "Tunnels", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.inboundMessagesProcessed", "How many messages does an inbound tunnel process in its lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.outboundMessagesProcessed", "How many messages does an inbound tunnel process in its lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.participatingMessagesProcessed", "How many messages does an inbound tunnel process in its lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.participatingMessagesProcessedActive", "How many messages beyond the average were processed in a more-than-average tunnel's lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.participatingBytesProcessed", "How many bytes does an inbound tunnel process in its lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("tunnel.participatingBytesProcessedActive", "How many bytes beyond the average were processed in a more-than-average tunnel's lifetime?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - - _isLive = true; - _persistenceHelper = new TunnelPoolPersistenceHelper(_context); - _tunnelBuilder = new TunnelBuilder(_context); - } - - /** - * If the tunnel is known in any way, fetch it, else return null - * - */ - public TunnelInfo getTunnelInfo(TunnelId id) { - if (!_isLive) { - if (_log.shouldLog(Log.ERROR)) - _log.error(toString() + ": Not live, unable to search for tunnel " + id); - return null; - } - if (id == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error(toString() + ": Id requested is null", new Exception("wtf, why do you want a null?")); - return null; - } - boolean typeKnown = id.getType() != TunnelId.TYPE_UNSPECIFIED; - - if ( (!typeKnown) || (id.getType() == TunnelId.TYPE_PARTICIPANT) ) { - synchronized (_participatingTunnels) { - if (_participatingTunnels.containsKey(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as a participant"); - return (TunnelInfo)_participatingTunnels.get(id); - } - } - } - if ( (!typeKnown) || (id.getType() == TunnelId.TYPE_OUTBOUND) ) { - synchronized (_outboundTunnels) { - if (_outboundTunnels.containsKey(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as outbound"); - return (TunnelInfo)_outboundTunnels.get(id); - } - } - } - if ( (!typeKnown) || (id.getType() == TunnelId.TYPE_INBOUND) ) { - synchronized (_freeInboundTunnels) { - if (_freeInboundTunnels.containsKey(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as a free inbound"); - return (TunnelInfo)_freeInboundTunnels.get(id); - } - } - } - synchronized (_pendingTunnels) { - if (_pendingTunnels.containsKey(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as a pending tunnel"); - return (TunnelInfo)_pendingTunnels.get(id); - } - } - - if ( (!typeKnown) || (id.getType() == TunnelId.TYPE_INBOUND) ) { - synchronized (_clientPools) { - for (Iterator iter = _clientPools.values().iterator(); iter.hasNext(); ) { - ClientTunnelPool pool = (ClientTunnelPool)iter.next(); - if (pool.isInboundTunnel(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as an inbound tunnel for the client " + pool.getDestination().calculateHash().toBase64()); - return pool.getInboundTunnel(id); - } else if (pool.isInactiveInboundTunnel(id)) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Found tunnel " + id + " as an inactive inbound tunnel for the client " + pool.getDestination().calculateHash().toBase64()); - return pool.getInactiveInboundTunnel(id); - } - } - } - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug(toString() + ": Did NOT find the tunnel " + id); - return null; - } - - /** - * Get the tunnelId of all tunnels we are managing (not ones we are merely - * participating in) - * - */ - public Set getManagedTunnelIds() { - if (!_isLive) return Collections.EMPTY_SET; - Set ids = new HashSet(64); - synchronized (_outboundTunnels) { - ids.addAll(_outboundTunnels.keySet()); - } - synchronized (_freeInboundTunnels) { - ids.addAll(_freeInboundTunnels.keySet()); - } - synchronized (_clientPools) { - for (Iterator iter = _clientPools.values().iterator(); iter.hasNext(); ) { - ClientTunnelPool pool = (ClientTunnelPool)iter.next(); - ids.addAll(pool.getInboundTunnelIds()); - } - } - return ids; - } - - /** - * Allocate a free tunnel for use by the destination - * - * @return true if the tunnel was allocated successfully, false if an error occurred - */ - public boolean allocateTunnel(TunnelId id, Destination dest) { - return allocateTunnel(id, getClientPool(dest)); - } - public boolean allocateTunnel(TunnelId id, ClientTunnelPool pool) { - if (!_isLive) return false; - if (pool == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error allocating tunnel " + id + " to " + pool.getDestination() + ": no pool for the client known"); - return false; - } - TunnelInfo tunnel = removeFreeTunnel(id); - if (tunnel == null) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Error allocating tunnel " + id + " to " + pool.getDestination() + ": tunnel is no longer free?"); - return false; - } - - TunnelInfo t = tunnel; - while (t != null) { - t.setDestination(pool.getDestination()); - t = t.getNextHopInfo(); - } - - pool.addInboundTunnel(tunnel); - return true; - } - - /** - * Set of tunnelIds for outbound tunnels - */ - public Set getOutboundTunnels() { - if (!_isLive) return null; - synchronized (_outboundTunnels) { - return new HashSet(_outboundTunnels.keySet()); - } - } - public int getOutboundTunnelCount() { - if (!_isLive) return 0; - synchronized (_outboundTunnels) { - return _outboundTunnels.size(); - } - } - public TunnelInfo getOutboundTunnel(TunnelId id) { - if (!_isLive) return null; - synchronized (_outboundTunnels) { - return (TunnelInfo)_outboundTunnels.get(id); - } - } - public void addOutboundTunnel(TunnelInfo tunnel) { - if (!_isLive) return; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Add outbound tunnel " + tunnel.getTunnelId()); - _context.messageHistory().tunnelJoined("outbound", tunnel); - synchronized (_outboundTunnels) { - _outboundTunnels.put(tunnel.getTunnelId(), tunnel); - } - synchronized (_pendingTunnels) { - _pendingTunnels.remove(tunnel.getTunnelId()); - } - } - public void removeOutboundTunnel(TunnelId id) { - if (!_isLive) return; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Removing outbound tunnel " + id); - int remaining = 0; - TunnelInfo info = null; - synchronized (_outboundTunnels) { - info = (TunnelInfo)_outboundTunnels.remove(id); - remaining = _outboundTunnels.size(); - } - addTunnelStats(id, info); - if (remaining <= 0) { - buildFakeTunnels(); - } - } - - /** - * Set of tunnelIds that this router has available for consumption - */ - public Set getFreeTunnels() { - if (!_isLive) return null; - synchronized (_freeInboundTunnels) { - return new HashSet(_freeInboundTunnels.keySet()); - } - } - public int getFreeTunnelCount() { - if (!_isLive) return 0; - synchronized (_freeInboundTunnels) { - return _freeInboundTunnels.size(); - } - } - public TunnelInfo getFreeTunnel(TunnelId id) { - if (!_isLive) return null; - synchronized (_freeInboundTunnels) { - return (TunnelInfo)_freeInboundTunnels.get(id); - } - } - public void addFreeTunnel(TunnelInfo tunnel) { - if (!_isLive) return; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Add free inbound tunnel " + tunnel.getTunnelId()); - _context.messageHistory().tunnelJoined("free inbound", tunnel); - synchronized (_freeInboundTunnels) { - _freeInboundTunnels.put(tunnel.getTunnelId(), tunnel); - } - synchronized (_pendingTunnels) { - _pendingTunnels.remove(tunnel.getTunnelId()); - } - if (tunnel.getDestination() != null) { - // this one was custom built, so tack 'er on directly - allocateTunnel(tunnel.getTunnelId(), tunnel.getDestination()); - } - } - public TunnelInfo removeFreeTunnel(TunnelId id) { - if (!_isLive) return null; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Removing free inbound tunnel " + id); - int remaining = 0; - TunnelInfo rv = null; - synchronized (_freeInboundTunnels) { - rv = (TunnelInfo)_freeInboundTunnels.remove(id); - remaining = _freeInboundTunnels.size(); - } - addTunnelStats(id, rv); - if (remaining <= 0) - buildFakeTunnels(); - return rv; - } - - /** - * set of tunnelIds that this router is participating in (but not managing) - */ - public Set getParticipatingTunnels() { - if (!_isLive) return null; - synchronized (_participatingTunnels) { - return new HashSet(_participatingTunnels.keySet()); - } - } - public int getParticipatingTunnelCount() { - if (!_isLive) return 0; - synchronized (_participatingTunnels) { - return _participatingTunnels.size(); - } - } - public TunnelInfo getParticipatingTunnel(TunnelId id) { - if (!_isLive) return null; - synchronized (_participatingTunnels) { - return (TunnelInfo)_participatingTunnels.get(id); - } - } - - public boolean addParticipatingTunnel(TunnelInfo tunnel) { - if (!_isLive) return false; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Add participating tunnel " + tunnel.getTunnelId()); - _context.messageHistory().tunnelJoined("participant", tunnel); - synchronized (_participatingTunnels) { - if (_participatingTunnels.containsKey(tunnel.getTunnelId())) { - return false; - } else { - _participatingTunnels.put(tunnel.getTunnelId(), tunnel); - tunnel.setIsReady(true); - return true; - } - } - } - - public TunnelInfo removeParticipatingTunnel(TunnelId id) { - if (!_isLive) return null; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Removing participating tunnel " + id); - TunnelInfo info = null; - synchronized (_participatingTunnels) { - info = (TunnelInfo)_participatingTunnels.remove(id); - } - addTunnelStats(id, info); - return info; - } - - /** - * Set of Destinations for clients currently being managed - * - */ - public Set getClientPools() { - if (!_isLive) return null; - synchronized (_clientPools) { - return new HashSet(_clientPools.keySet()); - } - } - - /** - * Create and start up a client pool for the destination - * - */ - public void createClientPool(Destination dest, ClientTunnelSettings settings) { - if (!_isLive) return; - ClientTunnelPool pool = null; - synchronized (_clientPools) { - if (_clientPools.containsKey(dest)) { - pool = (ClientTunnelPool)_clientPools.get(dest); - if (_log.shouldLog(Log.INFO)) - _log.info("Reusing an existing client tunnel pool for " + dest.calculateHash()); - } else { - pool = new ClientTunnelPool(_context, dest, settings, this); - if (_log.shouldLog(Log.INFO)) - _log.info("New client tunnel pool created for " + dest.calculateHash()); - _clientPools.put(dest, pool); - } - } - pool.startPool(); - } - - ClientTunnelPool addClientPool(ClientTunnelPool pool) { - if (!_isLive) return null; - ClientTunnelPool old = null; - - if (_log.shouldLog(Log.INFO)) - _log.info("Client tunnel pool added for " + pool.getDestination().calculateHash()); - - synchronized (_clientPools) { - old = (ClientTunnelPool)_clientPools.put(pool.getDestination(), pool); - } - return old; - } - public ClientTunnelPool getClientPool(Destination dest) { - if (!_isLive) return null; - synchronized (_clientPools) { - return (ClientTunnelPool)_clientPools.get(dest); - } - } - - public void removeClientPool(Destination dest) { - if (!_isLive) return; - if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing client tunnel pool for " + dest.calculateHash()); - ClientTunnelPool pool = null; - synchronized (_clientPools) { - pool = (ClientTunnelPool)_clientPools.remove(dest); - } - if (pool != null) - pool.stopPool(); - } - - public Set getPendingTunnels() { - if (!_isLive) return null; - synchronized (_pendingTunnels) { - return new HashSet(_pendingTunnels.keySet()); - } - } - public TunnelInfo getPendingTunnel(TunnelId id) { - if (!_isLive) return null; - synchronized (_pendingTunnels) { - return (TunnelInfo)_pendingTunnels.get(id); - } - } - public void addPendingTunnel(TunnelInfo info) { - if (!_isLive) return; - _context.messageHistory().tunnelJoined("pending", info); - synchronized (_pendingTunnels) { - _pendingTunnels.put(info.getTunnelId(), info); - } - } - public void removePendingTunnel(TunnelId id) { - if (!_isLive) return; - if (_log.shouldLog(Log.DEBUG)) _log.debug(toString() + ": Removing pending tunnel " + id); - TunnelInfo info = null; - synchronized (_pendingTunnels) { - info = (TunnelInfo)_pendingTunnels.remove(id); - } - addTunnelStats(id, info); - } - - /** fetch the settings for the pool (tunnel settings and quantities) */ - public ClientTunnelSettings getPoolSettings() { return _poolSettings; } - public void setPoolSettings(ClientTunnelSettings settings) { _poolSettings = settings; } - - /** how many clients the router should expect to handle at once (so it can build sufficient tunnels */ - public int getTargetClients() { return _targetClients; } - public void setTargetClients(int numConcurrentClients) { _targetClients = numConcurrentClients; } - - /** max time for any tunnel creation to take (in milliseconds) */ - public long getTunnelCreationTimeout() { return _tunnelCreationTimeout; } - public void setTunnelCreationTimeout(long timeout) { _tunnelCreationTimeout = timeout; } - - /** determine the number of hops in the longest tunnel we have */ - public int getLongestTunnelLength() { - int max = 0; - synchronized (_freeInboundTunnels) { - for (Iterator iter = _freeInboundTunnels.values().iterator(); iter.hasNext(); ) { - TunnelInfo info = (TunnelInfo)iter.next(); - int len = info.getLength(); - if (len > max) - max = len; - } - } - return max; - } - - /** - * Shit has hit the fan, so lets build a pair of failsafe 0-hop tunnels - one inbound, - * and one outbound. This method blocks until those tunnels are built, and does not - * make use of the JobQueue. - * - */ - public void buildFakeTunnels() { - buildFakeTunnels(false); - } - public void buildFakeTunnels(boolean force) { - if (force || getFreeValidTunnelCount() < 3) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Running low on valid inbound tunnels, building another"); - TunnelInfo inTunnelGateway = _tunnelBuilder.configureInboundTunnel(null, getPoolSettings(), true); - RequestTunnelJob inReqJob = new RequestTunnelJob(_context, this, inTunnelGateway, true, getTunnelCreationTimeout()); - inReqJob.runJob(); - } - if (force || getOutboundValidTunnelCount() < 3) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Running low on valid outbound tunnels, building another"); - TunnelInfo outTunnelGateway = _tunnelBuilder.configureOutboundTunnel(getPoolSettings(), true); - RequestTunnelJob outReqJob = new RequestTunnelJob(_context, this, outTunnelGateway, false, getTunnelCreationTimeout()); - outReqJob.runJob(); - } - } - - private int getFreeValidTunnelCount() { - int found = 0; - Set ids = getFreeTunnels(); - long mustExpireAfter = _context.clock().now(); - - for (Iterator iter = ids.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = getFreeTunnel(id); - if ( (info != null) && (info.getIsReady()) ) { - if (info.getSettings().getExpiration() > mustExpireAfter) { - if (info.getDestination() == null) { - found++; - } - } - } - } - return found; - } - - private int getOutboundValidTunnelCount() { - int found = 0; - Set ids = getOutboundTunnels(); - long mustExpireAfter = _context.clock().now(); - - for (Iterator iter = ids.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = getOutboundTunnel(id); - if ( (info != null) && (info.getIsReady()) ) { - if (info.getSettings().getExpiration() > mustExpireAfter) { - found++; - } - } - } - return found; - } - - private static final int MAX_FAILURES_PER_TUNNEL = 0; - public static final String PROP_MAX_TUNNEL_FAILURES = "tunnel.maxTunnelFailures"; - - private int getMaxTunnelFailures() { - String max = _context.getProperty(PROP_MAX_TUNNEL_FAILURES); - if (max != null) { - try { - return Integer.parseInt(max); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Max tunnel failures property is invalid [" + max + "]"); - } - } - return MAX_FAILURES_PER_TUNNEL; - } - - public void tunnelFailed(TunnelId id) { - if (!_isLive) return; - TunnelInfo info = getTunnelInfo(id); - if (info == null) - return; - int failures = info.incrementFailures(); - if (failures <= getMaxTunnelFailures()) { - if (_log.shouldLog(Log.INFO)) - _log.info("Tunnel " + id + " failure " + failures + ", but not fatal yet"); - return; - } - - if (_log.shouldLog(Log.WARN)) - _log.warn("Tunnel " + id + " marked as not ready, since it /failed/: " + info.toString(), new Exception("Failed tunnel")); - _context.messageHistory().tunnelFailed(info.getTunnelId()); - info.setIsReady(false); - Hash us = _context.routerHash(); - long lifetime = _context.clock().now() - info.getCreated(); - while (info != null) { - if (!info.getThisHop().equals(us)) { - _context.profileManager().tunnelFailed(info.getThisHop()); - } - info = info.getNextHopInfo(); - } - _context.statManager().addRateData("tunnel.failAfterTime", lifetime, lifetime); - _context.statManager().updateFrequency("tunnel.failFrequency"); - buildFakeTunnels(); - } - - public void startup() { - if (_log.shouldLog(Log.INFO)) _log.info("Starting up tunnel pool"); - _isLive = true; - _outboundTunnels = new HashMap(16); - _freeInboundTunnels = new HashMap(16); - _clientPools = new HashMap(8); - _participatingTunnels = new HashMap(64); - _pendingTunnels = new HashMap(8); - _poolSettings = createPoolSettings(); - _persistenceHelper.loadPool(this); - _tunnelCreationTimeout = -1; - try { - String str = _context.router().getConfigSetting(TUNNEL_CREATION_TIMEOUT_PARAM); - _tunnelCreationTimeout = Long.parseLong(str); - } catch (Throwable t) { - _tunnelCreationTimeout = TUNNEL_CREATION_TIMEOUT_DEFAULT; - } - _targetClients = TARGET_CLIENTS_DEFAULT; - try { - String str = _context.router().getConfigSetting(TARGET_CLIENTS_PARAM); - _targetClients = Integer.parseInt(str); - } catch (Throwable t) { - _targetClients = TARGET_CLIENTS_DEFAULT; - } - buildFakeTunnels(); - //_context.jobQueue().addJob(new WritePoolJob()); - _context.jobQueue().addJob(new TunnelPoolManagerJob(_context, this)); - _context.jobQueue().addJob(new TunnelPoolExpirationJob(_context, this)); - } - - public void restart() { - try { - String str = _context.router().getConfigSetting(TUNNEL_CREATION_TIMEOUT_PARAM); - _tunnelCreationTimeout = Long.parseLong(str); - } catch (Throwable t) { - _tunnelCreationTimeout = TUNNEL_CREATION_TIMEOUT_DEFAULT; - } - _targetClients = TARGET_CLIENTS_DEFAULT; - try { - String str = _context.router().getConfigSetting(TARGET_CLIENTS_PARAM); - _targetClients = Integer.parseInt(str); - } catch (Throwable t) { - _targetClients = TARGET_CLIENTS_DEFAULT; - } - } - - public void shutdown() { - if (_log.shouldLog(Log.INFO)) _log.info("Shutting down tunnel pool"); - //if (_persistenceHelper != null) - // _persistenceHelper.writePool(this); - _isLive = false; // the subjobs [should] check getIsLive() on each run - _outboundTunnels = null; - _freeInboundTunnels = null; - _clientPools = null; - _participatingTunnels = null; - _poolSettings = null; - _persistenceHelper = null; - _tunnelCreationTimeout = -1; - } - - public boolean isLive() { return _isLive; } - - void addTunnelStats(TunnelId id, TunnelInfo info) { - if ( (info != null) && (id != null) ) { - switch (id.getType()) { - case TunnelId.TYPE_INBOUND: - _context.statManager().addRateData("tunnel.inboundMessagesProcessed", - info.getMessagesProcessed(), - info.getSettings().getExpiration() - - info.getSettings().getCreated()); - break; - case TunnelId.TYPE_OUTBOUND: - _context.statManager().addRateData("tunnel.outboundMessagesProcessed", - info.getMessagesProcessed(), - info.getSettings().getExpiration() - - info.getSettings().getCreated()); - break; - case TunnelId.TYPE_PARTICIPANT: - long numMsgs = info.getMessagesProcessed(); - long lastAvg = (long)_context.statManager().getRate("tunnel.participatingMessagesProcessed").getRate(10*60*1000l).getAverageValue(); - _context.statManager().addRateData("tunnel.participatingMessagesProcessed", - numMsgs, - info.getSettings().getExpiration() - - info.getSettings().getCreated()); - if (numMsgs > lastAvg) - _context.statManager().addRateData("tunnel.participatingMessagesProcessedActive", - numMsgs-lastAvg, - info.getSettings().getExpiration() - - info.getSettings().getCreated()); - long numBytes = info.getBytesProcessed(); - lastAvg = (long)_context.statManager().getRate("tunnel.participatingBytesProcessed").getRate(10*60*1000l).getAverageValue(); - _context.statManager().addRateData("tunnel.participatingBytesProcessed", numBytes, numMsgs); - if (numBytes > lastAvg) - _context.statManager().addRateData("tunnel.participatingBytesProcessedActive", numBytes-lastAvg, numMsgs); - break; - case TunnelId.TYPE_UNSPECIFIED: - default: - break; - } - } - } - - private ClientTunnelSettings createPoolSettings() { - ClientTunnelSettings settings = new ClientTunnelSettings(); - settings.readFromProperties(_context.router().getConfigMap()); - return settings; - } - - public void renderStatusHTML(Writer out) throws IOException { - if (!_isLive) return; - out.write("

Tunnel Pool

\n"); - StringBuffer buf = new StringBuffer(4096); - renderTunnels(out, buf, "Free inbound tunnels", getFreeTunnels()); - renderTunnels(out, buf, "Outbound tunnels", getOutboundTunnels()); - renderTunnels(out, buf, "Participating tunnels", getParticipatingTunnels()); - for (Iterator iter = getClientPools().iterator(); iter.hasNext(); ) { - Destination dest = (Destination)iter.next(); - ClientTunnelPool pool = getClientPool(dest); - renderTunnels(out, buf, "Inbound tunnels for " + dest.calculateHash() + " - (still connected? " + (!pool.isStopped()) + ")", pool.getInboundTunnelIds()); - } - out.flush(); - } - - private void renderTunnels(Writer out, StringBuffer buf, String msg, Set tunnelIds) throws IOException { - buf.append("").append(msg).append(": (").append(tunnelIds.size()).append(" tunnels)\n"); - } - - private final void renderTunnel(Writer out, StringBuffer buf, TunnelId id, TunnelInfo tunnel) throws IOException { - buf.setLength(0); - if (tunnel == null) { - buf.append("
  • Tunnel: ").append(id.getTunnelId()).append(" is not known
  • \n"); - } else { - buf.append("
  • Tunnel: ").append(tunnel.getTunnelId()).append("
  • ");
    -            buf.append("\n\tStyle: ").append(getStyle(id));
    -            buf.append("\n\tReady? ").append(tunnel.getIsReady());
    -            buf.append("\n\tMessages processed: ").append(tunnel.getMessagesProcessed());
    -            long timeSinceTest = _context.clock().now() - tunnel.getLastTested();
    -            if (timeSinceTest < 60*60*1000) 
    -                buf.append("\n\tLast tested: ").append(timeSinceTest/1000).append(" seconds ago");
    -            else
    -                buf.append("\n\tLast tested: never");
    -            buf.append("\n\tDest? ").append(getDestination(tunnel));
    -            if (tunnel.getSettings() != null)
    -                buf.append("\n\tExpiration: ").append(new Date(tunnel.getSettings().getExpiration()));
    -            else
    -                buf.append("\n\tExpiration: none");
    -                
    -            buf.append("\n\tStart router: ").append(tunnel.getThisHop().toBase64()).append("\n");
    -            TunnelInfo t = tunnel.getNextHopInfo();
    -            if (t != null) {
    -                int hop = 1;
    -                while (t != null) {
    -                    buf.append("\tHop ").append(hop).append(": ").append(t.getThisHop().toBase64()).append("\n");
    -                    t = t.getNextHopInfo();
    -                    hop++;
    -                }
    -            } else {
    -                if (tunnel.getNextHop() != null)
    -                    buf.append("\tNext: ").append(tunnel.getNextHop().toBase64()).append("\n");
    -            }
    -
    -            buf.append("\n
    "); - } - out.write(buf.toString()); - buf.setLength(0); - } - - private final static String getStyle(TunnelId id) { - switch (id.getType()) { - case TunnelId.TYPE_INBOUND: - return "Inbound"; - case TunnelId.TYPE_OUTBOUND: - return "Outbound"; - case TunnelId.TYPE_PARTICIPANT: - return "Participant"; - case TunnelId.TYPE_UNSPECIFIED: - return "Unspecified"; - default: - return "Other! - " + id.getType(); - } - } - - private final static String getDestination(TunnelInfo info) { - while (info != null) { - if (info.getDestination() != null) - return info.getDestination().calculateHash().toString(); - else - info = info.getNextHopInfo(); - } - return "none"; - } - - /** - * This job instructs the troops to invade mars with a spork. - */ - private class WritePoolJob extends JobImpl { - public WritePoolJob() { - super(TunnelPool.this._context); - getTiming().setStartAfter(TunnelPool.this._context.clock().now() + WRITE_POOL_DELAY); - } - public String getName() { return "Write Out Tunnel Pool"; } - public void runJob() { - if (!isLive()) - return; - _persistenceHelper.writePool(TunnelPool.this); - requeue(WRITE_POOL_DELAY); - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolExpirationJob.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolExpirationJob.java deleted file mode 100644 index 3bb867fb8..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolExpirationJob.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.Date; -import java.util.Iterator; - -import net.i2p.data.TunnelId; -import net.i2p.router.JobImpl; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Periodically go through all of the tunnels not assigned to a client and mark - * them as no longer ready and/or drop them (as appropriate) - * - */ -class TunnelPoolExpirationJob extends JobImpl { - private Log _log; - private TunnelPool _pool; - - /** expire tunnels as necessary every 30 seconds */ - private final static long EXPIRE_POOL_DELAY = 30*1000; - - /** - * don't hard expire a tunnel until its later than expiration + buffer - */ - private final static long EXPIRE_BUFFER = 30*1000; - - public TunnelPoolExpirationJob(RouterContext ctx, TunnelPool pool) { - super(ctx); - _log = ctx.logManager().getLog(TunnelPoolExpirationJob.class); - _pool = pool; - getTiming().setStartAfter(getContext().clock().now() + EXPIRE_POOL_DELAY); - } - public String getName() { return "Expire Pooled Tunnels"; } - public void runJob() { - if (!_pool.isLive()) - return; - expireFree(); - expireOutbound(); - expireParticipants(); - expirePending(); - requeue(EXPIRE_POOL_DELAY); - } - - /** - * Drop all pooled free tunnels that are expired or are close enough to - * being expired that allocating them to a client would suck. - * - */ - public void expireFree() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getFreeTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getFreeTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring free inbound tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) - + "] (expire = " + new Date(expire) + ")"); - _pool.removeFreeTunnel(id); - } else if (info.getSettings().getExpiration() < now) { - if (_log.shouldLog(Log.INFO)) - _log.info("It is past the expiration for free inbound tunnel " + id - + " but not yet the buffer, mark it as no longer ready"); - info.setIsReady(false); - } - } - } - } - - /** - * Drop all pooled outbound tunnels that are expired - * - */ - public void expireOutbound() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getOutboundTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getOutboundTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring outbound tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) + "]"); - _pool.removeOutboundTunnel(id); - } else if (info.getSettings().getExpiration() < now) { - if (_log.shouldLog(Log.INFO)) - _log.info("It is past the expiration for outbound tunnel " + id - + " but not yet the buffer, mark it as no longer ready"); - info.setIsReady(false); - } - } - } - } - - /** - * Drop all tunnels we are participating in (but not managing) that are expired - * - */ - public void expireParticipants() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getParticipatingTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getParticipatingTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring participation in tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) + "]"); - _pool.removeParticipatingTunnel(id); - } - } - } - } - - /** - * Drop all tunnels that were in the process of being built, but expired before being handled - * - */ - public void expirePending() { - long now = getContext().clock().now(); - long expire = now - EXPIRE_BUFFER - Router.CLOCK_FUDGE_FACTOR; - - for (Iterator iter = _pool.getPendingTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getPendingTunnel(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() < expire) { - if (_log.shouldLog(Log.INFO)) - _log.info("Expiring pending tunnel " + id + " [" - + new Date(info.getSettings().getExpiration()) + "]"); - _pool.removePendingTunnel(id); - } - } - } - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolManagerJob.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolManagerJob.java deleted file mode 100644 index a0c30e85a..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolManagerJob.java +++ /dev/null @@ -1,195 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.util.Iterator; -import java.util.Set; - -import net.i2p.data.TunnelId; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Request new tunnels to be created if insufficient free inbound tunnels or - * valid outbound tunnels exist. - * - */ -class TunnelPoolManagerJob extends JobImpl { - private Log _log; - private TunnelPool _pool; - - /** - * How frequently to check the pool (and fire appropriate refill jobs) - * - */ - private final static long POOL_CHECK_DELAY = 30*1000; - - /** - * treat tunnels that are going to expire in the next minute as pretty much - * expired (for the purpose of building new ones) - */ - private final static long EXPIRE_FUDGE_PERIOD = 60*1000; - - public TunnelPoolManagerJob(RouterContext ctx, TunnelPool pool) { - super(ctx); - _log = ctx.logManager().getLog(TunnelPoolManagerJob.class); - _pool = pool; - } - - public String getName() { return "Manage Tunnel Pool"; } - public void runJob() { - try { - if (!_pool.isLive()) - return; - - boolean built = false; - - ClientTunnelSettings settings = new ClientTunnelSettings(); - settings.readFromProperties(getContext().router().getConfigMap()); - _pool.setPoolSettings(settings); - - try { - String str = getContext().router().getConfigSetting(TunnelPool.TARGET_CLIENTS_PARAM); - int clients = Integer.parseInt(str); - _pool.setTargetClients(clients); - } catch (NumberFormatException nfe) { - // ignore - } - - int targetClients = _pool.getTargetClients(); - int targetInboundTunnels = targetClients*_pool.getPoolSettings().getNumInboundTunnels() + 1; - int targetOutboundTunnels = targetClients*_pool.getPoolSettings().getNumOutboundTunnels() + 1; - - int curFreeInboundTunnels = getFreeTunnelCount(); - if (curFreeInboundTunnels < targetInboundTunnels) { - if (_log.shouldLog(Log.INFO)) - _log.info("Insufficient free inbound tunnels (" + curFreeInboundTunnels + ", not " - + targetInboundTunnels + "), requesting more"); - requestInboundTunnels(2); - //requestFakeInboundTunnels(1); - built = true; - } else { - // 10% chance of building a new tunnel - if (getContext().random().nextInt(10) > 0) { - // all good, no need for more inbound tunnels - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sufficient inbound tunnels (" + curFreeInboundTunnels + ")"); - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Building another inbound tunnel, cuz tunnels r k00l"); - requestInboundTunnels(1); - built = true; - } - } - - int curOutboundTunnels = getOutboundTunnelCount(); - if (curOutboundTunnels < targetOutboundTunnels) { - if (_log.shouldLog(Log.INFO)) - _log.info("Insufficient outbound tunnels (" + curOutboundTunnels + ", not " - + targetOutboundTunnels + "), requesting more"); - requestOutboundTunnels(2); - //requestFakeOutboundTunnels(1); - built = true; - } else { - // 10% chance of building a new tunnel - if (getContext().random().nextInt(10) > 0) { - // all good, no need for more outbound tunnels - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sufficient outbound tunnels (" + curOutboundTunnels + ")"); - } else { - if (_log.shouldLog(Log.INFO)) - _log.info("Building another outbound tunnel, since gravity still works"); - requestOutboundTunnels(1); - built = true; - } - } - - _pool.buildFakeTunnels(); - } catch (Throwable t) { - _log.log(Log.CRIT, "Unhandled exception managing the tunnel pool", t); - } - - requeue(POOL_CHECK_DELAY); - } - - /** - * How many free inbound tunnels are available for use (safely) - * - */ - private int getFreeTunnelCount() { - Set freeTunnels = _pool.getFreeTunnels(); - int free = 0; - int tooShort = 0; - int minLength = _pool.getPoolSettings().getDepthInbound(); - long mustExpireAfter = getContext().clock().now() + EXPIRE_FUDGE_PERIOD; - for (Iterator iter = freeTunnels.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getFreeTunnel(id); - if ( (info != null) && (info.getIsReady()) ) { - if (info.getSettings().getExpiration() > mustExpireAfter) { - if (info.getLength() >= minLength) { - if (info.getDestination() == null) { - free++; - } else { - // already alloc'ed - _log.error("Why is a free inbound tunnel allocated to a destination? [" - + info.getTunnelId().getTunnelId() + " to " - + info.getDestination().toBase64() + "]"); - } - } else { - // its valid, sure, but its not long enough *cough* - - // for the moment we'll keep these around so that we can use them - // for tunnel management and db messages, rather than force all - // tunnels to be the 2+ hop length as required for clients - tooShort++; // free++; - } - } else { - _log.info("Inbound tunnel " + id + " is expiring in the upcoming period, consider it not-free"); - } - } - } - if (free <= 0) { - if (_log.shouldLog(Log.WARN)) - _log.warn("No free tunnels that are long enough, but there are " + tooShort + " shorter ones"); - return tooShort; - } else { - return free; - } - } - - /** - * How many outbound tunnels are available for use (safely) - * - */ - private int getOutboundTunnelCount() { - Set outboundTunnels = _pool.getOutboundTunnels(); - int outbound = 0; - long mustExpireAfter = getContext().clock().now() + EXPIRE_FUDGE_PERIOD; - for (Iterator iter = outboundTunnels.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getOutboundTunnel(id); - if ( (info != null) && (info.getIsReady()) ) { - if (info.getSettings().getExpiration() > mustExpireAfter) { - outbound++; - } else { - _log.info("Outbound tunnel " + id + " is expiring in the upcoming period, consider it not-free"); - } - } - } - return outbound; - } - - private void requestInboundTunnels(int numTunnelsToRequest) { - _log.info("Requesting " + numTunnelsToRequest + " inbound tunnels"); - for (int i = 0; i < numTunnelsToRequest; i++) - getContext().jobQueue().addJob(new RequestInboundTunnelJob(getContext(), _pool, false)); - } - - private void requestOutboundTunnels(int numTunnelsToRequest) { - _log.info("Requesting " + numTunnelsToRequest + " outbound tunnels"); - for (int i = 0; i < numTunnelsToRequest; i++) - getContext().jobQueue().addJob(new RequestOutboundTunnelJob(getContext(), _pool, false)); - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolPersistenceHelper.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolPersistenceHelper.java deleted file mode 100644 index f11222a38..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelPoolPersistenceHelper.java +++ /dev/null @@ -1,207 +0,0 @@ -package net.i2p.router.tunnelmanager; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Properties; - -import net.i2p.data.DataFormatException; -import net.i2p.data.DataHelper; -import net.i2p.data.Destination; -import net.i2p.data.TunnelId; -import net.i2p.router.ClientTunnelSettings; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Handle all of the load / store of the tunnel pool (including any contained - * client tunnel pools) - * - */ -class TunnelPoolPersistenceHelper { - private Log _log; - private RouterContext _context; - - public final static String PARAM_TUNNEL_POOL_FILE = "router.tunnelPoolFile"; - public final static String DEFAULT_TUNNEL_POOL_FILE = "tunnelPool.dat"; - - public TunnelPoolPersistenceHelper(RouterContext ctx) { - _context = ctx; - _log = ctx.logManager().getLog(TunnelPoolPersistenceHelper.class); - } - - public void writePool(TunnelPool pool) { - File f = getTunnelPoolFile(); - writePool(pool, f); - } - public void writePool(TunnelPool pool, File f) { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(f); - DataHelper.writeLong(fos, 2, pool.getFreeTunnelCount()); - for (Iterator iter = pool.getFreeTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getFreeTunnel(id); - if (info != null) - info.writeBytes(fos); - } - DataHelper.writeLong(fos, 2, pool.getOutboundTunnelCount()); - for (Iterator iter = pool.getOutboundTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getOutboundTunnel(id); - if (info != null) - info.writeBytes(fos); - } - DataHelper.writeLong(fos, 2, pool.getParticipatingTunnels().size()); - for (Iterator iter = pool.getParticipatingTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getParticipatingTunnel(id); - if (info != null) - info.writeBytes(fos); - } - DataHelper.writeLong(fos, 2, pool.getPendingTunnels().size()); - for (Iterator iter = pool.getPendingTunnels().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getPendingTunnel(id); - if (info != null) - info.writeBytes(fos); - } - DataHelper.writeLong(fos, 2, pool.getClientPools().size()); - for (Iterator iter = pool.getClientPools().iterator(); iter.hasNext(); ) { - Destination dest = (Destination)iter.next(); - ClientTunnelPool cpool = (ClientTunnelPool)pool.getClientPool(dest); - writeClientPool(fos, cpool); - } - fos.flush(); - } catch (IOException ioe) { - _log.error("Error writing tunnel pool at " + f.getName(), ioe); - } catch (DataFormatException dfe) { - _log.error("Error formatting tunnels at " + f.getName(), dfe); - } finally { - if (fos != null) try { fos.close(); } catch (IOException ioe) {} - _log.debug("Tunnel pool state written to " + f.getName()); - } - } - - private void writeClientPool(FileOutputStream fos, ClientTunnelPool pool) throws IOException, DataFormatException { - pool.getDestination().writeBytes(fos); - Properties props = new Properties(); - pool.getClientSettings().writeToProperties(props); - DataHelper.writeProperties(fos, props); - DataHelper.writeLong(fos, 2, pool.getInboundTunnelIds().size()); - for (Iterator iter = pool.getInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getInboundTunnel(id); - if (info != null) - info.writeBytes(fos); - } - DataHelper.writeLong(fos, 2, pool.getInactiveInboundTunnelIds().size()); - for (Iterator iter = pool.getInactiveInboundTunnelIds().iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = pool.getInactiveInboundTunnel(id); - if (info != null) - info.writeBytes(fos); - } - } - - /** - * Load up the tunnels from disk, adding as appropriate to the TunnelPool - */ - public void loadPool(TunnelPool pool) { - File f = getTunnelPoolFile(); - loadPool(pool, f); - } - - public void loadPool(TunnelPool pool, File f) { - if (!f.exists()) return; - FileInputStream fin = null; - try { - fin = new FileInputStream(f); - int numFree = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numFree; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - pool.addFreeTunnel(info); - } - int numOut = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numOut; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - pool.addOutboundTunnel(info); - } - int numParticipating = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numParticipating; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - pool.addParticipatingTunnel(info); - } - int numPending = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numPending; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - pool.addPendingTunnel(info); - } - int numClients = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numClients; i++) { - readClientPool(fin, pool); - } - } catch (IOException ioe) { - _log.error("Error reading tunnel pool from " + f.getName(), ioe); - } catch (DataFormatException dfe) { - _log.error("Error formatting tunnels from " + f.getName(), dfe); - } finally { - if (fin != null) try { fin.close(); } catch (IOException ioe) {} - _log.debug("Tunnel pool state written to " + f.getName()); - } - } - - - private void readClientPool(FileInputStream fin, TunnelPool pool) throws IOException, DataFormatException { - Destination dest = new Destination(); - dest.readBytes(fin); - ClientTunnelSettings settings = new ClientTunnelSettings(); - Properties props = DataHelper.readProperties(fin); - settings.readFromProperties(props); - HashSet activeTunnels = new HashSet(); - int numActiveTunnels = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numActiveTunnels; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - activeTunnels.add(info); - } - HashSet inactiveTunnels = new HashSet(); - int numInactiveTunnels = (int)DataHelper.readLong(fin, 2); - for (int i = 0; i < numInactiveTunnels; i++) { - TunnelInfo info = new TunnelInfo(_context); - info.readBytes(fin); - inactiveTunnels.add(info); - } - - ClientTunnelPool cpool = new ClientTunnelPool(_context, dest, settings, pool); - cpool.setActiveTunnels(activeTunnels); - cpool.setInactiveTunnels(inactiveTunnels); - pool.addClientPool(cpool); - cpool.startPool(); - } - - - /** - * Retrieve the file the pool should be persisted in - * - */ - private File getTunnelPoolFile() { - String filename = null; - - String str = _context.router().getConfigSetting(PARAM_TUNNEL_POOL_FILE); - if ( (str != null) && (str.trim().length() > 0) ) - filename = str; - else - filename = DEFAULT_TUNNEL_POOL_FILE; - - return new File(filename); - } -} diff --git a/router/java/src/net/i2p/router/tunnelmanager/TunnelTestManager.java b/router/java/src/net/i2p/router/tunnelmanager/TunnelTestManager.java deleted file mode 100644 index bd4df87f4..000000000 --- a/router/java/src/net/i2p/router/tunnelmanager/TunnelTestManager.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.i2p.router.tunnelmanager; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import net.i2p.data.TunnelId; -import net.i2p.router.JobImpl; -import net.i2p.router.RouterContext; -import net.i2p.router.TunnelInfo; -import net.i2p.util.Log; - -/** - * Manage the testing for free, outbound, and active inbound client tunnels - * - */ -class TunnelTestManager { - private Log _log; - private RouterContext _context; - private TunnelPool _pool; - private boolean _stopTesting; - - /** dont test any particular tunnel more than once a minute */ - private final static long MINIMUM_RETEST_DELAY = 60*1000; - - public TunnelTestManager(RouterContext ctx, TunnelPool pool) { - _context = ctx; - _log = ctx.logManager().getLog(TunnelTestManager.class); - ctx.statManager().createRateStat("tunnel.testSuccessTime", "How long do successful tunnel tests take?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); - _pool = pool; - _stopTesting = false; - _context.jobQueue().addJob(new CoordinateTunnelTestingJob()); - } - - private Set selectTunnelsToTest() { - Set allIds = getAllIds(); - Set toTest = new HashSet(allIds.size()); - long now = _context.clock().now(); - for (Iterator iter = allIds.iterator(); iter.hasNext();) { - TunnelId id = (TunnelId)iter.next(); - TunnelInfo info = _pool.getTunnelInfo(id); - if ( (info != null) && (info.getSettings() != null) ) { - if (info.getSettings().getExpiration() <= 0) { - // skip local tunnels - } else if (!info.getIsReady()) { - // skip not ready tunnels - } else if (info.getSettings().getExpiration() < now + MINIMUM_RETEST_DELAY) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel " + id.getTunnelId() - + " will be expiring within the current period (" - + new Date(info.getSettings().getExpiration()) - + "), so skip testing it"); - } else if (info.getSettings().getCreated() + MINIMUM_RETEST_DELAY < now) { - // we're past the initial buffer period - if (info.getLastTested() + MINIMUM_RETEST_DELAY < now) { - // we haven't tested this tunnel in the minimum delay, so maybe we - // should. - if (_context.random().nextBoolean()) { - toTest.add(id); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("We could have tested tunnel " + id.getTunnelId() - + ", but randomly decided not to."); - } - } - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Tunnel " + id.getTunnelId() + " was just created (" - + new Date(info.getSettings().getCreated()) - + "), wait until the next pass to test it"); - } - } else { - if (_log.shouldLog(Log.WARN)) - _log.warn("Hmm, a normally testable tunnel [" + id.getTunnelId() + "] didn't have info or settings: " + info); - } - } - return toTest; - } - - private Set getAllIds() { - return _pool.getManagedTunnelIds(); - } - - public void stopTesting() { _stopTesting = true; } - - private void runTest(TunnelId tunnel) { - _context.jobQueue().addJob(new TestTunnelJob(_context, tunnel, _pool)); - } - - private class CoordinateTunnelTestingJob extends JobImpl { - public CoordinateTunnelTestingJob() { - super(TunnelTestManager.this._context); - getTiming().setStartAfter(TunnelTestManager.this._context.clock().now() + MINIMUM_RETEST_DELAY); - } - public String getName() { return "Coordinate Tunnel Testing"; } - public void runJob() { - if (_stopTesting) return; - - Set toTestIds = selectTunnelsToTest(); - if (_log.shouldLog(Log.INFO)) - _log.info("Running tests on selected tunnels: " + toTestIds); - for (Iterator iter = toTestIds.iterator(); iter.hasNext(); ) { - TunnelId id = (TunnelId)iter.next(); - runTest(id); - } - reschedule(); - } - - private void reschedule() { - long nxt = TunnelTestManager.this._context.clock().now() + 30*1000; - getTiming().setStartAfter(nxt); - TunnelTestManager.this._context.jobQueue().addJob(CoordinateTunnelTestingJob.this); - } - } -}