From 3c4956f0c3f8b5ddf853b6fbde62b0d2c3dd513d Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Sun, 19 Jun 2022 12:04:10 -0400
Subject: [PATCH] SSU2: Add delayed lookup of RI for relay and peer test

Prefer SSU2 introducers for slots 1 and 2
log tweaks
---
 .../transport/udp/IntroductionManager.java    | 85 +++++++++++++++++--
 .../router/transport/udp/PeerTestManager.java | 65 +++++++++++++-
 2 files changed, 142 insertions(+), 8 deletions(-)

diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
index 66a7d3e407..082b90b398 100644
--- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
@@ -27,6 +27,7 @@ import net.i2p.router.RouterContext;
 import net.i2p.router.transport.TransportUtil;
 import net.i2p.util.Addresses;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleTimer2;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -235,11 +236,18 @@ class IntroductionManager {
                 Introducer intro;
                 byte[] key = ua.getIntroducerKey(i);
                 if (key != null) {
+                    // SSU 1
+                    //// debug, replace SSU 1 with SSU 2 if available for slots 1-2
+                    //// leave slot 0 for SSU 1
+                    //// this will churn the SSU 1 introducers, oh well
+                    if (preferV2 && i > 0)
+                        continue;
                     intro = new Introducer(ua.getIntroducerHost(i).getAddress(),
                                            ua.getIntroducerPort(i), key, tag, sexp);
                     if (_log.shouldInfo())
                         _log.info("Reusing introducer: " + ua.getIntroducerHost(i));
                 } else {
+                    // SSU 2
                     intro = new Introducer(ua.getIntroducerHash(i), tag, sexp);
                     if (_log.shouldInfo())
                         _log.info("Reusing introducer: " + ua.getIntroducerHash(i));
@@ -879,6 +887,73 @@ class IntroductionManager {
      *  @since 0.9.55
      */
     void receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data) {
+        receiveRelayIntro(bob, alice, data, 0);
+    }
+
+    /**
+     *  We are Charlie and we got this from Bob.
+     *  Bob should have sent us the RI, but maybe it's in the block
+     *  after this, or maybe it's in a different packet.
+     *  Check for RI, if not found, return true to retry, unless retryCount is at the limit.
+     *  Creates the timer if retryCount == 0.
+     *
+     *  SSU 2 only.
+     *
+     *  @return true if RI found, false to delay and retry.
+     *  @since 0.9.55
+     */
+    private boolean receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, int retryCount) {
+        RouterInfo aliceRI = null;
+        if (retryCount < 5 && !_context.banlist().isBanlisted(alice)) {
+            aliceRI = _context.netDb().lookupRouterInfoLocally(alice);
+            if (aliceRI == null) {
+                if (_log.shouldInfo())
+                    _log.info("Delay after " + retryCount + " retries, no RI for " + alice.toBase64());
+                if (retryCount == 0)
+                    new DelayIntro(bob, alice, data);
+                return false;
+            }
+        }
+        receiveRelayIntro(bob, alice, data, aliceRI);
+        return true;
+    }
+
+    /** 
+     * Wait for RI.
+     * @since 0.9.55
+     */
+    private class DelayIntro extends SimpleTimer2.TimedEvent {
+        private final PeerState2 bob;
+        private final Hash alice;
+        private final byte[] data;
+        private volatile int count;
+        private static final long DELAY = 50;
+
+        public DelayIntro(PeerState2 b, Hash a, byte[] d) {
+            super(_context.simpleTimer2());
+            bob = b;
+            alice = a;
+            data = d;
+            schedule(DELAY);
+        }
+
+        public void timeReached() {
+            boolean ok = receiveRelayIntro(bob, alice, data, ++count);
+            if (!ok)
+                reschedule(DELAY << count);
+        }
+    }
+
+    /**
+     *  We are Charlie and we got this from Bob.
+     *  Send a HolePunch to Alice, who will soon be sending us a SessionRequest.
+     *  And send a RelayResponse to bob.
+     *
+     *  SSU 2 only.
+     *
+     *  @since 0.9.55
+     */
+    private void receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, RouterInfo aliceRI) {
         long nonce = DataHelper.fromLong(data, 0, 4);
         long tag = DataHelper.fromLong(data, 4, 4);
         long time = DataHelper.fromLong(data, 8, 4) * 1000;
@@ -912,7 +987,6 @@ class IntroductionManager {
             return;
         }
 
-        RouterInfo aliceRI = null;
         SessionKey aliceIntroKey = null;
         int rcode;
         PeerState aps = _transport.getPeerState(alice);
@@ -927,8 +1001,7 @@ class IntroductionManager {
             rcode = SSU2Util.RELAY_REJECT_CHARLIE_ADDRESS;
         } else {
             // bob should have sent it to us. Don't bother to lookup
-            // remotely if he didn't, or it was out-of-order or lost.
-            aliceRI = _context.netDb().lookupRouterInfoLocally(alice);
+            // remotely if he didn't, or it was lost.
             if (aliceRI != null) {
                 // validate signed data
                 SigningPublicKey spk = aliceRI.getIdentity().getSigningPublicKey();
@@ -946,7 +1019,7 @@ class IntroductionManager {
                 }
             } else {
                 if (_log.shouldWarn())
-                    _log.warn("Alice RI not found " + alice);
+                    _log.warn("Alice RI not found " + alice + " for relay intro from " + bob);
                 rcode = SSU2Util.RELAY_REJECT_CHARLIE_UNKNOWN_ALICE;
             }
         }
@@ -985,8 +1058,8 @@ class IntroductionManager {
              return;
         }
         UDPPacket packet = _builder2.buildRelayResponse(data, bob);
-        if (_log.shouldDebug())
-            _log.debug("Send relay response " + rcode + " as charlie " + " nonce " + nonce + " to bob " + bob);
+        if (_log.shouldInfo())
+            _log.info("Send relay response " + rcode + " as charlie " + " nonce " + nonce + " to bob " + bob);
         _transport.send(packet);
         if (rcode == SSU2Util.RELAY_ACCEPT) {
             // send hole punch with the same data we sent to Bob
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
index d7d9e1c1a9..c03982cd4b 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
@@ -32,6 +32,7 @@ import net.i2p.util.Addresses;
 import net.i2p.util.Log;
 import net.i2p.util.HexDump;
 import net.i2p.util.SimpleTimer;
+import net.i2p.util.SimpleTimer2;
 import net.i2p.util.VersionComparator;
 
 /**
@@ -838,7 +839,67 @@ class PeerTestManager {
      * @since 0.9.54
      */
     public void receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, int status, Hash h, byte[] data) {
-        receiveTest(from, fromPeer, msg, status, h, data, null, 0);
+        if (status == 0 && (msg == 2 || msg == 4) && !_context.banlist().isBanlisted(h))
+            receiveTest(from, fromPeer, msg, h, data, 0);
+        else
+            receiveTest(from, fromPeer, msg, status, h, data, null, 0);
+    }
+
+    /**
+     * Status 0 only, Msg 2 and 4 only, SSU 2 only.
+     * Bob should have sent us the RI, but maybe it's in the block
+     * after this, or maybe it's in a different packet.
+     * Check for RI, if not found, return true to retry, unless retryCount is at the limit.
+     * Creates the timer if retryCount == 0.
+     *
+     * We are Alice for msg 4, Charlie for msg 2.
+     *
+     * @return true if RI found, false to delay and retry.
+     * @since 0.9.55
+     */
+    private boolean receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, Hash h, byte[] data, int retryCount) {
+        if (retryCount < 5) {
+            RouterInfo ri = _context.netDb().lookupRouterInfoLocally(h);
+            if (ri == null) {
+                if (_log.shouldInfo())
+                    _log.info("Delay after " + retryCount + " retries, no RI for " + h.toBase64());
+                if (retryCount == 0)
+                    new DelayTest(from, fromPeer, msg, h, data);
+                return false;
+            }
+        }
+        receiveTest(from, fromPeer, msg, 0, h, data, null, 0);
+        return true;
+    }
+
+    /** 
+     * Wait for RI.
+     * @since 0.9.55
+     */
+    private class DelayTest extends SimpleTimer2.TimedEvent {
+        private final RemoteHostId from;
+        private final PeerState2 fromPeer;
+        private final int msg;
+        private final Hash hash;
+        private final byte[] data;
+        private volatile int count;
+        private static final long DELAY = 50;
+
+        public DelayTest(RemoteHostId f, PeerState2 fp, int m, Hash h, byte[] d) {
+            super(_context.simpleTimer2());
+            from = f;
+            fromPeer = fp;
+            msg = m;
+            hash = h;
+            data = d;
+            schedule(DELAY);
+        }
+
+        public void timeReached() {
+            boolean ok = receiveTest(from, fromPeer, msg, hash, data, ++count);
+            if (!ok)
+                reschedule(DELAY << count);
+        }
     }
 
     /**
@@ -1102,7 +1163,7 @@ class PeerTestManager {
                         }
                     } else {
                         if (_log.shouldWarn())
-                            _log.warn("Alice RI not found " + h);
+                            _log.warn("Alice RI not found " + h + " for peer test from " + fromPeer);
                         rcode = SSU2Util.TEST_REJECT_CHARLIE_UNKNOWN_ALICE;
                     }
                 }
-- 
GitLab