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