From a6f61d2bf65ee95d35ca516ddb32db70d6fab95e Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Sun, 6 Mar 2022 06:15:23 -0500
Subject: [PATCH] SSU2: Fixes part 1

after initial testnet testing

Use correct intro key for Session/Token request
Fix state management in EstablishmentManager, OES2, IES2
Fix next send time during handshake
Fix header decryption in PacketHandler
Add additional packet checks in IES2 handling
Remove expired IES immediately (SSU1 also)
Failsafe sleep in EstablishmentManager on exception
Remove dup requestSent() calls
Don't release packet in PS2
Log tweaks and javadocs
---
 .../transport/udp/EstablishmentManager.java   | 129 +++++++++++++++---
 .../transport/udp/InboundEstablishState.java  |   4 +-
 .../transport/udp/InboundEstablishState2.java |  33 +++--
 .../transport/udp/OutboundEstablishState.java |   9 +-
 .../udp/OutboundEstablishState2.java          |  68 ++++++---
 .../router/transport/udp/PacketBuilder2.java  |  28 ++--
 .../router/transport/udp/PacketHandler.java   |  45 +++---
 .../i2p/router/transport/udp/PeerState2.java  |  24 ++--
 .../i2p/router/transport/udp/SSU2Header.java  |   2 +
 9 files changed, 235 insertions(+), 107 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 92173a10d5..7fba352139 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -10,6 +10,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import net.i2p.data.Base64;
 import net.i2p.data.Hash;
 import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterIdentity;
@@ -382,7 +383,17 @@ class EstablishmentManager {
                     }
                 } else {
                     // must have a valid session key
-                    byte[] keyBytes = addr.getIntroKey();
+                    byte[] keyBytes;
+                    int version = _transport.getSSUVersion(ra);
+                    if (version == 1) {
+                        keyBytes = addr.getIntroKey();
+                    } else {
+                        String siv = ra.getOption("i");
+                        if (siv != null)
+                            keyBytes = Base64.decode(siv);
+                        else
+                            keyBytes = null;
+                    }
                     if (keyBytes == null) {
                         _transport.markUnreachable(toHash);
                         _transport.failed(msg, "Peer has no key, cannot establish");
@@ -403,7 +414,6 @@ class EstablishmentManager {
                     // don't ask if they are indirect
                     boolean requestIntroduction = allowExtendedOptions && !isIndirect &&
                                                   _transport.introducersMaybeRequired(TransportUtil.isIPv6(ra));
-                    int version = _transport.getSSUVersion(ra);
                     if (version == 1) {
                         state = new OutboundEstablishState(_context, maybeTo, to,
                                                        toIdentity, allowExtendedOptions,
@@ -751,7 +761,7 @@ class EstablishmentManager {
      */
     void receiveRetry(OutboundEstablishState2 state, UDPPacket packet) {
         try {
-            state.receiveSessionCreated(packet);
+            state.receiveRetry(packet);
         } catch (GeneralSecurityException gse) {
             if (_log.shouldWarn())
                 _log.warn("Corrupt Retry from: " + state, gse);
@@ -760,7 +770,7 @@ class EstablishmentManager {
         }
         notifyActivity();
         if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Receive retry from: " + state);
+            _log.debug("Receive retry with token " + state.getToken() + " from: " + state);
     }
 
     /**
@@ -1108,15 +1118,20 @@ class EstablishmentManager {
     public static final long MAX_TAG_VALUE = 0xFFFFFFFFl;
     
     /**
-     *  This may be called more than once
+     *  This handles both initial send and retransmission of Session Created,
+     *  and, for SSU2, send of Retry.
+     *  Retry is never retransmnitted.
+     *
+     *  This may be called more than once.
+     *
+     *  Caller must synch on state.
      */
     private void sendCreated(InboundEstablishState state) {
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Send created to: " + state);
-        
         int version = state.getVersion();
         UDPPacket pkt;
         if (version == 1) {
+            if (_log.shouldDebug())
+                _log.debug("Send created to: " + state);
             try {
                 state.generateSessionKey();
             } catch (DHSessionKeyBuilder.InvalidPublicParameterException ippe) {
@@ -1130,13 +1145,27 @@ class EstablishmentManager {
                                                      _transport.getExternalPort(state.getSentIP().length == 16),
                                                      _transport.getIntroKey());
         } else {
-            // if already sent, get from the state to retx
             InboundEstablishState2 state2 = (InboundEstablishState2) state;
             InboundEstablishState.InboundState istate = state2.getState();
-            if (istate == IB_STATE_CREATED_SENT)
+            if (istate == IB_STATE_CREATED_SENT) {
+                if (_log.shouldDebug())
+                    _log.debug("Send created to: " + state);
+                // if already sent, get from the state to retx
                 pkt = state2.getRetransmitSessionCreatedPacket();
-            else
-                pkt = _builder2.buildSessionCreatedPacket((InboundEstablishState2) state);
+            } else if (istate == IB_STATE_REQUEST_RECEIVED) {
+                if (_log.shouldDebug())
+                    _log.debug("Send created to: " + state);
+                pkt = _builder2.buildSessionCreatedPacket(state2);
+            } else if (istate == IB_STATE_TOKEN_REQUEST_RECEIVED ||
+                       istate == IB_STATE_REQUEST_BAD_TOKEN_RECEIVED) {
+                if (_log.shouldDebug())
+                    _log.debug("Send retry to: " + state);
+                pkt = _builder2.buildRetryPacket(state2);
+            } else {
+                if (_log.shouldWarn())
+                    _log.warn("Unhandled state " + istate + " on " + state);
+                return;
+            }
         }
         if (pkt == null) {
             if (_log.shouldLog(Log.WARN))
@@ -1152,23 +1181,44 @@ class EstablishmentManager {
     }
 
     /**
-     *  Caller should probably synch on outboundState
+     *  This handles both initial send and retransmission of Session Request,
+     *  and, for SSU2, initial send and retransmission of Token Request.
+     *
+     *  This may be called more than once.
+     *
+     *  Caller must synch on state.
      */
     private void sendRequest(OutboundEstablishState state) {
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Send SessionRequest to: " + state);
         int version = state.getVersion();
         UDPPacket packet;
         if (version == 1) {
+            if (_log.shouldDebug())
+                _log.debug("Send Session Request to: " + state);
             packet = _builder.buildSessionRequestPacket(state);
         } else {
-            // if already sent, get from the state to retx
             OutboundEstablishState2 state2 = (OutboundEstablishState2) state;
             OutboundEstablishState.OutboundState ostate = state2.getState();
-            if (ostate == OB_STATE_REQUEST_SENT)
+            if (ostate == OB_STATE_REQUEST_SENT ||
+                ostate == OB_STATE_REQUEST_SENT_NEW_TOKEN) {
+                if (_log.shouldDebug())
+                    _log.debug("Send Session Request to: " + state);
+                // if already sent, get from the state to retx
                 packet = state2.getRetransmitSessionRequestPacket();
-            else
+            } else if (ostate == OB_STATE_NEEDS_TOKEN ||
+                       ostate == OB_STATE_TOKEN_REQUEST_SENT) {
+                if (_log.shouldDebug())
+                    _log.debug("Send Token Request to: " + state);
+                packet = _builder2.buildTokenRequestPacket(state2);
+            } else if (ostate == OB_STATE_UNKNOWN ||
+                       ostate == OB_STATE_RETRY_RECEIVED) {
+                if (_log.shouldDebug())
+                    _log.debug("Send Session Request to: " + state);
                 packet = _builder2.buildSessionRequestPacket(state2);
+            } else {
+                if (_log.shouldWarn())
+                    _log.warn("Unhandled state " + ostate + " on " + state);
+                return;
+            }
         }
         if (packet != null) {
             _transport.send(packet);
@@ -1315,7 +1365,8 @@ class EstablishmentManager {
      *  Note that while a SessionConfirmed could in theory be fragmented,
      *  in practice a RouterIdentity is 387 bytes and a single fragment is 512 bytes max,
      *  so it will never be fragmented.
-     *  Caller should probably synch on state.
+     *
+     *  Caller must synch on state.
      */
     private void sendConfirmation(OutboundEstablishState state) {
         boolean valid = state.validateSessionCreated();
@@ -1379,9 +1430,13 @@ class EstablishmentManager {
      *  ack to the SessionConfirmed - otherwise we haven't generated the keys.
      *  Caller should probably synch on state.
      *
+     *  SSU1 only.
+     *
      *  @since 0.9.2
      */
     private void sendDestroy(OutboundEstablishState state) {
+        if (state.getVersion() > 1)
+            return;
         UDPPacket packet = _builder.buildSessionDestroyPacket(state);
         if (packet != null) {
             if (_log.shouldLog(Log.DEBUG))
@@ -1397,9 +1452,13 @@ class EstablishmentManager {
      *  Otherwise we haven't generated the keys.
      *  Caller should probably synch on state.
      *
+     *  SSU1 only.
+     *
      *  @since 0.9.2
      */
     private void sendDestroy(InboundEstablishState state) {
+        if (state.getVersion() > 1)
+            return;
         UDPPacket packet = _builder.buildSessionDestroyPacket(state);
         if (packet != null) {
             if (_log.shouldLog(Log.DEBUG))
@@ -1463,8 +1522,11 @@ class EstablishmentManager {
             //if (_log.shouldLog(Log.DEBUG))
             //    _log.debug("Processing for inbound: " + inboundState);
             synchronized (inboundState) {
-                switch (inboundState.getState()) {
+                InboundEstablishState.InboundState istate = inboundState.getState();
+                switch (istate) {
                   case IB_STATE_REQUEST_RECEIVED:
+                  case IB_STATE_TOKEN_REQUEST_RECEIVED:      // SSU2
+                  case IB_STATE_REQUEST_BAD_TOKEN_RECEIVED:  // SSU2
                     if (expired)
                         processExpired(inboundState);
                     else
@@ -1473,11 +1535,18 @@ class EstablishmentManager {
 
                   case IB_STATE_CREATED_SENT: // fallthrough
                   case IB_STATE_CONFIRMED_PARTIALLY:
+                  case IB_STATE_RETRY_SENT:                  // SSU2
                     if (expired) {
                         sendDestroy(inboundState);
                         processExpired(inboundState);
                     } else if (inboundState.getNextSendTime() <= now) {
-                        sendCreated(inboundState);
+                        if (istate == IB_STATE_RETRY_SENT) {
+                            // Retry is never retransmitted
+                            inboundState.fail();
+                            processExpired(inboundState);
+                        } else {
+                            sendCreated(inboundState);
+                        }
                     }
                     break;
 
@@ -1510,6 +1579,12 @@ class EstablishmentManager {
                     // Can't happen, always call receiveSessionRequest() before putting in map
                     if (_log.shouldLog(Log.ERROR))
                         _log.error("hrm, state is unknown for " + inboundState);
+                    break;
+
+                  default:
+                    if (_log.shouldWarn())
+                        _log.warn("Unhandled state on " + inboundState);
+                    break;
                 }
             }
 
@@ -1585,6 +1660,7 @@ class EstablishmentManager {
                 switch (outboundState.getState()) {
                     case OB_STATE_UNKNOWN:  // fall thru
                     case OB_STATE_INTRODUCED:
+                    case OB_STATE_NEEDS_TOKEN:             // SSU2 only
                         if (expired)
                             processExpired(outboundState);
                         else
@@ -1592,6 +1668,9 @@ class EstablishmentManager {
                         break;
 
                     case OB_STATE_REQUEST_SENT:
+                    case OB_STATE_TOKEN_REQUEST_SENT:      // SSU2 only
+                    case OB_STATE_RETRY_RECEIVED:          // SSU2 only
+                    case OB_STATE_REQUEST_SENT_NEW_TOKEN:  // SSU2 only
                         // no response yet (or it was invalid), lets retry
                         long rtime = outboundState.getRequestSentTime();
                         if (expired || (rtime > 0 && rtime + OB_MESSAGE_TIMEOUT <= now))
@@ -1635,6 +1714,11 @@ class EstablishmentManager {
                     case OB_STATE_VALIDATION_FAILED:
                         processExpired(outboundState);
                         break;
+
+                    default:
+                        if (_log.shouldWarn())
+                            _log.warn("Unhandled state on " + outboundState);
+                        break;
                 }
             }
             
@@ -1695,6 +1779,7 @@ class EstablishmentManager {
      *  @since 0.9.2
      */
     private void processExpired(InboundEstablishState inboundState) {
+        _inboundStates.remove(inboundState.getRemoteHostId());
         OutNetMessage msg;
         while ((msg = inboundState.getNextQueuedMessage()) != null) {
             _transport.failed(msg, "Expired during failed establish");
@@ -1794,6 +1879,8 @@ class EstablishmentManager {
                     doPass();
                 } catch (RuntimeException re) {
                     _log.log(Log.CRIT, "Error in the establisher", re);
+                    // don't loop too fast
+                    try { Thread.sleep(1000); } catch (InterruptedException ie) {}
                 }
             }
             _inboundStates.clear();
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 a60b673e11..a45099c6de 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
@@ -32,8 +32,8 @@ class InboundEstablishState {
     protected final Log _log;
     // SessionRequest message
     private byte _receivedX[];
-    private byte _bobIP[];
-    private final int _bobPort;
+    protected byte _bobIP[];
+    protected final int _bobPort;
     private final DHSessionKeyBuilder _keyBuilder;
     // SessionCreated message
     private byte _sentY[];
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 cc49bb5772..d0687eee64 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
@@ -77,9 +77,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         //_sendHeaderEncryptKey2 set below
         //_rcvHeaderEncryptKey2 set below
         _introductionRequested = false; // todo
-        //_bobIP = TODO
-        //if (_log.shouldLog(Log.DEBUG))
-        //    _log.debug("Receive sessionRequest, BobIP = " + Addresses.toString(_bobIP));
         int off = pkt.getOffset();
         int len = pkt.getLength();
         byte data[] = pkt.getData();
@@ -107,12 +104,18 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         } else if (type == SESSION_REQUEST_FLAG_BYTE &&
                    (token == 0 ||
                     (ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
+            if (_log.shouldInfo())
+                _log.info("Invalid token " + token + " in session request from: " + _aliceSocketAddress);
             _currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
             _sendHeaderEncryptKey2 = introKey;
+            // Generate token for the retry.
+            // We do NOT register it with the EstablishmentManager, it must be used immediately.
             do {
                 token = ctx.random().nextLong();
             } while (token == 0);
             _token = token;
+            // do NOT bother to init the handshake state and decrypt the payload
+            _timeReceived = _establishBegin;
         } else {
             // fast MSB check for key < 2^255
             if ((data[off + LONG_HEADER_SIZE + KEY_LEN - 1] & 0x80) != 0)
@@ -251,7 +254,11 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
     }
 
     public void gotAddress(byte[] ip, int port) {
-        throw new IllegalStateException("Address in Handshake");
+        if (_log.shouldDebug())
+            _log.debug("Got Address: " + Addresses.toString(ip, port));
+        _bobIP = ip;
+        // final, see super
+        //_bobPort = port;
     }
 
     public void gotIntroKey(byte[] key) {
@@ -356,8 +363,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         }
         _createdSentCount++;
         _nextSend = _lastSend + delay;
-        if ( (_currentState == InboundState.IB_STATE_UNKNOWN) || (_currentState == InboundState.IB_STATE_REQUEST_RECEIVED) )
-            _currentState = InboundState.IB_STATE_CREATED_SENT;
+        _currentState = InboundState.IB_STATE_CREATED_SENT;
     }
 
     
@@ -368,6 +374,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
             throw new IllegalStateException("Bad state for Retry Sent: " + _currentState);
         _currentState = InboundState.IB_STATE_RETRY_SENT;
         _lastSend = _context.clock().now();
+        // Won't really be transmitted, they have 3 sec to respond or
+        // EstablishmentManager.handleInbound() will fail the connection
+        _nextSend = _lastSend + RETRANSMIT_DELAY;
     }
 
     /**
@@ -539,17 +548,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
         System.arraycopy(_sessCrForReTX, 0, data, off, _sessCrForReTX.length);
-        InetAddress to;
-        try {
-            to = InetAddress.getByAddress(_aliceIP);
-        } catch (UnknownHostException uhe) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("How did we think this was a valid IP?  " + _remoteHostId);
-            packet.release();
-            return null;
-        }
-        pkt.setAddress(to);
-        pkt.setPort(_alicePort);
+        pkt.setSocketAddress(_aliceSocketAddress);
         packet.setMessageType(PacketBuilder2.TYPE_CONF);
         packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
         createdPacketSent();
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 b77fbff129..5f4b1730b7 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
@@ -35,8 +35,8 @@ class OutboundEstablishState {
     private DHSessionKeyBuilder _keyBuilder;
     // SessionCreated message
     private byte _receivedY[];
-    private byte _aliceIP[];
-    private int _alicePort;
+    protected byte _aliceIP[];
+    protected int _alicePort;
     private long _receivedRelayTag;
     private long _receivedSignedOnTime;
     private SessionKey _sessionKey;
@@ -94,6 +94,11 @@ class OutboundEstablishState {
         /** SessionConfirmed failed validation */
         OB_STATE_VALIDATION_FAILED,
 
+        /**
+         * SSU2: We don't have a token
+         * @since 0.9.54
+         */
+        OB_STATE_NEEDS_TOKEN,
         /**
          * SSU2: We have sent a token request
          * @since 0.9.54
diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
index aa2d0fc492..ea2ecaf48a 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
@@ -123,6 +123,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         _routerAddress = ra;
         if (_token != 0)
             createNewState(ra);
+        else
+            _currentState = OutboundState.OB_STATE_NEEDS_TOKEN;
 
         byte[] ik = introKey.getData();
         _sendHeaderEncryptKey1 = ik;
@@ -196,7 +198,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
 
     public void gotAddress(byte[] ip, int port) {
         if (_log.shouldDebug())
-            _log.debug("Got ADDRESS block: " + Addresses.toString(ip, port));
+            _log.debug("Got Address: " + Addresses.toString(ip, port));
+        _aliceIP = ip;
+        _alicePort = port;
     }
 
     public void gotIntroKey(byte[] key) {
@@ -245,8 +249,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
     // end payload callbacks
     /////////////////////////////////////////////////////////
     
-    // SSU 1 unsupported things
+    // SSU 1 overrides
 
+    @Override
+    public synchronized boolean validateSessionCreated() {
+        // All validation is in receiveSessionCreated()
+        boolean rv = _currentState == OutboundState.OB_STATE_CREATED_RECEIVED ||
+                     _currentState == OutboundState.OB_STATE_CONFIRMED_COMPLETELY;
+        return rv;
+    }
 
     // SSU 2 things
 
@@ -268,6 +279,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
     public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
     public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
     public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
+    /**
+     *  @return null before Session Request is sent (i.e. we sent a Token Request first)
+     */
     public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
     public byte[] getRcvRetryHeaderEncryptKey2() { return _rcvRetryHeaderEncryptKey2; }
     public InetSocketAddress getSentAddress() { return _bobSocketAddress; }
@@ -278,9 +292,22 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
     public synchronized void receiveRetry(UDPPacket packet) throws GeneralSecurityException {
         ////// TODO state check
         DatagramPacket pkt = packet.getPacket();
+        SocketAddress from = pkt.getSocketAddress();
+        if (!from.equals(_bobSocketAddress))
+            throw new GeneralSecurityException("Address mismatch: req: " + _bobSocketAddress + " conf: " + from);
         int off = pkt.getOffset();
         int len = pkt.getLength();
         byte data[] = pkt.getData();
+        long rid = DataHelper.fromLong8(data, off);
+        if (rid != _rcvConnID)
+            throw new GeneralSecurityException("Conn ID mismatch: 1: " + _rcvConnID + " 2: " + rid);
+        long sid = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
+        if (sid != _sendConnID)
+            throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
+        long token = DataHelper.fromLong8(data, off + TOKEN_OFFSET);
+        if (token == 0)
+            throw new GeneralSecurityException("Bad token 0 in retry");
+        _token = token;
         _timeReceived = 0;
         try {
             // decrypt in-place
@@ -302,7 +329,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         if (skew > MAX_SKEW || skew < 0 - MAX_SKEW)
             throw new GeneralSecurityException("Skew exceeded in Session/Token Request: " + skew);
         createNewState(_routerAddress);
-        ////// TODO state change
+        _currentState = OutboundState.OB_STATE_RETRY_RECEIVED;
+        packetReceived();
     }
 
     public synchronized void receiveSessionCreated(UDPPacket packet) throws GeneralSecurityException {
@@ -320,6 +348,13 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         int off = pkt.getOffset();
         int len = pkt.getLength();
         byte data[] = pkt.getData();
+        long rid = DataHelper.fromLong8(data, off);
+        if (rid != _rcvConnID)
+            throw new GeneralSecurityException("Conn ID mismatch: 1: " + _rcvConnID + " 2: " + rid);
+        long sid = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
+        if (sid != _sendConnID)
+            throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
+
         _handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
         if (_log.shouldDebug())
             _log.debug("State after mixHash 2: " + _handshakeState);
@@ -344,11 +379,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         _sessReqForReTX = null;
         _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed");
 
-        if (_currentState == OutboundState.OB_STATE_UNKNOWN ||
-            _currentState == OutboundState.OB_STATE_REQUEST_SENT ||
-            _currentState == OutboundState.OB_STATE_INTRODUCED ||
-            _currentState == OutboundState.OB_STATE_PENDING_INTRO)
-            _currentState = OutboundState.OB_STATE_CREATED_RECEIVED;
+        _currentState = OutboundState.OB_STATE_CREATED_RECEIVED;
 
         if (_requestSentCount == 1) {
             _rtt = (int) (_context.clock().now() - _requestSentTime);
@@ -361,10 +392,10 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
      * and save them for retransmission
      */
     public synchronized void tokenRequestSent(DatagramPacket packet) {
-        if (_currentState == OutboundState.OB_STATE_UNKNOWN)
+        OutboundState old = _currentState;
+        requestSent();
+        if (old == OutboundState.OB_STATE_NEEDS_TOKEN)
             _currentState = OutboundState.OB_STATE_TOKEN_REQUEST_SENT;
-        else if (_currentState == OutboundState.OB_STATE_RETRY_RECEIVED)
-            _currentState = OutboundState.OB_STATE_REQUEST_SENT_NEW_TOKEN;
         // don't bother saving for retx, just make a new one every time
     }
 
@@ -383,7 +414,10 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         }
         if (_rcvHeaderEncryptKey2 == null)
             _rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
+        OutboundState old = _currentState;
         requestSent();
+        if (old == OutboundState.OB_STATE_RETRY_RECEIVED)
+            _currentState = OutboundState.OB_STATE_REQUEST_SENT_NEW_TOKEN;
     }
 
     /**
@@ -461,17 +495,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
         System.arraycopy(_sessReqForReTX, 0, data, off, _sessReqForReTX.length);
-        InetAddress to;
-        try {
-            to = InetAddress.getByAddress(_bobIP);
-        } catch (UnknownHostException uhe) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("How did we think this was a valid IP?  " + _remoteHostId);
-            packet.release();
-            return null;
-        }
-        pkt.setAddress(to);
-        pkt.setPort(_bobPort);
+        pkt.setSocketAddress(_bobSocketAddress);
         packet.setMessageType(PacketBuilder2.TYPE_SREQ);
         packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
         requestSent();
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
index 6be94e5a49..4977c9d7d9 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
@@ -207,7 +207,8 @@ class PacketBuilder2 {
             off += sz;
             sizeWritten += sz;
         }
-        Block block = getPadding(sizeWritten, peer.getMTU());
+        // FIXME
+        Block block = getPadding(sizeWritten, currentMTU);
         if (block != null) {
             blocks.add(block);
             int sz = block.getTotalLength();
@@ -216,13 +217,13 @@ class PacketBuilder2 {
         }
         SSU2Payload.writePayload(data, SHORT_HEADER_SIZE, blocks);
         pkt.setLength(off);
-        if (_log.shouldDebug())
-            _log.debug("Packet " + pktNum + " before encryption:\n" + HexDump.dump(data, 0, off));
+        //if (_log.shouldDebug())
+        //    _log.debug("Packet " + pktNum + " before encryption:\n" + HexDump.dump(data, 0, off));
 
         encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
         setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
-        if (_log.shouldDebug())
-            _log.debug("Packet " + pktNum + " after encryption:\n" + HexDump.dump(data, 0, pkt.getLength()));
+        //if (_log.shouldDebug())
+        //    _log.debug("Packet " + pktNum + " after encryption:\n" + HexDump.dump(data, 0, pkt.getLength()));
         
         // FIXME ticket #2675
         // the packet could have been built before the current mtu got lowered, so
@@ -294,7 +295,6 @@ class PacketBuilder2 {
         pkt.setLength(LONG_HEADER_SIZE);
         byte[] introKey = state.getSendHeaderEncryptKey1();
         encryptTokenRequest(packet, introKey, n, introKey, introKey);
-        state.requestSent();
         pkt.setSocketAddress(state.getSentAddress());
         packet.setMessageType(TYPE_SREQ);
         packet.setPriority(PRIORITY_HIGH);
@@ -316,7 +316,6 @@ class PacketBuilder2 {
         pkt.setLength(LONG_HEADER_SIZE);
         byte[] introKey = state.getSendHeaderEncryptKey1();
         encryptSessionRequest(packet, state.getHandshakeState(), introKey, introKey, state.needIntroduction());
-        state.requestSent();
         pkt.setSocketAddress(state.getSentAddress());
         packet.setMessageType(TYPE_SREQ);
         packet.setPriority(PRIORITY_HIGH);
@@ -451,7 +450,8 @@ class PacketBuilder2 {
         pkt.setLength(SHORT_HEADER_SIZE);
         SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(ourInfo,  0, len,
                                                             false, gzip, 0, numFragments);
-        encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(),
+        boolean isIPv6 = state.getSentIP().length == 16;
+        encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), isIPv6,
                                 state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), block, state.getNextToken());
         pkt.setSocketAddress(state.getSentAddress());
         packet.setMessageType(TYPE_CONF);
@@ -875,22 +875,24 @@ class PacketBuilder2 {
      *  @param packet containing only 16 byte header
      */
     private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu,
-                                         byte[] hdrKey1, byte[] hdrKey2,
+                                         boolean isIPv6, byte[] hdrKey1, byte[] hdrKey2,
                                          SSU2Payload.RIBlock riblock, long token) {
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
+        mtu -= UDP_HEADER_SIZE;
+        mtu -= isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE;
         try {
             List<Block> blocks = new ArrayList<Block>(3);
             int len = riblock.getTotalLength();
             blocks.add(riblock);
-            if (token > 0) {
-                // TODO only if room
+            // only if room
+            if (token > 0 && mtu - len >= 15) {
                 Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
                 len += block.getTotalLength();
                 blocks.add(block);
             }
-            Block block = getPadding(len, mtu - 80);
+            Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
             if (block != null) {
                 len += block.getTotalLength();
                 blocks.add(block);
@@ -904,6 +906,8 @@ class PacketBuilder2 {
                 _log.debug("State after mixHash 3: " + state);
             state.writeMessage(data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN, len);
             pkt.setLength(pkt.getLength() + KEY_LEN + MAC_LEN + len + MAC_LEN);
+            if (_log.shouldDebug())
+                _log.debug("Session confirmed packet length is: " + pkt.getLength());
         } catch (RuntimeException re) {
             if (!_log.shouldWarn())
                 _log.error("Bad msg 3 out", re);
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
index 0d2f02e924..084796181d 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
@@ -211,7 +211,7 @@ class PacketHandler {
                     handlePacket(_reader, packet);
                 } catch (RuntimeException e) {
                     if (_log.shouldLog(Log.ERROR))
-                        _log.error("Crazy error handling a packet: " + packet, e);
+                        _log.error("Internal error handling " + packet, e);
                 }
                 
                 // back to the cache with thee!
@@ -779,6 +779,8 @@ class PacketHandler {
      */
     private void receiveSSU2Packet(UDPPacket packet, PeerState2 state) {
         // header and body decryption is done by PeerState2
+        // This bypasses InboundMessageStates completely.
+        // All handling of fragments and acks is done in PeerState2.
         state.receivePacket(packet);
     }
 
@@ -830,8 +832,9 @@ class PacketHandler {
             // Session Request (after Retry) or Session Confirmed
             // or retransmitted Session Request or Token Rquest
             k2 = state.getRcvHeaderEncryptKey2();
-            if (state.getState() == InboundEstablishState.InboundState.IB_STATE_RETRY_SENT) {
-                // Session Request
+            if (k2 == null) {
+                // Session Request after Retry
+                k2 = k1;
                 header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
                 if (header == null ||
                     header.getType() != SSU2Util.SESSION_REQUEST_FLAG_BYTE ||
@@ -841,6 +844,13 @@ class PacketHandler {
                         _log.warn("Failed decrypt Session Request after Retry: " + header);
                     return false;
                 }
+                if (header.getSrcConnID() != state.getSendConnID()) {
+                    if (_log.shouldWarn())
+                        _log.warn("Bad Source Conn id " + header);
+                    // TODO could be a retransmitted Session Request,
+                    // tell establisher?
+                    return false;
+                }
                 type = SSU2Util.SESSION_REQUEST_FLAG_BYTE;
             } else {
                 // Session Confirmed or retransmitted Session Request or Token Request
@@ -862,13 +872,6 @@ class PacketHandler {
                     _log.warn("Bad Dest Conn id " + header);
                 return false;
             }
-            if (header.getSrcConnID() != state.getSendConnID()) {
-                if (_log.shouldWarn())
-                    _log.warn("Bad Source Conn id " + header);
-                // TODO could be a retransmitted Session Request,
-                // tell establisher?
-                return false;
-            }
         }
 
         // all good
@@ -912,15 +915,21 @@ class PacketHandler {
         // decrypt header
         byte[] k1 = state.getRcvHeaderEncryptKey1();
         byte[] k2 = state.getRcvHeaderEncryptKey2();
-        SSU2Header.Header header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
-        if (header != null) {
-            // dest conn ID decrypts the same for both Session Created
-            // and Retry, so we can bail out now if it doesn't match
-            if (header.getDestConnID() != state.getRcvConnID()) {
-                if (_log.shouldWarn())
-                    _log.warn("Bad Dest Conn id " + header);
-                return false;
+        SSU2Header.Header header;
+        if (k2 != null) {
+            header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
+            if (header != null) {
+                // dest conn ID decrypts the same for both Session Created
+                // and Retry, so we can bail out now if it doesn't match
+                if (header.getDestConnID() != state.getRcvConnID()) {
+                    if (_log.shouldWarn())
+                        _log.warn("Bad Dest Conn id " + header);
+                    return false;
+                }
             }
+        } else {
+            // we have only sent a Token Request
+            header = null;
         }
         int type;
         if (header == null ||
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 8c7f7b3597..c863aa7b55 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java
@@ -185,8 +185,8 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
         byte[] data = dpacket.getData();
         int off = dpacket.getOffset();
         int len = dpacket.getLength();
-        if (_log.shouldDebug())
-            _log.debug("Packet before header decryption:\n" + HexDump.dump(data, off, len));
+        //if (_log.shouldDebug())
+        //    _log.debug("Packet before header decryption:\n" + HexDump.dump(data, off, len));
         try {
             if (len < MIN_DATA_LEN) {
                 if (_log.shouldWarn())
@@ -199,11 +199,6 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
                     _log.warn("bad data header on " + this);
                 return;
             }
-            if (header.getDestConnID() != _rcvConnID) {
-                if (_log.shouldWarn())
-                    _log.warn("bad Dest Conn id " + header.getDestConnID() + " on " + this);
-                return;
-            }
             if (header.getType() != DATA_FLAG_BYTE) {
                 if (_log.shouldWarn())
                     _log.warn("bad data pkt type " + (header.getType() & 0xff) + " on " + this);
@@ -217,16 +212,21 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
                 // we didn't know the session has disconnected yet.
                 return;
             }
+            if (header.getDestConnID() != _rcvConnID) {
+                if (_log.shouldWarn())
+                    _log.warn("bad Dest Conn id " + header.getDestConnID() + " on " + this);
+                return;
+            }
             long n = header.getPacketNumber();
             SSU2Header.acceptTrialDecrypt(packet, header);
-            if (_log.shouldDebug())
-                _log.debug("Packet " + n + " after header decryption:\n" + HexDump.dump(data, off, len));
+            //if (_log.shouldDebug())
+            //    _log.debug("Packet " + n + " after header decryption:\n" + HexDump.dump(data, off, len));
             synchronized (_rcvCha) {
                 _rcvCha.setNonce(n);
                 // decrypt in-place
                 _rcvCha.decryptWithAd(header.data, data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE);
-                if (_log.shouldDebug())
-                    _log.debug("Packet " + n + " after full decryption:\n" + HexDump.dump(data, off, len - MAC_LEN));
+                //if (_log.shouldDebug())
+                //    _log.debug("Packet " + n + " after full decryption:\n" + HexDump.dump(data, off, len - MAC_LEN));
                 if (_receivedMessages.set(n)) {
                     if (_log.shouldWarn())
                         _log.warn("dup pkt rcvd " + n + " on " + this);
@@ -244,8 +244,6 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
         } catch (IndexOutOfBoundsException ioobe) {
             if (_log.shouldWarn())
                 _log.warn("Bad encrypted packet:\n" + HexDump.dump(data, off, len), ioobe);
-        } finally {
-            packet.release();
         }
     }
 
diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Header.java b/router/java/src/net/i2p/router/transport/udp/SSU2Header.java
index d89836983a..67fee090fd 100644
--- a/router/java/src/net/i2p/router/transport/udp/SSU2Header.java
+++ b/router/java/src/net/i2p/router/transport/udp/SSU2Header.java
@@ -190,10 +190,12 @@ final class SSU2Header {
         public String toString() {
             if (data.length >= SESSION_HEADER_SIZE) {
                 return "Handshake header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType() +
+                       " version " + getVersion() + " netID " + getNetID() +
                        " srcID " + getSrcConnID() + " token " + getToken() + " key " + Base64.encode(getEphemeralKey());
             }
             if (data.length >= LONG_HEADER_SIZE) {
                 return "Long header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType() +
+                       " version " + getVersion() + " netID " + getNetID() +
                        " srcID " + getSrcConnID() + " token " + getToken();
             }
             return "Short header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType();
-- 
GitLab