diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java index b7e8b51397..d09b11f04b 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java @@ -82,7 +82,21 @@ class FloodfillVerifyStoreJob extends JobImpl { return; } - DatabaseLookupMessage lookup = buildLookup(); + boolean isInboundExploratory; + TunnelInfo replyTunnelInfo; + if (_isRouterInfo || getContext().keyRing().get(_key) != null) { + replyTunnelInfo = getContext().tunnelManager().selectInboundExploratoryTunnel(_target); + isInboundExploratory = true; + } else { + replyTunnelInfo = getContext().tunnelManager().selectInboundTunnel(_key, _target); + isInboundExploratory = false; + } + if (replyTunnelInfo == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("No inbound tunnels to get a reply from!"); + return; + } + DatabaseLookupMessage lookup = buildLookup(replyTunnelInfo); if (lookup == null) { _facade.verifyFinished(_key); return; @@ -112,7 +126,19 @@ class FloodfillVerifyStoreJob extends JobImpl { return; } if (DatabaseLookupMessage.supportsEncryptedReplies(peer)) { - MessageWrapper.OneTimeSession sess = MessageWrapper.generateSession(getContext()); + // register the session with the right SKM + MessageWrapper.OneTimeSession sess; + if (isInboundExploratory) { + sess = MessageWrapper.generateSession(getContext()); + } else { + sess = MessageWrapper.generateSession(getContext(), _key); + if (sess == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("No SKM to reply to"); + _facade.verifyFinished(_key); + return; + } + } if (_log.shouldLog(Log.INFO)) _log.info("Requesting encrypted reply from " + _target + ' ' + sess.key + ' ' + sess.tag); lookup.setReplySession(sess.key, sess.tag); @@ -154,20 +180,10 @@ class FloodfillVerifyStoreJob extends JobImpl { return _sentTo; } - private DatabaseLookupMessage buildLookup() { + private DatabaseLookupMessage buildLookup(TunnelInfo replyTunnelInfo) { // If we are verifying a leaseset, use the destination's own tunnels, // to avoid association by the exploratory tunnel OBEP. // Unless it is an encrypted leaseset. - TunnelInfo replyTunnelInfo; - if (_isRouterInfo || getContext().keyRing().get(_key) != null) - replyTunnelInfo = getContext().tunnelManager().selectInboundExploratoryTunnel(_target); - else - replyTunnelInfo = getContext().tunnelManager().selectInboundTunnel(_key, _target); - if (replyTunnelInfo == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("No inbound tunnels to get a reply from!"); - return null; - } DatabaseLookupMessage m = new DatabaseLookupMessage(getContext(), true); m.setMessageExpiration(getContext().clock().now() + VERIFY_TIMEOUT); m.setReplyTunnel(replyTunnelInfo.getReceiveTunnelId(0)); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java index 44a1b61ad7..3930aebca9 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java @@ -168,10 +168,38 @@ public class MessageWrapper { * @since 0.9.7 */ public static OneTimeSession generateSession(RouterContext ctx) { + return generateSession(ctx, ctx.sessionKeyManager()); + } + + /** + * Create a single key and tag, for receiving a single encrypted message, + * and register it with the client's session key manager, to expire in two minutes. + * The recipient can then send us an AES-encrypted message, + * avoiding ElGamal. + * + * @return null if we can't find the SKM for the localDest + * @since 0.9.9 + */ + public static OneTimeSession generateSession(RouterContext ctx, Hash localDest) { + SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(localDest); + if (skm == null) + return null; + return generateSession(ctx, skm); + } + + /** + * Create a single key and tag, for receiving a single encrypted message, + * and register it with the given session key manager, to expire in two minutes. + * The recipient can then send us an AES-encrypted message, + * avoiding ElGamal. + * + * @since 0.9.9 + */ + private static OneTimeSession generateSession(RouterContext ctx, SessionKeyManager skm) { SessionKey key = ctx.keyGenerator().generateSessionKey(); SessionTag tag = new SessionTag(true); Set tags = new RemovableSingletonSet(tag); - ctx.sessionKeyManager().tagsReceived(key, tags, 2*60*1000); + skm.tagsReceived(key, tags, 2*60*1000); return new OneTimeSession(key, tag); } diff --git a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java index d886d61927..8e2f36f4c1 100644 --- a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java +++ b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java @@ -62,13 +62,15 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver { if ( (_client != null) && (type == DatabaseSearchReplyMessage.MESSAGE_TYPE) && (_client.equals(((DatabaseSearchReplyMessage)msg).getSearchKey()))) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Removing replies from a DSRM down a tunnel for " + _client + ": " + msg); DatabaseSearchReplyMessage orig = (DatabaseSearchReplyMessage) msg; - DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context); - newMsg.setFromHash(orig.getFromHash()); - newMsg.setSearchKey(orig.getSearchKey()); - msg = newMsg; + if (orig.getNumReplies() > 0) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Removing replies from a DSRM down a tunnel for " + _client + ": " + msg); + DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context); + newMsg.setFromHash(orig.getFromHash()); + newMsg.setSearchKey(orig.getSearchKey()); + msg = newMsg; + } } else if ( (_client != null) && (type == DatabaseStoreMessage.MESSAGE_TYPE) && (((DatabaseStoreMessage)msg).getEntry().getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO)) { @@ -156,29 +158,43 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver { case DeliveryInstructions.DELIVERY_MODE_LOCAL: if (_log.shouldLog(Log.DEBUG)) _log.debug("local delivery instructions for clove: " + data.getClass().getName()); - if (data instanceof GarlicMessage) { + int type = data.getType(); + if (type == GarlicMessage.MESSAGE_TYPE) { _receiver.receive((GarlicMessage)data); - } else if (data instanceof DatabaseStoreMessage) { + } else if (type == DatabaseStoreMessage.MESSAGE_TYPE) { // Treat db store explicitly here (not in HandleFloodfillDatabaseStoreMessageJob), // since we don't want to republish (or flood) // unnecessarily. Reply tokens ignored. DatabaseStoreMessage dsm = (DatabaseStoreMessage)data; try { if (dsm.getEntry().getType() == DatabaseEntry.KEY_TYPE_LEASESET) { - // If it was stored to us before, don't undo the - // receivedAsPublished flag so we will continue to respond to requests - // for the leaseset. That is, we don't want this to change the - // RAP flag of the leaseset. - // When the keyspace rotates at midnight, and this leaseset moves out - // of our keyspace, maybe we shouldn't do this? - // Should we do this whether ff or not? - LeaseSet ls = (LeaseSet) dsm.getEntry(); - LeaseSet old = _context.netDb().store(dsm.getKey(), ls); - if (old != null && old.getReceivedAsPublished() - /** && ((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled() **/ ) - ls.setReceivedAsPublished(true); - if (_log.shouldLog(Log.INFO)) - _log.info("Storing LS for: " + dsm.getKey() + " sent to: " + _client); + if (dsm.getKey().equals(_client)) { + // store of our own LS. + // This is almost certainly a response to a FloodfillVerifyStoreJob search. + // We must send to the InNetMessagePool so the message can be matched + // and the verify marked as successful. + // Ensure the reply info is clear... + dsm.setReplyToken(0); + dsm.setReplyTunnel(null); + dsm.setReplyGateway(null); + // ... and inject it. + _context.inNetMessagePool().add(dsm, null, null); + } else { + // If it was stored to us before, don't undo the + // receivedAsPublished flag so we will continue to respond to requests + // for the leaseset. That is, we don't want this to change the + // RAP flag of the leaseset. + // When the keyspace rotates at midnight, and this leaseset moves out + // of our keyspace, maybe we shouldn't do this? + // Should we do this whether ff or not? + LeaseSet ls = (LeaseSet) dsm.getEntry(); + LeaseSet old = _context.netDb().store(dsm.getKey(), ls); + if (old != null && old.getReceivedAsPublished() + /** && ((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled() **/ ) + ls.setReceivedAsPublished(true); + if (_log.shouldLog(Log.INFO)) + _log.info("Storing LS for: " + dsm.getKey() + " sent to: " + _client); + } } else { if (_client != null) { // drop it, since the data we receive shouldn't include router @@ -195,13 +211,26 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver { if (_log.shouldLog(Log.WARN)) _log.warn("Bad store attempt", iae); } - } else if (data instanceof DataMessage) { + } else if (_client != null && type == DatabaseSearchReplyMessage.MESSAGE_TYPE && + _client.equals(((DatabaseSearchReplyMessage) data).getSearchKey())) { + // DSRMs show up here now that replies are encrypted + DatabaseSearchReplyMessage orig = (DatabaseSearchReplyMessage) data; + if (orig.getNumReplies() > 0) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Removing replies from a garlic DSRM down a tunnel for " + _client + ": " + data); + DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context); + newMsg.setFromHash(orig.getFromHash()); + newMsg.setSearchKey(orig.getSearchKey()); + orig = newMsg; + } + _context.inNetMessagePool().add(orig, null, null); + } else if (type == DataMessage.MESSAGE_TYPE) { // a data message targetting the local router is how we send load tests (real // data messages target destinations) _context.statManager().addRateData("tunnel.handleLoadClove", 1, 0); data = null; //_context.inNetMessagePool().add(data, null, null); - } else if (_client != null && data.getType() != DeliveryStatusMessage.MESSAGE_TYPE) { + } else if (_client != null && type != DeliveryStatusMessage.MESSAGE_TYPE) { // drop it, since the data we receive shouldn't include other stuff, // as that might open an attack vector _context.statManager().addRateData("tunnel.dropDangerousClientTunnelMessage", 1,