From 1e89fac192eafb3935d67333f0339704f6d4afde Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Wed, 18 Nov 2015 18:12:23 +0000
Subject: [PATCH] SSU: Add support for requesting a relay tag via Session
 Request extended options (ticket #1465)

---
 .../transport/udp/EstablishmentManager.java   | 13 +++++--
 .../transport/udp/InboundEstablishState.java  | 12 +++++++
 .../transport/udp/OutboundEstablishState.java | 18 +++++++++-
 .../router/transport/udp/PacketBuilder.java   | 19 ++++++----
 .../i2p/router/transport/udp/UDPPacket.java   |  8 +++++
 .../router/transport/udp/UDPPacketReader.java | 35 ++++++++++++++-----
 6 files changed, 86 insertions(+), 19 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 53e7e9d8dd..5ec58a9e9c 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -131,8 +131,11 @@ class EstablishmentManager {
      * Java I2P has always parsed the length of the extended options field,
      * but i2pd hasn't recognized it until this release.
      * No matter, the options weren't defined until this release anyway.
+     *
+     * FIXME 0.9.22 for testing, change to 0.9.24 for release
+     *
      */
-    private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.24";
+    private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.22";
 
 
     public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
@@ -367,8 +370,11 @@ class EstablishmentManager {
                     }
                     boolean allowExtendedOptions = VersionComparator.comp(toRouterInfo.getVersion(),
                                                                           VERSION_ALLOW_EXTENDED_OPTIONS) >= 0;
+                    // w/o ext options, it's always 'requested', no need to set
+                    boolean requestIntroduction = allowExtendedOptions && _transport.introducersRequired();
                     state = new OutboundEstablishState(_context, maybeTo, to,
                                                        toIdentity, allowExtendedOptions,
+                                                       requestIntroduction,
                                                        sessionKey, addr, _transport.getDHFactory());
                     OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state);
                     boolean isNew = oldState == null;
@@ -488,8 +494,9 @@ class EstablishmentManager {
             // Don't offer to relay to privileged ports.
             // Only offer for an IPv4 session.
             // TODO if already we have their RI, only offer if they need it (no 'C' cap)
-            // TODO if extended options, only if they asked for it
-            if (_transport.canIntroduce() && state.getSentPort() >= 1024 &&
+            // if extended options, only if they asked for it
+            if (state.isIntroductionRequested() &&
+                _transport.canIntroduce() && state.getSentPort() >= 1024 &&
                 state.getSentIP().length == 4) {
                 // ensure > 0
                 long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
index 590aa29ed2..96d45c216b 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
@@ -62,6 +62,8 @@ class InboundEstablishState {
     private final Queue<OutNetMessage> _queuedMessages;
     // count for backoff
     private int _createdSentCount;
+    // default true
+    private boolean _introductionRequested = true;
     
     public enum InboundState {
         /** nothin known yet */
@@ -150,6 +152,10 @@ class InboundEstablishState {
         if (_bobIP == null)
             _bobIP = new byte[req.readIPSize()];
         req.readIP(_bobIP, 0);
+        byte[] ext = req.readExtendedOptions();
+        if (ext != null && ext.length >= UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH) {
+            _introductionRequested = (ext[1] & (byte) UDPPacket.SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG) != 0;
+        }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Receive sessionRequest, BobIP = " + Addresses.toString(_bobIP));
         if (_currentState == InboundState.IB_STATE_UNKNOWN)
@@ -160,6 +166,12 @@ class InboundEstablishState {
     public synchronized boolean sessionRequestReceived() { return _receivedX != null; }
     public synchronized byte[] getReceivedX() { return _receivedX; }
     public synchronized byte[] getReceivedOurIP() { return _bobIP; }
+    /**
+     *  True (default) if no extended options in session request,
+     *  or value of flag bit in the extended options.
+     *  @since 0.9.24
+     */
+    public synchronized boolean isIntroductionRequested() { return _introductionRequested; }
     
     /**
      *  Generates session key and mac key.
diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
index cfb0d2f577..d849bb836b 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
@@ -57,6 +57,7 @@ class OutboundEstablishState {
     private final RemoteHostId _claimedAddress;
     private final RouterIdentity _remotePeer;
     private final boolean _allowExtendedOptions;
+    private final boolean _needIntroduction;
     private final SessionKey _introKey;
     private final Queue<OutNetMessage> _queuedMessages;
     private OutboundState _currentState;
@@ -108,12 +109,16 @@ class OutboundEstablishState {
      *  @param claimedAddress an IP/port based RemoteHostId, or null if unknown
      *  @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
      *  @param remotePeer must have supported sig type
+     *  @param allowExtenededOptions are we allowed to send extended options to Bob?
+     *  @param needIntroduction should we ask Bob to be an introducer for us?
+               ignored unless allowExtendedOptions is true
      *  @param introKey Bob's introduction key, as published in the netdb
      *  @param addr non-null
      */
     public OutboundEstablishState(RouterContext ctx, RemoteHostId claimedAddress,
                                   RemoteHostId remoteHostId,
                                   RouterIdentity remotePeer, boolean allowExtendedOptions,
+                                  boolean needIntroduction,
                                   SessionKey introKey, UDPAddress addr,
                                   DHSessionKeyBuilder.Factory dh) {
         _context = ctx;
@@ -128,6 +133,7 @@ class OutboundEstablishState {
         _claimedAddress = claimedAddress;
         _remoteHostId = remoteHostId;
         _allowExtendedOptions = allowExtendedOptions;
+        _needIntroduction = needIntroduction;
         _remotePeer = remotePeer;
         _introKey = introKey;
         _queuedMessages = new LinkedBlockingQueue<OutNetMessage>();
@@ -161,8 +167,18 @@ class OutboundEstablishState {
     /** @return -1 if unset */
     public long getIntroNonce() { return _introductionNonce; }
 
-    /** @since 0.9.24 */
+    /**
+     *  Are we allowed to send extended options to this peer?
+     *  @since 0.9.24
+     */
     public boolean isExtendedOptionsAllowed() { return _allowExtendedOptions; }
+
+    /**
+     *  Should we ask this peer to be an introducer for us?
+     *  Ignored unless allowExtendedOptions is true
+     *  @since 0.9.24
+     */
+    public boolean needIntroduction() { return _needIntroduction; }
     
     /**
      *  Queue a message to be sent after the session is established.
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
index 594388716b..a777d819b7 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
@@ -783,15 +783,20 @@ class PacketBuilder {
      * @return ready to send packet, or null if there was a problem
      */
     public UDPPacket buildSessionRequestPacket(OutboundEstablishState state) {
-        // TODO
-        // boolean ext = state.isExtendedOptionsAllowed();
-        // if (ext)
-        //byte[] options = new byte[3];
-        //UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE, options);
-        UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE);
+        int off = HEADER_SIZE;
+        byte[] options;
+        boolean ext = state.isExtendedOptionsAllowed();
+        if (ext) {
+            options = new byte[UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH];
+            if (state.needIntroduction())
+                options[1] = (byte) UDPPacket.SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG;
+            off += UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH + 1;
+        } else {
+            options = null;
+        }
+        UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE, options);
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
-        int off = HEADER_SIZE; // + 1 + options.length;
 
         byte toIP[] = state.getSentIP();
         if (!_transport.isValid(toIP)) {
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
index fae13ab1ff..a68b848201 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
@@ -101,6 +101,14 @@ class UDPPacket implements CDQEntry {
      */
     public static final byte HEADER_FLAG_EXTENDED_OPTIONS = (1 << 2);
 
+    // Extended options for session request
+    public static final int SESS_REQ_MIN_EXT_OPTIONS_LENGTH = 2;
+    // bytes 0-1 are flags
+    /**
+     * set to 1 to request a session tag, i.e. we want him to be an introducer for us
+     */
+    public static final int SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG = 0x01;
+
     // various flag fields for use in the data packets
     public static final byte DATA_FLAG_EXPLICIT_ACK = (byte)(1 << 7);
     public static final byte DATA_FLAG_ACK_BITFIELDS = (1 << 6);
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
index 34f8d7bf7b..616f48c4ae 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
@@ -104,6 +104,7 @@ class UDPPacketReader {
     
     /**
      * Returns extended option data, 0-255 bytes, or null if none.
+     * Returned array does NOT include the length byte.
      *
      * @return extended options or null if none is included
      * @since 0.9.24
@@ -175,8 +176,26 @@ class UDPPacketReader {
     
     /* ------- Begin Reader Classes ------- */
 
+    /**
+     * Base
+     *
+     * @since 0.9.24
+     */
+    public abstract class Reader {
+        /**
+         * Returns extended option data from the header, 0-255 bytes, or null if none.
+         * Returned array does NOT include the length byte.
+         *
+         * @return extended options or null if none is included
+         * @since 0.9.24
+         */
+        public byte[] readExtendedOptions() {
+            return UDPPacketReader.this.readExtendedOptions();
+        }
+    }
+
     /** Help read the SessionRequest payload */
-    public class SessionRequestReader {
+    public class SessionRequestReader extends Reader {
         public static final int X_LENGTH = 256;
         public void readX(byte target[], int targetOffset) {
             int readOffset = readBodyOffset();
@@ -198,7 +217,7 @@ class UDPPacketReader {
     }
     
     /** Help read the SessionCreated payload */
-    public class SessionCreatedReader {
+    public class SessionCreatedReader extends Reader {
         public static final int Y_LENGTH = 256;
         public void readY(byte target[], int targetOffset) {
             int readOffset = readBodyOffset();
@@ -253,7 +272,7 @@ class UDPPacketReader {
     }
     
     /** parse out the confirmed message */
-    public class SessionConfirmedReader {
+    public class SessionConfirmedReader extends Reader {
         /** which fragment is this? */
         public int readCurrentFragmentNum() {
             int readOffset = readBodyOffset();
@@ -306,7 +325,7 @@ class UDPPacketReader {
     }
     
     /** parse out the data message */
-    public class DataReader {
+    public class DataReader extends Reader {
 
         /**
          *  @return the data size, NOT including IP header, UDP header, IV, or MAC
@@ -642,7 +661,7 @@ class UDPPacketReader {
     }
     
     /** Help read the PeerTest payload */
-    public class PeerTestReader {
+    public class PeerTestReader extends Reader {
         private static final int NONCE_LENGTH = 4;
         
         public long readNonce() {
@@ -683,7 +702,7 @@ class UDPPacketReader {
     }
     
     /** Help read the RelayRequest payload */
-    public class RelayRequestReader {
+    public class RelayRequestReader extends Reader {
         public long readTag() { 
             long rv = DataHelper.fromLong(_message, readBodyOffset(), 4); 
             if (_log.shouldLog(Log.DEBUG))
@@ -767,7 +786,7 @@ class UDPPacketReader {
     }
     
     /** Help read the RelayIntro payload */
-    public class RelayIntroReader {
+    public class RelayIntroReader extends Reader {
         public int readIPSize() {
             int offset = readBodyOffset();
             return _message[offset] & 0xff;
@@ -808,7 +827,7 @@ class UDPPacketReader {
     
     
     /** Help read the RelayResponse payload */
-    public class RelayResponseReader {
+    public class RelayResponseReader extends Reader {
         public int readCharlieIPSize() {
             int offset = readBodyOffset();
             return _message[offset] & 0xff;
-- 
GitLab