diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java index 4813fa2a17..858458e3ff 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -601,9 +601,12 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem return; int status = ise != null ? ise.getStatus() : -1; String error; - //TODO MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION if (status == MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET) { + // We won't get this one unless it is treated as a hard failure + // in streaming. See PacketQueue.java error = usingWWWProxy ? "nolsp" : "nols"; + } else if (status == MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION) { + error = usingWWWProxy ? "encp" : "enc"; } else { error = usingWWWProxy ? "dnfp" : "dnf"; } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketQueue.java index 102bfa6cc1..51b8baef73 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketQueue.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/PacketQueue.java @@ -44,7 +44,7 @@ class PacketQueue implements SendMessageStatusListener { private static final int FINAL_TAGS_TO_SEND = 4; private static final int FINAL_TAG_THRESHOLD = 2; private static final long REMOVE_EXPIRED_TIME = 67*1000; - private static final boolean ENABLE_STATUS_LISTEN = false; + private static final boolean ENABLE_STATUS_LISTEN = true; public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) { _context = context; @@ -267,6 +267,20 @@ class PacketQueue implements SendMessageStatusListener { _messageStatusMap.remove(id); break; + case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET: + // Ideally we would like to make this a hard failure, + // but it caused far too many fast-fails that were then + // resolved by the user clicking reload in his browser. + // Until the LS fetch is faster and more reliable, + // or we increase the timeout for it, + // we can't treat this one as a hard fail. + // Let the streaming retransmission paper over the problem. + if (_log.shouldLog(Log.WARN)) + _log.warn("LS lookup (soft) failure for msg " + msgId + " on " + con); + _messageStatusMap.remove(id); + break; + + case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL: case MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER: case MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK: @@ -280,7 +294,6 @@ class PacketQueue implements SendMessageStatusListener { case MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION: case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET: case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET: - case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET: case SendMessageStatusListener.STATUS_CANCELLED: if (con.getHighestAckedThrough() >= 0) { // a retxed SYN succeeded before the first SYN failed diff --git a/installer/resources/proxy/enc-header.ht b/installer/resources/proxy/enc-header.ht new file mode 100644 index 0000000000..e5c271e234 --- /dev/null +++ b/installer/resources/proxy/enc-header.ht @@ -0,0 +1,24 @@ +HTTP/1.1 504 Gateway Timeout +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + + +_("Warning: Eepsite Unreachable") + + + + + +
+

_("Warning: Eepsite Unreachable")

+

+_("The eepsite was not reachable, because it uses encryption options that are not supported by your I2P or Java version.") +


+

_("Could not connect to the following destination:") +

diff --git a/installer/resources/proxy/encp-header.ht b/installer/resources/proxy/encp-header.ht new file mode 100644 index 0000000000..9d53fb7076 --- /dev/null +++ b/installer/resources/proxy/encp-header.ht @@ -0,0 +1,25 @@ +HTTP/1.1 504 Gateway Timeout +Content-Type: text/html; charset=UTF-8 +Cache-control: no-cache +Connection: close +Proxy-Connection: close + + + +_("Warning: Outproxy Unreachable") + + + + + +
+

_("Warning: Outproxy Unreachable")

+

+_("The HTTP outproxy was not reachable, because it uses encryption options that are not supported by your I2P or Java version.") +_("You may want to {0}retry{1} as this will randomly reselect an outproxy from the pool you have defined {2}here{3} (if you have more than one configured).", "", "", "", "") +_("If you continue to have trouble you may want to edit your outproxy list {0}here{1}.", "", "") +

+

_("Could not connect to the following destination:")

diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index 2e4ee3c150..fdd1fd30fb 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.Set; import net.i2p.data.DatabaseEntry; +import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.router.RouterInfo; @@ -51,18 +52,51 @@ public abstract class NetworkDatabaseFacade implements Service { public abstract LeaseSet lookupLeaseSetLocally(Hash key); public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs); public abstract RouterInfo lookupRouterInfoLocally(Hash key); + + /** + * Lookup using the client's tunnels + * Succeeds even if LS validation fails due to unsupported sig type + * + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.16 + */ + public abstract void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest); + + /** + * Lookup locally in netDB and in badDest cache + * Succeeds even if LS validation failed due to unsupported sig type + * + * @since 0.9.16 + */ + public abstract Destination lookupDestinationLocally(Hash key); + /** - * return the leaseSet if another leaseSet already existed at that key + * @return the leaseSet if another leaseSet already existed at that key * * @throws IllegalArgumentException if the data is not valid */ public abstract LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException; + /** - * return the routerInfo if another router already existed at that key + * @return the routerInfo if another router already existed at that key * * @throws IllegalArgumentException if the data is not valid */ public abstract RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException; + + /** + * @return the old entry if it already existed at that key + * @throws IllegalArgumentException if the data is not valid + * @since 0.9.16 + */ + public DatabaseEntry store(Hash key, DatabaseEntry entry) throws IllegalArgumentException { + if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) + return store(key, (RouterInfo) entry); + if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) + return store(key, (LeaseSet) entry); + throw new IllegalArgumentException("unknown type"); + } + /** * @throws IllegalArgumentException if the local router is not valid */ @@ -101,4 +135,12 @@ public abstract class NetworkDatabaseFacade implements Service { * @since IPv6 */ public boolean floodfillEnabled() { return false; }; + + /** + * Is it permanently negative cached? + * + * @param key only for Destinations; for RouterIdentities, see Banlist + * @since 0.9.16 + */ + public boolean isNegativeCachedForever(Hash key) { return false; } } diff --git a/router/java/src/net/i2p/router/PersistentKeyRing.java b/router/java/src/net/i2p/router/PersistentKeyRing.java index a3e71ee8e2..920eec7d29 100644 --- a/router/java/src/net/i2p/router/PersistentKeyRing.java +++ b/router/java/src/net/i2p/router/PersistentKeyRing.java @@ -70,9 +70,8 @@ public class PersistentKeyRing extends KeyRing { Hash h = e.getKey(); buf.append(h.toBase64().substring(0, 6)).append("…"); buf.append(""); - LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h); - if (ls != null) { - Destination dest = ls.getDestination(); + Destination dest = _ctx.netDb().lookupDestinationLocally(h); + if (dest != null) { if (_ctx.clientManager().isLocal(dest)) { TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h); if (in != null && in.getDestinationNickname() != null) diff --git a/router/java/src/net/i2p/router/client/LookupDestJob.java b/router/java/src/net/i2p/router/client/LookupDestJob.java index be08388ba2..911365b8e8 100644 --- a/router/java/src/net/i2p/router/client/LookupDestJob.java +++ b/router/java/src/net/i2p/router/client/LookupDestJob.java @@ -38,7 +38,11 @@ class LookupDestJob extends JobImpl { } /** - * One of h or name non-null + * One of h or name non-null. + * + * For hash or b32 name, the dest will be returned if the LS can be found, + * even if the dest uses unsupported crypto. + * * @param reqID must be >= 0 if name != null * @param sessID must non-null if reqID >= 0 * @param fromLocalDest use these tunnels for the lookup, or null for exploratory @@ -88,7 +92,7 @@ class LookupDestJob extends JobImpl { returnFail(); } else { DoneJob done = new DoneJob(getContext()); - getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout, _fromLocalDest); + getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest); } } @@ -98,9 +102,9 @@ class LookupDestJob extends JobImpl { } public String getName() { return "LeaseSet Lookup Reply to Client"; } public void runJob() { - LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_hash); - if (ls != null) - returnDest(ls.getDestination()); + Destination dest = getContext().netDb().lookupDestinationLocally(_hash); + if (dest != null) + returnDest(dest); else returnFail(); } diff --git a/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java index 6c99bb90b3..460d7a7125 100644 --- a/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Set; import net.i2p.data.DatabaseEntry; +import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.router.RouterInfo; @@ -23,8 +24,8 @@ import net.i2p.router.NetworkDatabaseFacade; import net.i2p.router.RouterContext; public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade { - private Map _routers; - private RouterContext _context; + private final Map _routers; + private final RouterContext _context; public DummyNetworkDatabaseFacade(RouterContext ctx) { _routers = Collections.synchronizedMap(new HashMap()); @@ -42,6 +43,11 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade { public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {} public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {} public LeaseSet lookupLeaseSetLocally(Hash key) { return null; } + + public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {} + + public Destination lookupDestinationLocally(Hash key) { return null; } + public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) { RouterInfo info = lookupRouterInfoLocally(key); if (info == null) @@ -50,13 +56,16 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade { _context.jobQueue().addJob(onFindJob); } public RouterInfo lookupRouterInfoLocally(Hash key) { return _routers.get(key); } + public void publish(LeaseSet localLeaseSet) {} public void publish(RouterInfo localRouterInfo) {} + public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; } public RouterInfo store(Hash key, RouterInfo routerInfo) { RouterInfo rv = _routers.put(key, routerInfo); return rv; } + public void unpublish(LeaseSet localLeaseSet) {} public void fail(Hash dbEntry) { _routers.remove(dbEntry); diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 7da54172ce..d9f7a0a516 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -425,12 +425,19 @@ public class OutboundClientMessageOneShotJob extends JobImpl { getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime); } - //if (_finished == Result.NONE) { + + int cause; + if (getContext().netDb().isNegativeCachedForever(_to.calculateHash())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to send to " + _toString + " because the sig type is unsupported"); + cause = MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION; + } else { if (_log.shouldLog(Log.WARN)) _log.warn("Unable to send to " + _toString + " because we couldn't find their leaseSet"); - //} + cause = MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET; + } - dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET); + dieFatal(cause); } } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlyLookupMatchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlyLookupMatchJob.java index 0de6c6504f..46f985a917 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlyLookupMatchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodOnlyLookupMatchJob.java @@ -2,10 +2,10 @@ package net.i2p.router.networkdb.kademlia; import net.i2p.data.DatabaseEntry; import net.i2p.data.LeaseSet; -import net.i2p.data.router.RouterInfo; import net.i2p.data.i2np.DatabaseSearchReplyMessage; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.router.RouterInfo; import net.i2p.router.JobImpl; import net.i2p.router.ReplyJob; import net.i2p.router.RouterContext; @@ -62,6 +62,9 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob { } else { getContext().netDb().store(dsm.getKey(), (RouterInfo) dsm.getEntry()); } + } catch (UnsupportedCryptoException uce) { + _search.failed(); + return; } catch (IllegalArgumentException iae) { if (_log.shouldLog(Log.WARN)) _log.warn(_search.getJobId() + ": Received an invalid store reply", iae); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index 22d51249a2..e8a80368c4 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -7,11 +7,12 @@ import java.util.Map; import java.util.Set; import net.i2p.data.DatabaseEntry; +import net.i2p.data.Destination; import net.i2p.data.Hash; -import net.i2p.data.router.RouterInfo; import net.i2p.data.TunnelId; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.DatabaseStoreMessage; +import net.i2p.data.router.RouterInfo; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.OutNetMessage; @@ -31,7 +32,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad private final Set _verifiesInProgress; private FloodThrottler _floodThrottler; private LookupThrottler _lookupThrottler; - private NegativeLookupCache _negativeCache; /** * This is the flood redundancy. Entries are @@ -65,7 +65,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad _context.statManager().createRateStat("netDb.searchReplyNotValidated", "How many search replies we get that we are NOT able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); _context.statManager().createRateStat("netDb.searchReplyValidationSkipped", "How many search replies we get from unreliable peers that we skip?", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); _context.statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l }); } @Override @@ -73,7 +72,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad super.startup(); _context.jobQueue().addJob(new FloodfillMonitorJob(_context, this)); _lookupThrottler = new LookupThrottler(); - _negativeCache = new NegativeLookupCache(); // refresh old routers Job rrj = new RefreshRoutersJob(_context, this); @@ -171,25 +169,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad return _lookupThrottler.shouldThrottle(from, id); } - /** - * Increment in the negative lookup cache - * @since 0.9.4 - */ - void lookupFailed(Hash key) { - _negativeCache.lookupFailed(key); - } - - /** - * Is the key in the negative lookup cache? - * @since 0.9.4 - */ - boolean isNegativeCached(Hash key) { - boolean rv = _negativeCache.isCached(key); - if (rv) - _context.statManager().addRateData("netDb.negativeCache", 1); - return rv; - } - /** * Send to a subset of all floodfill peers. * We do this to implement Kademlia within the floodfills, i.e. @@ -301,7 +280,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad } /** - * Lookup using exploratory tunnels + * Lookup using exploratory tunnels. + * + * Caller should check negative cache and/or banlist before calling. * * Begin a kademlia style search for the key specified, which can take up to timeoutMs and * will fire the appropriate jobs on success or timeout (or if the kademlia search completes @@ -315,7 +296,10 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad } /** - * Lookup using the client's tunnels + * Lookup using the client's tunnels. + * + * Caller should check negative cache and/or banlist before calling. + * * @param fromLocalDest use these tunnels for the lookup, or null for exploratory * @return null always * @since 0.9.10 @@ -473,6 +457,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad // should we skip the search? if (_floodfillEnabled || _context.jobQueue().getMaxLag() > 500 || + _context.banlist().isBanlistedForever(peer) || getKBucketSetSize() > MAX_DB_BEFORE_SKIPPING_SEARCH) { // don't try to overload ourselves (e.g. failing 3000 router refs at // once, and then firing off 3000 netDb lookup tasks) 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 85d0bc974d..085c0c921a 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillVerifyStoreJob.java @@ -6,6 +6,7 @@ import java.util.Set; import net.i2p.data.Certificate; import net.i2p.data.DatabaseEntry; +import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.router.RouterInfo; @@ -173,9 +174,9 @@ class FloodfillVerifyStoreJob extends JobImpl { FloodfillPeerSelector sel = (FloodfillPeerSelector)_facade.getPeerSelector(); Certificate keyCert = null; if (!_isRouterInfo) { - LeaseSet ls = _facade.lookupLeaseSetLocally(_key); - if (ls != null) { - Certificate cert = ls.getDestination().getCertificate(); + Destination dest = _facade.lookupDestinationLocally(_key); + if (dest != null) { + Certificate cert = dest.getCertificate(); if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) keyCert = cert; } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java index 57d1ee57bc..ffb6d77f00 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java @@ -51,6 +51,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { long recvBegin = System.currentTimeMillis(); String invalidMessage = null; + // set if invalid store but not his fault + boolean dontBlamePeer = false; boolean wasNew = false; RouterInfo prevNetDb = null; Hash key = _message.getKey(); @@ -72,6 +74,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { if (getContext().clientManager().isLocal(key)) { //getContext().statManager().addRateData("netDb.storeLocalLeaseSetAttempt", 1, 0); // throw rather than return, so that we send the ack below (prevent easy attack) + dontBlamePeer = true; throw new IllegalArgumentException("Peer attempted to store local leaseSet: " + key.toBase64().substring(0, 4)); } @@ -114,6 +117,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { //if (!ls.getReceivedAsReply()) // match.setReceivedAsPublished(true); } + } catch (UnsupportedCryptoException uce) { + invalidMessage = uce.getMessage(); + dontBlamePeer = true; } catch (IllegalArgumentException iae) { invalidMessage = iae.getMessage(); } @@ -131,8 +137,10 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { if (getContext().routerHash().equals(key)) { //getContext().statManager().addRateData("netDb.storeLocalRouterInfoAttempt", 1, 0); // throw rather than return, so that we send the ack below (prevent easy attack) + dontBlamePeer = true; throw new IllegalArgumentException("Peer attempted to store our RouterInfo"); } + getContext().profileManager().heardAbout(key); prevNetDb = getContext().netDb().store(key, ri); wasNew = ((null == prevNetDb) || (prevNetDb.getPublished() < ri.getPublished())); // Check new routerinfo address against blocklist @@ -152,7 +160,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { _log.warn("New address received, Blocklisting old peer " + key + ' ' + ri); } } - getContext().profileManager().heardAbout(key); + } catch (UnsupportedCryptoException uce) { + invalidMessage = uce.getMessage(); + dontBlamePeer = true; } catch (IllegalArgumentException iae) { invalidMessage = iae.getMessage(); } @@ -165,14 +175,16 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { long recvEnd = System.currentTimeMillis(); getContext().statManager().addRateData("netDb.storeRecvTime", recvEnd-recvBegin); - if (_message.getReplyToken() > 0) + // ack even if invalid or unsupported + // TODO any cases where we shouldn't? + if (_message.getReplyToken() > 0) sendAck(); long ackEnd = System.currentTimeMillis(); if (_from != null) _fromHash = _from.getHash(); if (_fromHash != null) { - if (invalidMessage == null) { + if (invalidMessage == null || dontBlamePeer) { getContext().profileManager().dbStoreReceived(_fromHash, wasNew); getContext().statManager().addRateData("netDb.storeHandled", ackEnd-recvEnd); } else { @@ -180,7 +192,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl { if (_log.shouldLog(Log.WARN)) _log.warn("Peer " + _fromHash.toBase64() + " sent bad data: " + invalidMessage); } - } else if (invalidMessage != null) { + } else if (invalidMessage != null && !dontBlamePeer) { if (_log.shouldLog(Log.WARN)) _log.warn("Unknown peer sent bad data: " + invalidMessage); } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index ba52e5d611..5507710cdc 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -19,14 +19,20 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import net.i2p.crypto.SigType; +import net.i2p.data.Certificate; import net.i2p.data.DatabaseEntry; +import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.data.KeyCertificate; import net.i2p.data.LeaseSet; -import net.i2p.data.router.RouterAddress; -import net.i2p.data.router.RouterInfo; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.DatabaseStoreMessage; +import net.i2p.data.router.RouterAddress; +import net.i2p.data.router.RouterIdentity; +import net.i2p.data.router.RouterInfo; import net.i2p.kademlia.KBucketSet; import net.i2p.kademlia.RejectTrimmer; import net.i2p.kademlia.SelectionCollector; @@ -63,6 +69,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { protected final RouterContext _context; private final ReseedChecker _reseedChecker; private volatile long _lastRIPublishTime; + private NegativeLookupCache _negativeCache; /** * Map of Hash to RepublishLeaseSetJob for leases we'realready managing. @@ -155,6 +162,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _reseedChecker = new ReseedChecker(context); context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 }); context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 }); + context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l }); // following are for StoreJob context.statManager().createRateStat("netDb.storeRouterInfoSent", "How many routerInfo store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l }); context.statManager().createRateStat("netDb.storeLeaseSetSent", "How many leaseSet store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l }); @@ -223,6 +231,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { //_ds = null; _exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't. // _exploreKeys = null; + _negativeCache.clear(); } public synchronized void restart() { @@ -262,6 +271,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { //_ds = new TransientDataStore(); // _exploreKeys = new HashSet(64); _dbDir = dbDir; + _negativeCache = new NegativeLookupCache(); createHandlers(); @@ -480,7 +490,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } /** - * Lookup using exploratory tunnels + * Lookup using exploratory tunnels. + * Use lookupDestination() if you don't need the LS or need it validated. */ public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) { lookupLeaseSet(key, onFindJob, onFailedLookupJob, timeoutMs, null); @@ -488,6 +499,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { /** * Lookup using the client's tunnels + * Use lookupDestination() if you don't need the LS or need it validated. + * * @param fromLocalDest use these tunnels for the lookup, or null for exploratory * @since 0.9.10 */ @@ -500,6 +513,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _log.debug("leaseSet found locally, firing " + onFindJob); if (onFindJob != null) _context.jobQueue().addJob(onFindJob); + } else if (isNegativeCached(key)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Negative cached, not searching: " + key); + if (onFailedLookupJob != null) + _context.jobQueue().addJob(onFailedLookupJob); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("leaseSet not found locally, running search"); @@ -509,6 +527,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _log.debug("after lookupLeaseSet"); } + /** + * Use lookupDestination() if you don't need the LS or need it validated. + */ public LeaseSet lookupLeaseSetLocally(Hash key) { if (!_initialized) return null; DatabaseEntry ds = _ds.get(key); @@ -531,6 +552,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { return null; } } + + /** + * Lookup using the client's tunnels + * Succeeds even if LS validation and store fails due to unsupported sig type, expired, etc. + * + * Note that there are not separate success and fail jobs. Caller must call + * lookupDestinationLocally() in the job to determine success. + * + * @param onFinishedJob non-null + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.16 + */ + public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) { + if (!_initialized) return; + Destination d = lookupDestinationLocally(key); + if (d != null) { + _context.jobQueue().addJob(onFinishedJob); + } else { + search(key, onFinishedJob, onFinishedJob, timeoutMs, true, fromLocalDest); + } + } + + /** + * Lookup locally in netDB and in badDest cache + * Succeeds even if LS validation fails due to unsupported sig type, expired, etc. + * + * @since 0.9.16 + */ + public Destination lookupDestinationLocally(Hash key) { + if (!_initialized) return null; + DatabaseEntry ds = _ds.get(key); + if (ds != null) { + if (ds.getType() == DatabaseEntry.KEY_TYPE_LEASESET) { + LeaseSet ls = (LeaseSet)ds; + return ls.getDestination(); + } + } else { + return _negativeCache.getBadDest(key); + } + return null; + } public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) { if (!_initialized) return; @@ -538,6 +600,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { if (ri != null) { if (onFindJob != null) _context.jobQueue().addJob(onFindJob); + } else if (_context.banlist().isBanlistedForever(key)) { + if (onFailedLookupJob != null) + _context.jobQueue().addJob(onFailedLookupJob); } else { search(key, onFindJob, onFailedLookupJob, timeoutMs, false); } @@ -694,9 +759,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { * Unlike for RouterInfos, this is only called once, when stored. * After that, LeaseSet.isCurrent() is used. * + * @throws UnsupportedCryptoException if that's why it failed. * @return reason why the entry is not valid, or null if it is valid */ - private String validate(Hash key, LeaseSet leaseSet) { + private String validate(Hash key, LeaseSet leaseSet) throws UnsupportedCryptoException { if (!key.equals(leaseSet.getDestination().calculateHash())) { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid store attempt! key does not match leaseSet.destination! key = " @@ -704,9 +770,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { return "Key does not match leaseSet.destination - " + key.toBase64(); } if (!leaseSet.verifySignature()) { + // throws UnsupportedCryptoException + processStoreFailure(key, leaseSet); if (_log.shouldLog(Log.WARN)) - _log.warn("Invalid leaseSet signature! leaseSet = " + leaseSet); - return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64(); + _log.warn("Invalid leaseSet signature! " + leaseSet); + return "Invalid leaseSet signature on " + key; } long earliest = leaseSet.getEarliestLeaseDate(); long latest = leaseSet.getLatestLeaseDate(); @@ -722,7 +790,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { + " first exp. " + new Date(earliest) + " last exp. " + new Date(latest), new Exception("Rejecting store")); - return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64() + return "Expired leaseSet for " + leaseSet.getDestination().calculateHash() + " expired " + DataHelper.formatDuration(age) + " ago"; } if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) { @@ -739,9 +807,13 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } /** - * Store the leaseSet + * Store the leaseSet. + * + * If the store fails due to unsupported crypto, it will negative cache + * the hash until restart. * * @throws IllegalArgumentException if the leaseSet is not valid + * @throws UnsupportedCryptoException if that's why it failed. * @return previous entry or null */ public LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException { @@ -798,6 +870,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { * * Call this only on first store, to check the key and signature once * + * If the store fails due to unsupported crypto, it will banlist + * the router hash until restart and then throw UnsupportedCrytpoException. + * + * @throws UnsupportedCryptoException if that's why it failed. * @return reason why the entry is not valid, or null if it is valid */ private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException { @@ -807,6 +883,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { return "Key does not match routerInfo.identity"; } if (!routerInfo.isValid()) { + // throws UnsupportedCryptoException + processStoreFailure(key, routerInfo); if (_log.shouldLog(Log.WARN)) _log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo); return "Invalid routerInfo signature"; @@ -892,15 +970,29 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } /** - * store the routerInfo + * Store the routerInfo. + * + * If the store fails due to unsupported crypto, it will banlist + * the router hash until restart and then throw UnsupportedCrytpoException. * * @throws IllegalArgumentException if the routerInfo is not valid + * @throws UnsupportedCryptoException if that's why it failed. * @return previous entry or null */ public RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException { return store(key, routerInfo, true); } + /** + * Store the routerInfo. + * + * If the store fails due to unsupported crypto, it will banlist + * the router hash until restart and then throw UnsupportedCrytpoException. + * + * @throws IllegalArgumentException if the routerInfo is not valid + * @throws UnsupportedCryptoException if that's why it failed. + * @return previous entry or null + */ RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException { if (!_initialized) return null; @@ -934,6 +1026,59 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _kb.add(key); return rv; } + + /** + * If the validate fails, call this + * to determine if it was because of unsupported crypto. + * + * If so, this will banlist-forever the router hash or permanently negative cache the dest hash, + * and then throw the exception. Otherwise it does nothing. + * + * @throws UnsupportedCryptoException if that's why it failed. + * @since 0.9.16 + */ + private void processStoreFailure(Hash h, DatabaseEntry entry) throws UnsupportedCryptoException { + if (entry.getHash().equals(h)) { + if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) { + LeaseSet ls = (LeaseSet) entry; + Destination d = ls.getDestination(); + Certificate c = d.getCertificate(); + if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + try { + KeyCertificate kc = c.toKeyCertificate(); + SigType type = kc.getSigType(); + if (type == null || !type.isAvailable()) { + failPermanently(d); + String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode()); + if (_log.shouldLog(Log.WARN)) + _log.warn("Unsupported sig type " + stype + " for destination " + h); + throw new UnsupportedCryptoException("Sig type " + stype); + } + } catch (DataFormatException dfe) {} + } + } else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { + RouterInfo ri = (RouterInfo) entry; + RouterIdentity id = ri.getIdentity(); + Certificate c = id.getCertificate(); + if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + try { + KeyCertificate kc = c.toKeyCertificate(); + SigType type = kc.getSigType(); + if (type == null || !type.isAvailable()) { + String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode()); + _context.banlist().banlistRouterForever(h, "Unsupported signature type " + stype); + if (_log.shouldLog(Log.WARN)) + _log.warn("Unsupported sig type " + stype + " for router " + h); + throw new UnsupportedCryptoException("Sig type " + stype); + } + } catch (DataFormatException dfe) {} + } + } + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Verify fail, cause unknown: " + entry); + } + /** * Final remove for a leaseset. @@ -1005,8 +1150,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { * without any match) * * Unused - called only by FNDF.searchFull() from FloodSearchJob which is overridden - don't use this. + * + * @throws UnsupportedOperationException always */ SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease) { + throw new UnsupportedOperationException(); +/**** if (!_initialized) return null; boolean isNew = true; SearchJob searchJob = null; @@ -1031,6 +1180,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _context.statManager().addRateData("netDb.lookupDeferred", deferred, searchJob.getExpiration()-_context.clock().now()); } return searchJob; +****/ } /** @@ -1102,6 +1252,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { _context.jobQueue().addJob(new StoreJob(_context, this, key, ds, onSuccess, onFailure, sendTimeout, toIgnore)); } + /** + * Increment in the negative lookup cache + * + * @param key for Destinations or RouterIdentities + * @since 0.9.4 moved from FNDF to KNDF in 0.9.16 + */ + void lookupFailed(Hash key) { + _negativeCache.lookupFailed(key); + } + + /** + * Is the key in the negative lookup cache? + *& + * @param key for Destinations or RouterIdentities + * @since 0.9.4 moved from FNDF to KNDF in 0.9.16 + */ + boolean isNegativeCached(Hash key) { + boolean rv = _negativeCache.isCached(key); + if (rv) + _context.statManager().addRateData("netDb.negativeCache", 1); + return rv; + } + + /** + * Negative cache until restart + * @since 0.9.16 + */ + void failPermanently(Destination dest) { + _negativeCache.failPermanently(dest); + } + + /** + * Is it permanently negative cached? + * + * @param key only for Destinations; for RouterIdentities, see Banlist + * @since 0.9.16 + */ + public boolean isNegativeCachedForever(Hash key) { + return _negativeCache.getBadDest(key) != null; + } + /** * Debug info, HTML formatted * @since 0.9.10 diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/NegativeLookupCache.java b/router/java/src/net/i2p/router/networkdb/kademlia/NegativeLookupCache.java index abbb67ed62..1784f9434d 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/NegativeLookupCache.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/NegativeLookupCache.java @@ -1,6 +1,9 @@ package net.i2p.router.networkdb.kademlia; +import java.util.Map; +import net.i2p.data.Destination; import net.i2p.data.Hash; +import net.i2p.util.LHMCache; import net.i2p.util.ObjectCounter; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; @@ -12,11 +15,15 @@ import net.i2p.util.SimpleTimer; */ class NegativeLookupCache { private final ObjectCounter counter; + private final Map badDests; + private static final int MAX_FAILS = 3; + private static final int MAX_BAD_DESTS = 128; private static final long CLEAN_TIME = 2*60*1000; public NegativeLookupCache() { this.counter = new ObjectCounter(); + this.badDests = new LHMCache(MAX_BAD_DESTS); SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME); } @@ -25,7 +32,46 @@ class NegativeLookupCache { } public boolean isCached(Hash h) { - return this.counter.count(h) >= MAX_FAILS; + if (counter.count(h) >= MAX_FAILS) + return true; + synchronized(badDests) { + return badDests.get(h) != null; + } + } + + /** + * Negative cache the hash until restart, + * but cache the destination. + * + * @since 0.9.16 + */ + public void failPermanently(Destination dest) { + Hash h = dest.calculateHash(); + synchronized(badDests) { + badDests.put(h, dest); + } + } + + /** + * Get an unsupported but cached Destination + * + * @return dest or null if not cached + * @since 0.9.16 + */ + public Destination getBadDest(Hash h) { + synchronized(badDests) { + return badDests.get(h); + } + } + + /** + * @since 0.9.16 + */ + public void clear() { + counter.clear(); + synchronized(badDests) { + badDests.clear(); + } } private class Cleaner implements SimpleTimer.TimedEvent { diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java index 1c945b1eef..f0edded8b0 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchJob.java @@ -202,22 +202,29 @@ class SearchJob extends JobImpl { _log.debug(getJobId() + ": Already completed"); return; } + if (_state.isAborted()) { + if (_log.shouldLog(Log.INFO)) + _log.info(getJobId() + ": Search aborted"); + _state.complete(); + fail(); + return; + } if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Searching: " + _state); if (isLocal()) { if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Key found locally"); - _state.complete(true); + _state.complete(); succeed(); } else if (isExpired()) { if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Key search expired"); - _state.complete(true); + _state.complete(); fail(); } else if (_state.getAttempted().size() > MAX_PEERS_QUERIED) { if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Too many peers quried"); - _state.complete(true); + _state.complete(); fail(); } else { //_log.debug("Continuing search"); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchState.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchState.java index 106c8ad4dc..61bd1b645d 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchState.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchState.java @@ -19,15 +19,16 @@ import net.i2p.router.RouterContext; */ class SearchState { private final RouterContext _context; - private final HashSet _pendingPeers; + private final Set _pendingPeers; private final Map _pendingPeerTimes; - private final HashSet _attemptedPeers; - private final HashSet _failedPeers; - private final HashSet _successfulPeers; - private final HashSet _repliedPeers; + private final Set _attemptedPeers; + private final Set _failedPeers; + private final Set _successfulPeers; + private final Set _repliedPeers; private final Hash _searchKey; private volatile long _completed; private volatile long _started; + private volatile boolean _aborted; public SearchState(RouterContext context, Hash key) { _context = context; @@ -87,10 +88,19 @@ class SearchState { return new HashSet(_failedPeers); } } + public boolean completed() { return _completed != -1; } - public void complete(boolean completed) { - if (completed) - _completed = _context.clock().now(); + + public void complete() { + _completed = _context.clock().now(); + } + + /** @since 0.9.16 */ + public boolean isAborted() { return _aborted; } + + /** @since 0.9.16 */ + public void abort() { + _aborted = true; } public long getWhenStarted() { return _started; } @@ -177,6 +187,8 @@ class SearchState { buf.append(" completed? false "); else buf.append(" completed on ").append(new Date(_completed)); + if (_aborted) + buf.append(" (Aborted)"); buf.append("\n\tAttempted: "); synchronized (_attemptedPeers) { buf.append(_attemptedPeers.size()).append(' '); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java index 9974037164..63cc1c18f8 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java @@ -81,34 +81,21 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob { if (message instanceof DatabaseStoreMessage) { long timeToReply = _state.dataFound(_peer); - DatabaseStoreMessage msg = (DatabaseStoreMessage)message; DatabaseEntry entry = msg.getEntry(); - if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) { - try { - _facade.store(msg.getKey(), (LeaseSet) entry); - getContext().profileManager().dbLookupSuccessful(_peer, timeToReply); - } catch (IllegalArgumentException iae) { - if (_log.shouldLog(Log.ERROR)) - _log.warn("Peer " + _peer + " sent us an invalid leaseSet: " + iae.getMessage()); - getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply); - } - } else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { - if (_log.shouldLog(Log.INFO)) - _log.info(getJobId() + ": dbStore received on search containing router " - + msg.getKey() + " with publishDate of " - + new Date(entry.getDate())); - try { - _facade.store(msg.getKey(), (RouterInfo) entry); - getContext().profileManager().dbLookupSuccessful(_peer, timeToReply); - } catch (IllegalArgumentException iae) { - if (_log.shouldLog(Log.ERROR)) - _log.warn("Peer " + _peer + " sent us an invalid routerInfo: " + iae.getMessage()); - getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply); - } - } else { - if (_log.shouldLog(Log.ERROR)) - _log.error(getJobId() + ": Unknown db store type?!@ " + entry.getType()); + try { + _facade.store(msg.getKey(), entry); + getContext().profileManager().dbLookupSuccessful(_peer, timeToReply); + } catch (UnsupportedCryptoException iae) { + // don't blame the peer + getContext().profileManager().dbLookupSuccessful(_peer, timeToReply); + _state.abort(); + // searchNext() will call fail() + } catch (IllegalArgumentException iae) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Peer " + _peer + " sent us invalid data: ", iae); + // blame the peer + getContext().profileManager().dbLookupReply(_peer, 0, 0, 1, 0, timeToReply); } } else if (message instanceof DatabaseSearchReplyMessage) { _job.replyFound((DatabaseSearchReplyMessage)message, _peer); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/UnsupportedCryptoException.java b/router/java/src/net/i2p/router/networkdb/kademlia/UnsupportedCryptoException.java new file mode 100644 index 0000000000..0159eb0201 --- /dev/null +++ b/router/java/src/net/i2p/router/networkdb/kademlia/UnsupportedCryptoException.java @@ -0,0 +1,18 @@ +package net.i2p.router.networkdb.kademlia; + +/** + * Signature verification failed because the + * sig type is unknown or unavailable. + * + * @since 0.9.16 + */ +public class UnsupportedCryptoException extends IllegalArgumentException { + + public UnsupportedCryptoException(String msg) { + super(msg); + } + + public UnsupportedCryptoException(String msg, Throwable t) { + super(msg, t); + } +}