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 9626f5671de4b6ed481d240dbce0efe341ea0af2..24f6a95e9f787bfbe5a958f9ad7d40a64ed10268 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -20,6 +20,7 @@ import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.data.SessionKey; +import net.i2p.data.SigningPublicKey; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.i2np.DeliveryStatusMessage; @@ -1514,14 +1515,130 @@ class EstablishmentManager { /** * We are Alice, we sent a RelayRequest to Bob and got a RelayResponse back. + * Time and version already checked by caller. * * SSU 2 only. * - * @param data including token if code == 0 + * @param data including nonce, including token if code == 0 * @since 0.9.55 */ void receiveRelayResponse(PeerState2 bob, long nonce, int code, byte[] data) { - // lookup nonce, determine who signed, validate sig, send SessionRequest if code == 0 + // don't remove unless accepted or rejected by charlie + OutboundEstablishState charlie; + Long lnonce = Long.valueOf(nonce); + if (code > 0 && code < 64) + charlie = _liveIntroductions.get(lnonce); + else + charlie = _liveIntroductions.remove(lnonce); + if (charlie == null) { + if (_log.shouldDebug()) + _log.debug("Dup or unknown RelayResponse: " + nonce); + return; // already established + } + long token; + if (code == 0) { + token = DataHelper.fromLong8(data, data.length - 8); + data = Arrays.copyOfRange(data, 0, data.length - 8); + } else { + token = 0; + } + Hash bobHash = bob.getRemotePeer(); + Hash charlieHash = charlie.getRemoteHostId().getPeerHash(); + RouterInfo bobRI = _context.netDb().lookupRouterInfoLocally(bobHash); + RouterInfo charlieRI = _context.netDb().lookupRouterInfoLocally(charlieHash); + Hash signer; + if (code > 0 && code < 64) + signer = bobHash; + else + signer = charlieHash; + RouterInfo signerRI = _context.netDb().lookupRouterInfoLocally(signer); + if (signerRI != null) { + // validate signed data + SigningPublicKey spk = signerRI.getIdentity().getSigningPublicKey(); + if (SSU2Util.validateSig(_context, SSU2Util.RELAY_REQUEST_PROLOGUE, + bobHash, null, data, spk)) { + } else { + if (_log.shouldWarn()) + _log.warn("Signature failed relay response\n" + signerRI); + } + } else { + if (_log.shouldWarn()) + _log.warn("Signer RI not found " + signer); + } + if (code == 0) { + int iplen = data[9] & 0xff; + if (iplen != 6 && iplen != 18) { + if (_log.shouldWarn()) + _log.warn("Bad IP length " + iplen + " from " + charlie); + charlie.fail(); + return; + } + boolean isIPv6 = iplen == 18; + int port = (int) DataHelper.fromLong(data, 10, 2); + byte[] ip = new byte[iplen - 2]; + System.arraycopy(data, 12, ip, 0, iplen - 2); + // validate + if (!TransportUtil.isValidPort(port) || + !_transport.isValid(ip) || + _transport.isTooClose(ip) || + _context.blocklist().isBlocklisted(ip)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Bad relay resp from " + charlie + " for " + Addresses.toString(ip, port)); + _context.statManager().addRateData("udp.relayBadIP", 1); + charlie.fail(); + return; + } + InetAddress charlieIP; + try { + charlieIP = InetAddress.getByAddress(ip); + } catch (UnknownHostException uhe) { + charlie.fail(); + return; + } + if (_log.shouldDebug()) + _log.debug("Received RelayResponse from " + charlie + " - they are on " + + Addresses.toString(ip, port)); + if (charlieRI == null) { + if (_log.shouldWarn()) + _log.warn("Charlie RI not found " + charlie); + // maybe it will show up later + return; + } + synchronized (charlie) { + RemoteHostId oldId = charlie.getRemoteHostId(); + ((OutboundEstablishState2) charlie).introduced(ip, port, token); + RemoteHostId newId = charlie.getRemoteHostId(); + addOutboundToken(newId, token, _context.clock().now() + 10*1000); + // Swap out the RemoteHostId the state is indexed under. + // It was a Hash, change it to a IP/port. + // Remove the entry in the byClaimedAddress map as it's now in main map. + // Add an entry in the byHash map so additional OB pkts can find it. + _outboundByHash.put(charlieHash, charlie); + RemoteHostId claimed = charlie.getClaimedAddress(); + if (!oldId.equals(newId)) { + _outboundStates.remove(oldId); + _outboundStates.put(newId, charlie); + if (_log.shouldLog(Log.INFO)) + _log.info("RR replaced " + oldId + " with " + newId + ", claimed address was " + claimed); + } + // + if (claimed != null) + _outboundByClaimedAddress.remove(oldId, charlie); // only if == state + } + notifyActivity(); + } else if (code >= 64) { + // that's it + if (_log.shouldDebug()) + _log.debug("Received RelayResponse rejection " + code + " from charlie " + charlie); + charlie.fail(); + _liveIntroductions.remove(lnonce); + } else { + // don't give up, maybe more bobs out there + // TODO keep track + if (_log.shouldDebug()) + _log.debug("Received RelayResponse rejection " + code + " from bob " + bob); + notifyActivity(); + } } /** diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 7f47889134e677cc26f3ac9187d6793b8f755660..960fb5ba711ca9c1f8896584f50d336654bae020 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -1010,7 +1010,27 @@ class IntroductionManager { PeerState2 alice = _nonceToAlice.remove(Long.valueOf(nonce)); if (alice != null) { // We are Bob, send to Alice - // We don't check the signature here + // Debug, check the signature, but send it along even if failed + if (true) { + RouterInfo charlie = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer()); + if (charlie != null) { + byte[] signedData; + if (status == 0) + signedData = Arrays.copyOfRange(data, 0, data.length - 8); // token + else + signedData = data; + SigningPublicKey spk = charlie.getIdentity().getSigningPublicKey(); + if (SSU2Util.validateSig(_context, SSU2Util.RELAY_REQUEST_PROLOGUE, + _context.routerHash(), null, data, spk)) { + } else { + if (_log.shouldWarn()) + _log.warn("Signature failed relay response\n" + charlie); + } + } else { + if (_log.shouldWarn()) + _log.warn("Signer RI not found " + peer); + } + } byte[] idata = new byte[2 + data.length]; //idata[0] = 0; // flag idata[1] = (byte) status; 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 df2068d663c13d9272f57997fa961bda6276e32c..7be86d014e024f2c0a441871e755b6b0c84eeb4e 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -110,7 +110,7 @@ class OutboundEstablishState { */ OB_STATE_RETRY_RECEIVED, /** - * SSU2: We have sent a second token request with a new token + * SSU2: We have sent a session request after receiving a retry * @since 0.9.54 */ OB_STATE_REQUEST_SENT_NEW_TOKEN 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 b659d6c7e1bb6b1e05ae11753becf427864645ec..b35eeda95f38e8371d96cfd4b34c94ac6af23820 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java @@ -111,12 +111,18 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } } _mtu = mtu; + _routerAddress = ra; if (addr.getIntroducerCount() > 0) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("new outbound establish to " + remotePeer.calculateHash() + ", with address: " + addr); _currentState = OutboundState.OB_STATE_PENDING_INTRO; + // we will get a token in the relay response or hole punch } else { - _currentState = OutboundState.OB_STATE_UNKNOWN; + _token = _transport.getEstablisher().getOutboundToken(_remoteHostId); + if (_token != 0) { + _currentState = OutboundState.OB_STATE_UNKNOWN; + createNewState(ra); + } else { + _currentState = OutboundState.OB_STATE_NEEDS_TOKEN; + } } _sendConnID = ctx.random().nextLong(); @@ -127,13 +133,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } while (_sendConnID == rcid); _rcvConnID = rcid; - _token = _transport.getEstablisher().getOutboundToken(_remoteHostId); - _routerAddress = ra; - if (_token != 0) - createNewState(ra); - else - _currentState = OutboundState.OB_STATE_NEEDS_TOKEN; - byte[] ik = introKey.getData(); _sendHeaderEncryptKey1 = ik; _rcvHeaderEncryptKey1 = ik; @@ -144,6 +143,19 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl _log.debug("New " + this); } + /** + * After introduction + * + * @since 0.9.55 + */ + public synchronized void introduced(byte[] ip, int port, long token) { + if (_currentState != OutboundState.OB_STATE_PENDING_INTRO) + return; + introduced(ip, port); + _token = token; + createNewState(_routerAddress); + } + private void createNewState(RouterAddress addr) { String ss = addr.getOption("s"); if (ss == null) @@ -163,18 +175,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl _transport.getSSU2StaticPubKey(), 0); } - public synchronized void restart(long token) { - _token = token; - HandshakeState old = _handshakeState; - if (old != null) { - // TODO pass the old keys over to createNewState() - old.destroy(); - } - createNewState(_routerAddress); - //_rcvHeaderEncryptKey2 will be set after the Session Request message is created - _rcvHeaderEncryptKey2 = null; - } - private void processPayload(byte[] payload, int offset, int length, boolean isHandshake) throws GeneralSecurityException { try { int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, isHandshake);