From 014d39c097b51ad8a2f621428263eb48ffcb861b Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Wed, 10 Jan 2024 17:04:32 +0000
Subject: [PATCH] SSU2: Delay sending relay tag

---
 .../transport/udp/EstablishmentManager.java   | 22 +++++++----
 .../transport/udp/InboundEstablishState2.java | 38 ++++++++++++++++++-
 .../i2p/router/transport/udp/PeerState2.java  | 35 ++++++++++++++---
 3 files changed, 81 insertions(+), 14 deletions(-)

diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
index f65660b498..9bb132e4c9 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -842,14 +842,18 @@ class EstablishmentManager {
             else
                 _log.debug("Receive DUP session/token request from: " + state);
         }
+
+        // Wait until we have RI
+        // sentRelayTag remains 0 and will not be sent in SessionConfirmed
+        // See InboundEstablishState2
         // call for both Session and Token request, why not
-        if (state.isIntroductionRequested() &&
-            state.getSentRelayTag() == 0 &&     // only set once
-            state.getSentPort() >= 1024 &&
-            _transport.canIntroduce(state.getSentIP().length == 16)) {
-            long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
-            state.setSentRelayTag(tag);
-        }
+        //if (state.isIntroductionRequested() &&
+        //    state.getSentRelayTag() == 0 &&     // only set once
+        //    state.getSentPort() >= 1024 &&
+        //    _transport.canIntroduce(state.getSentIP().length == 16)) {
+        //    long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
+        //    state.setSentRelayTag(tag);
+        //}
         notifyActivity();
     }
 
@@ -1175,11 +1179,13 @@ class EstablishmentManager {
             peer = new PeerState(_context, _transport,
                                  state.getSentIP(), state.getSentPort(), remote.calculateHash(), true, state.getRTT(),
                                  state.getCipherKey(), state.getMACKey());
+            peer.setWeRelayToThemAs(state.getSentRelayTag());
         } else {
             InboundEstablishState2 state2 = (InboundEstablishState2) state;
             peer = state2.getPeerState();
+            // now handled in IES2.createPeerState()
+            //peer.setWeRelayToThemAs(state.getSentRelayTag());
         }
-        peer.setWeRelayToThemAs(state.getSentRelayTag());
 
         if (version == 1) {
             // Lookup the peer's MTU from the netdb, since it isn't included in the protocol setup (yet)
diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
index 16ef065567..e102c62c6e 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
@@ -25,12 +25,14 @@ import net.i2p.data.SessionKey;
 import net.i2p.data.i2np.I2NPMessage;
 import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterInfo;
+import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.router.transport.TransportImpl;
 import static net.i2p.router.transport.udp.SSU2Util.*;
 import net.i2p.util.Addresses;
 import net.i2p.util.Log;
+import net.i2p.util.VersionComparator;
 
 /**
  * Data for a new connection being established, where the remote peer has
@@ -63,6 +65,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
     // testing
     private static final boolean ENFORCE_TOKEN = true;
     private static final long MAX_SKEW = 2*60*1000L;
+    // SSU2 fixes (2.1.0)
+    private static final String MIN_RELAY_VERSION = "0.9.57";
 
 
     /**
@@ -398,6 +402,32 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         }
 
         _receivedConfirmedIdentity = _receivedUnconfirmedIdentity;
+        // deferred relay tag request handling, now that we have the RI
+        // formerly in EstablishmentManager.receiveSessionOrTokenReques()
+        if (_introductionRequested) {
+            if (getSentPort() < 1024 ||
+                !_transport.canIntroduce(isIPv6)) {
+                _introductionRequested = false;
+            } else if (VersionComparator.comp(ri.getVersion(), MIN_RELAY_VERSION) < 0) {
+                _introductionRequested = false;
+                String caps = ri.getCapabilities();
+                if (_log.shouldWarn())
+                    _log.warn("Not offering to relay to router version " + ri.getVersion() + " caps " + caps + ": " + this);
+            } else {
+                String caps = ri.getCapabilities();
+                // may be requesting relay for ipv4/6 if reachable on the other
+                // or may be starting up and not know if reachable or not
+                if (caps.indexOf(Router.CAPABILITY_REACHABLE) < 0 ||
+                    _context.random().nextInt(4) == 0) {
+                    // leave it set to true; createPeerState() will copy to PS2,
+                    // who will send the relay tag with ACK 0
+                } else {
+                    _introductionRequested = false;
+                    if (_log.shouldWarn())
+                        _log.warn("Not offering to relay to router version " + ri.getVersion() + " caps " + caps + ": " + this);
+                }
+            }
+        }
         createPeerState();
         //_sendHeaderEncryptKey2 calculated below
     }
@@ -419,7 +449,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
 
     public void gotRelayTagRequest() {
         if (_log.shouldDebug())
-            _log.debug("Got relay tag request");
+            _log.debug("Got relay tag request on " + this);
         _introductionRequested = true;
     }
 
@@ -877,6 +907,12 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         RouterAddress ra = _transport.getCurrentExternalAddress(isIPv6);
         if (ra != null)
             _pstate.setOurAddress(ra.getIP(), ra.getPort());
+        if (_introductionRequested) {
+            long tag = 1 + _context.random().nextLong(EstablishmentManager.MAX_TAG_VALUE);
+            setSentRelayTag(tag);
+            _pstate.setWeRelayToThemAs(tag);
+        }
+        _pstate.sendAck0();
     }
 
     /**
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState2.java b/router/java/src/net/i2p/router/transport/udp/PeerState2.java
index 608c147f3f..ef46abe2ce 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java
@@ -115,6 +115,8 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
 
 
     /**
+     *  If inbound, caller MUST immediately call setWeRelayToThemAs() (if nonzero) and sendAck0().
+     *
      *  @param rtt from the EstablishState, or 0 if not available
      */
     public PeerState2(RouterContext ctx, UDPTransport transport,
@@ -135,12 +137,9 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
         _sentMessages = new ConcurrentHashMap<Long, List<PacketBuilder.Fragment>>(32);
         _sentMessagesLastExpired = _keyEstablishedTime;
         if (isInbound) {
-            // Send immediate ack of Session Confirmed
+            // Prep for immediate ack of Session Confirmed
             _receivedMessages.set(0);
-            try {
-                UDPPacket ack = transport.getBuilder2().buildACK(this);
-                transport.send(ack);
-            } catch (IOException ioe) {}
+            // ACK 0 now sent in sendAck0() below
         } else {
             // For outbound, SessionConfirmed is packet 0
             _packetNumber.set(1);
@@ -148,6 +147,32 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
         _ackTimer = new ACKTimer();
     }
 
+    /**
+     *  Send immediate ACK 0 of Session Confirmed. Inbound only.
+     *  Bundle relay tag if requested, see InboundEstablishState2.
+     *
+     *  @since 0.9.62
+     */
+    void sendAck0() {
+        if (!_isInbound)
+            return;
+        long tag = getWeRelayToThemAs();
+        try {
+            UDPPacket pkt;
+            if (tag > 0) {
+                SSU2Payload.Block block = new SSU2Payload.RelayTagBlock(tag);
+                pkt = _transport.getBuilder2().buildPacket(Collections.<Fragment>emptyList(),
+                                                           Collections.singletonList(block),
+                                                           this);
+                if (_log.shouldInfo())
+                    _log.info("Sending ack 0 with tag " + tag + " on " + this);
+            } else {
+                pkt = _transport.getBuilder2().buildACK(this);
+            }
+            _transport.send(pkt);
+        } catch (IOException ioe) {}
+    }
+
     // SSU 1 overrides
 
     @Override
-- 
GitLab