From d73b327fd04cfa0dd0974142bfb32ff4ff03d4a0 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 1 Aug 2020 12:36:31 +0000
Subject: [PATCH] NetDB: Track client that requested LS OCMOSJ: Don't send to a
 RAP LS

---
 core/java/src/net/i2p/data/LeaseSet.java      | 24 ++++++++++++++-
 .../net/i2p/router/NetworkDatabaseFacade.java | 11 +++++++
 .../dummy/DummyNetworkDatabaseFacade.java     |  2 ++
 .../OutboundClientMessageOneShotJob.java      | 29 ++++++++++++++++---
 ...andleFloodfillDatabaseStoreMessageJob.java |  8 ++---
 .../KademliaNetworkDatabaseFacade.java        | 29 +++++++++++++++++++
 .../tunnel/InboundMessageDistributor.java     |  6 ++--
 7 files changed, 97 insertions(+), 12 deletions(-)

diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java
index 601acdcc92..1d67c3f195 100644
--- a/core/java/src/net/i2p/data/LeaseSet.java
+++ b/core/java/src/net/i2p/data/LeaseSet.java
@@ -70,6 +70,7 @@ public class LeaseSet extends DatabaseEntry {
     protected final List<Lease> _leases;
     protected boolean _receivedAsPublished;
     private boolean _receivedAsReply;
+    private Hash _receivedBy;
     // Store these since isCurrent() and getEarliestLeaseDate() are called frequently
     private long _firstExpiration;
     protected long _lastExpiration;
@@ -198,9 +199,30 @@ public class LeaseSet extends DatabaseEntry {
      */
     public boolean getReceivedAsReply() { return _receivedAsReply; }
 
-    /** set to true @since 0.7.14 */
+    /**
+     * set to true
+     * @since 0.7.14
+     */
     public void setReceivedAsReply() { _receivedAsReply = true; }
 
+    /**
+     * The Hash of the local client that received this LS,
+     * null if the router or unknown.
+     *
+     * @since 0.9.47
+     */
+    public Hash getReceivedBy() { return _receivedBy; }
+
+    /**
+     * Also sets receivedAsReply to true
+     * @param localClient may be null
+     * @since 0.9.47
+     */
+    public void setReceivedBy(Hash localClient) {
+        _receivedAsReply = true;
+        _receivedBy = localClient;
+    }
+
     /**
      * @throws IllegalStateException if already signed
      */
diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java
index d2402de825..591971f353 100644
--- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java
@@ -74,6 +74,17 @@ public abstract class NetworkDatabaseFacade implements Service {
      */
     public abstract void lookupLeaseSetRemotely(Hash key, Hash fromLocalDest);
 
+    /**
+     *  Unconditionally lookup using the client's tunnels.
+     *
+     *  @param fromLocalDest use these tunnels for the lookup, or null for exploratory
+     *  @param onFindJob may be null
+     *  @param onFailedLookupJob may be null
+     *  @since 0.9.47
+     */
+    public abstract void lookupLeaseSetRemotely(Hash key, Job onFindJob, Job onFailedLookupJob,
+                                       long timeoutMs, Hash fromLocalDest);
+
     /**
      *  Lookup using the client's tunnels
      *  Succeeds even if LS validation fails due to unsupported sig type
diff --git a/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java
index 71b4a5a40d..9fbea9b812 100644
--- a/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java
@@ -45,6 +45,8 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
     public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {}
     public LeaseSet lookupLeaseSetLocally(Hash key) { return null; }
     public void lookupLeaseSetRemotely(Hash key, Hash fromLocalDest) {}
+    public void lookupLeaseSetRemotely(Hash key, Job onFindJob, Job onFailedLookupJob,
+                                       long timeoutMs, Hash fromLocalDest) {}
 
     public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {}
 
diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
index 8a40d9b3bc..7250c08945 100644
--- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
+++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
@@ -207,7 +207,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
         _to = msg.getDestination();
         Hash toHash = _to.calculateHash();
         _hashPair = new OutboundCache.HashPair(_from.calculateHash(), toHash);
-        _toString = toHash.toBase64().substring(0,4);
+        _toString = toHash.toBase32();
         // we look up here rather than runJob() so we may adjust the timeout
         _leaseSet = ctx.netDb().lookupLeaseSetLocally(toHash);
         
@@ -297,6 +297,23 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
         SendJob success = new SendJob(getContext());
         // set in constructor
         if (_leaseSet != null) {
+            if (!_leaseSet.getReceivedAsReply()) {
+                boolean shouldFetch = true;
+                if (_leaseSet.getType() != DatabaseEntry.KEY_TYPE_LEASESET) {
+                    LeaseSet2 ls2 = (LeaseSet2) _leaseSet;
+                    shouldFetch = !ls2.isUnpublished() || ls2.isBlindedWhenPublished();
+                }
+                if (shouldFetch) {
+                    if (_log.shouldInfo())
+                        _log.info(getJobId() + ": RAP LS, firing search: " + _leaseSet.getHash().toBase32());
+                    LookupLeaseSetFailedJob failed = new LookupLeaseSetFailedJob(getContext());
+                    getContext().netDb().lookupLeaseSetRemotely(_leaseSet.getHash(), success, failed,
+                                                                LS_LOOKUP_TIMEOUT, _from.calculateHash());
+                } else {
+                    dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET);
+                }
+                return;
+            }
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug(getJobId() + ": Send outbound client message - leaseSet found locally for " + _toString);
             if (!_leaseSet.isCurrent(Router.CLOCK_FUDGE_FACTOR / 4)) {
@@ -405,13 +422,17 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
      */
     private int getNextLease() {
         // set in runJob if found locally
-        if (_leaseSet == null) {
+        if (_leaseSet == null || !_leaseSet.getReceivedAsReply()) {
             _leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
             if (_leaseSet == null) {
                 // shouldn't happen
                 if (_log.shouldLog(Log.WARN))
                     _log.warn(getJobId() + ": Lookup locally didn't find the leaseSet for " + _toString);
                 return MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
+            } else if (_leaseSet.getReceivedAsPublished()) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn(getJobId() + ": Only have RAP LS for " + _toString);
+                return MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
             } 
         } 
 
@@ -571,8 +592,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
                     _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");
+                if (_log.shouldInfo())
+                    _log.info("Unable to send to " + _toString + ", no LS found");
                 cause = MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
             }
 
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 3915c5c849..4a7dafbb97 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/HandleFloodfillDatabaseStoreMessageJob.java
@@ -116,16 +116,16 @@ class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
                 } else if (match.getEarliestLeaseDate() < ls.getEarliestLeaseDate()) {
                     wasNew = true;
                     // If it is in our keyspace and we are talking to it
-                    if (match.getReceivedAsPublished())
-                        ls.setReceivedAsPublished(true);
+                    //if (match.getReceivedAsPublished())
+                    //    ls.setReceivedAsPublished(true);
                 } else if (type != DatabaseEntry.KEY_TYPE_LEASESET &&
                            match.getType() != DatabaseEntry.KEY_TYPE_LEASESET) {
                     LeaseSet2 ls2 = (LeaseSet2) ls;
                     LeaseSet2 match2 = (LeaseSet2) match;
                     if (match2.getPublished() < ls2.getPublished()) {
                         wasNew = true;
-                        if (match.getReceivedAsPublished())
-                            ls.setReceivedAsPublished(true);
+                        //if (match.getReceivedAsPublished())
+                        //    ls.setReceivedAsPublished(true);
                     } else {
                         wasNew = false;
                     }
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 d248494e2b..460b557260 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
@@ -602,8 +602,27 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad
     public void lookupLeaseSetRemotely(Hash key, Hash fromLocalDest) {
         if (!_initialized) return;
         key = _blindCache.getHash(key);
+        if (isNegativeCached(key))
+            return;
         search(key, null, null, 20*1000, true, fromLocalDest);
     }
+    
+    /**
+     *  Unconditionally lookup using the client's tunnels.
+     *
+     *  @param fromLocalDest use these tunnels for the lookup, or null for exploratory
+     *  @param onFindJob may be null
+     *  @param onFailedLookupJob may be null
+     *  @since 0.9.47
+     */
+    public void lookupLeaseSetRemotely(Hash key, Job onFindJob, Job onFailedLookupJob,
+                                       long timeoutMs, Hash fromLocalDest) {
+        if (!_initialized) return;
+        key = _blindCache.getHash(key);
+        if (isNegativeCached(key))
+            return;
+        search(key, onFindJob, onFailedLookupJob, timeoutMs, true, fromLocalDest);
+    }
 
     /**
      *  Use lookupDestination() if you don't need the LS or don't need it validated.
@@ -936,6 +955,16 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad
             rv = (LeaseSet)_ds.get(key);
             if ( (rv != null) && (rv.equals(leaseSet)) ) {
                 // if it hasn't changed, no need to do anything
+                // except copy over the flags
+                Hash to = leaseSet.getReceivedBy();
+                if (to != null) {
+                    rv.setReceivedBy(to);
+                } else if (leaseSet.getReceivedAsReply()) {
+                    rv.setReceivedAsReply();
+                }
+                if (leaseSet.getReceivedAsPublished()) {
+                    rv.setReceivedAsPublished(true);
+                }
                 return rv;
             }
         } catch (ClassCastException cce) {
diff --git a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java
index ad39d4fda7..ff13ce889a 100644
--- a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java
+++ b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java
@@ -125,7 +125,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
                         // allow DSM of our own key (used by FloodfillVerifyStoreJob)
                         // or other keys (used by IterativeSearchJob)
                         // as long as there's no reply token (we will never set a reply token but an attacker might)
-                        ((LeaseSet)dsm.getEntry()).setReceivedAsReply();
+                        ((LeaseSet)dsm.getEntry()).setReceivedBy(_client);
                     }
                     break;
 
@@ -155,7 +155,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
                         return;
                     }
                     if (dsm.getEntry().isLeaseSet())
-                        ((LeaseSet)dsm.getEntry()).setReceivedAsReply();
+                        ((LeaseSet)dsm.getEntry()).setReceivedBy(_client);
                     break;
 
                 case DatabaseSearchReplyMessage.MESSAGE_TYPE:
@@ -264,7 +264,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
                                     // Or, it's a normal LS bundled with data and a MessageStatusMessage.
 
                                     // ... and inject it.
-                                    ((LeaseSet)dsm.getEntry()).setReceivedAsReply();
+                                    ((LeaseSet)dsm.getEntry()).setReceivedBy(_client);
                                     if (_log.shouldLog(Log.INFO))
                                         _log.info("Storing garlic LS down tunnel for: " + dsm.getKey() + " sent to: " +
                                                   (_client != null ? _client.toBase32() : "router"));
-- 
GitLab