From 1ab3e9b310f97bf5995d83d3ad06b65357836252 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Wed, 20 Oct 2021 09:37:32 -0400 Subject: [PATCH] SSU: Send Bob-to-Alice Peer Test message in-session Matches what i2pd does. More checks to require in-session for Alice/Bob and Bob/Charlie Peer Test messages. --- .../router/transport/udp/PacketBuilder.java | 19 +++- .../router/transport/udp/PacketHandler.java | 2 +- .../router/transport/udp/PeerTestManager.java | 91 +++++++++++++------ .../router/transport/udp/PeerTestState.java | 36 +++++++- 4 files changed, 116 insertions(+), 32 deletions(-) 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 9a097809b6..b9d39334ad 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -1089,11 +1089,26 @@ class PacketBuilder { /** * Build a packet as if we are either Bob or Charlie and we are helping test Alice. - * + * Not for use as Bob, as of 0.9.52; use in-session cipher/mac keys instead. + * * @return ready to send packet, or null if there was a problem */ public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort, SessionKey aliceIntroKey, SessionKey charlieIntroKey, long nonce) { + return buildPeerTestToAlice(aliceIP, alicePort, aliceIntroKey, aliceIntroKey, charlieIntroKey, nonce); + } + + /** + * Build a packet as if we are either Bob or Charlie and we are helping test Alice. + * + * @param aliceCipherKey the intro key if we are Charlie + * @param aliceMACKey the intro key if we are Charlie + * @return ready to send packet, or null if there was a problem + * @since 0.9.52 + */ + public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort, + SessionKey aliceCipherKey, SessionKey aliceMACKey, + SessionKey charlieIntroKey, long nonce) { UDPPacket packet = buildPacketHeader(PEER_TEST_FLAG_BYTE); DatagramPacket pkt = packet.getPacket(); byte data[] = pkt.getData(); @@ -1117,7 +1132,7 @@ class PacketBuilder { off = pad1(data, off); off = pad2(data, off); pkt.setLength(off); - authenticate(packet, aliceIntroKey, aliceIntroKey); + authenticate(packet, aliceCipherKey, aliceMACKey); setTo(packet, aliceIP, alicePort); packet.setMessageType(TYPE_TTA); return packet; 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 ea0908dcda..9ab63895d0 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -792,7 +792,7 @@ class PacketHandler { } if (_log.shouldLog(Log.DEBUG)) _log.debug("Received test packet: " + reader + " from " + from); - _testManager.receiveTest(from, reader); + _testManager.receiveTest(from, state, auth == AuthType.SESSION, reader); //_context.statManager().addRateData("udp.receivePacketSize.test", packet.getPacket().getLength(), packet.getLifetime()); break; case UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST: diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index 5ee1008906..1897f5c47f 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -193,8 +193,7 @@ class PeerTestManager { _context.clock().now()); test.setBobIP(bobIP); test.setBobPort(bobPort); - test.setBobCipherKey(bobCipherKey); - test.setBobMACKey(bobMACKey); + test.setBobKeys(bobCipherKey, bobMACKey); _currentTest = test; _currentTestComplete = false; @@ -276,7 +275,7 @@ class PeerTestManager { _log.debug("Sending test to Bob: " + test); test.setLastSendTime(_context.clock().now()); _transport.send(_packetBuilder.buildPeerTestFromAlice(test.getBobIP(), test.getBobPort(), - test.getBobCipherKey(), test.getBobMACKey(), //_bobIntroKey, + test.getBobCipherKey(), test.getBobMACKey(), test.getNonce(), _transport.getIntroKey())); } else { _currentTest = null; @@ -312,8 +311,12 @@ class PeerTestManager { /** * Receive a PeerTest message which contains the correct nonce for our current * test. We are Alice. + * + * @param fromPeer non-null if an associated session was found, otherwise null + * @param inSession true if authenticated in-session */ - private synchronized void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) { + private synchronized void receiveTestReply(RemoteHostId from, PeerState fromPeer, boolean inSession, + UDPPacketReader.PeerTestReader testInfo) { _context.statManager().addRateData("udp.receiveTestReply", 1); PeerTestState test = _currentTest; if (expired()) @@ -323,6 +326,17 @@ class PeerTestManager { if ( (DataHelper.eq(from.getIP(), test.getBobIP().getAddress())) && (from.getPort() == test.getBobPort()) ) { // The reply is from Bob + if (inSession) { + // i2pd has sent the Bob->Alice message in-session for a long time + // Java I2P switched to in-session in 0.9.52 + //if (_log.shouldDebug()) + // _log.debug("Bob replied to us (Alice) in-session " + fromPeer); + } else { + // TODO check Bob version, drop if >= 0.9.52 + if (_log.shouldDebug()) + _log.debug("Bob replied to us (Alice) with intro key " + from + ' ' + fromPeer); + } + int ipSize = testInfo.readIPSize(); boolean expectV6 = test.isIPv6(); if ((!expectV6 && ipSize != 4) || @@ -512,8 +526,11 @@ class PeerTestManager { * adjusting our test state. * * We could be Alice, Bob, or Charlie. + * + * @param fromPeer non-null if an associated session was found, otherwise null + * @param inSession true if authenticated in-session */ - public void receiveTest(RemoteHostId from, UDPPacketReader reader) { + public void receiveTest(RemoteHostId from, PeerState fromPeer, boolean inSession, UDPPacketReader reader) { _context.statManager().addRateData("udp.receiveTest", 1); byte[] fromIP = from.getIP(); int fromPort = from.getPort(); @@ -557,7 +574,7 @@ class PeerTestManager { PeerTestState test = _currentTest; if ( (test != null) && (test.getNonce() == nonce) ) { // we are Alice, we initiated the test - receiveTestReply(from, testInfo); + receiveTestReply(from, fromPeer, inSession, testInfo); return; } @@ -606,7 +623,7 @@ class PeerTestManager { _log.warn("Too many active tests, droppping from Alice " + Addresses.toString(fromIP, fromPort)); return; } - if (_transport.getPeerState(from) == null) { + if (!inSession || fromPeer == null) { // Require an existing session to start a test, // as a way of preventing trouble if (_log.shouldLog(Log.WARN)) @@ -615,7 +632,7 @@ class PeerTestManager { } if (_log.shouldLog(Log.DEBUG)) _log.debug("test IP/port are blank coming from " + from + ", assuming we are Bob and they are alice"); - receiveFromAliceAsBob(from, testInfo, nonce, null); + receiveFromAliceAsBob(from, fromPeer, testInfo, nonce, null); } else { if (_recentTests.contains(lNonce)) { // ignore the packet, as its a holdover from a recently completed locally @@ -630,7 +647,7 @@ class PeerTestManager { _log.debug("We are charlie, as the testIP/port is " + Addresses.toString(testIP, testPort) + " and the state is unknown for " + nonce); // we are charlie, since alice never sends us her IP and port, only bob does (and, // erm, we're not alice, since it isn't our nonce) - receiveFromBobAsCharlie(from, testInfo, nonce, null); + receiveFromBobAsCharlie(from, fromPeer, inSession, testInfo, nonce, null); } } } else { @@ -638,10 +655,16 @@ class PeerTestManager { if (state.getOurRole() == BOB) { if (DataHelper.eq(fromIP, state.getAliceIP().getAddress()) && (fromPort == state.getAlicePort()) ) { - receiveFromAliceAsBob(from, testInfo, nonce, state); + if (!inSession || fromPeer == null) { + // Still should be in-session + if (_log.shouldWarn()) + _log.warn("No session, dropping test from Alice " + Addresses.toString(fromIP, fromPort)); + return; + } + receiveFromAliceAsBob(from, fromPeer, testInfo, nonce, state); } else if (DataHelper.eq(fromIP, state.getCharlieIP().getAddress()) && (fromPort == state.getCharliePort()) ) { - receiveFromCharlieAsBob(from, state); + receiveFromCharlieAsBob(from, fromPeer, inSession, state); } else { if (_log.shouldLog(Log.WARN)) _log.warn("Received from a fourth party as bob! alice: " + state.getAliceIP() + ", charlie: " + state.getCharlieIP() + ", dave: " + from); @@ -650,7 +673,7 @@ class PeerTestManager { if ( (testIP == null) || (testPort <= 0) ) { receiveFromAliceAsCharlie(from, testInfo, nonce, state); } else { - receiveFromBobAsCharlie(from, testInfo, nonce, state); + receiveFromBobAsCharlie(from, fromPeer, inSession, testInfo, nonce, state); } } } @@ -662,9 +685,18 @@ class PeerTestManager { * The packet's IP/port does not match the IP/port included in the message, * so we must be Charlie receiving a PeerTest from Bob. * + * @param bob non-null if received in-session, otherwise null + * @param inSession true if authenticated in-session * @param state null if new */ - private void receiveFromBobAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) { + private void receiveFromBobAsCharlie(RemoteHostId from, PeerState bob, boolean inSession, + UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) { + if (!inSession || bob == null) { + if (_log.shouldWarn()) + _log.warn("Received from bob (" + from + ") as charlie w/o session"); + return; + } + long now = _context.clock().now(); int sz = testInfo.readIPSize(); boolean isNew = false; @@ -701,17 +733,8 @@ class PeerTestManager { state.setBobIP(bobIP); state.setBobPort(from.getPort()); state.setReceiveBobTime(now); + state.setBobKeys(bob.getCurrentCipherKey(), bob.getCurrentMACKey()); - PeerState bob = _transport.getPeerState(from); - if (bob == null) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Received from bob (" + from + ") who hasn't established a session with us, refusing to help him test " + aliceIP +":" + alicePort); - return; - } else { - state.setBobCipherKey(bob.getCurrentCipherKey()); - state.setBobMACKey(bob.getCurrentMACKey()); - } - // we send two packets below, but increment just once if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_CHARLIE) { if (_log.shouldLog(Log.WARN)) @@ -748,9 +771,12 @@ class PeerTestManager { * any info in the message), plus we are not acting as Charlie (so we've got to be Bob). * * testInfo IP/port ignored + * + * @param alice non-null * @param state null if new */ - private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) { + private void receiveFromAliceAsBob(RemoteHostId from, PeerState alice, UDPPacketReader.PeerTestReader testInfo, + long nonce, PeerTestState state) { // we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info PeerState charlie; RouterInfo charlieInfo = null; @@ -819,6 +845,7 @@ class PeerTestManager { state.setAliceIP(aliceIP); state.setAlicePort(from.getPort()); state.setAliceIntroKey(aliceIntroKey); + state.setAliceKeys(alice.getCurrentCipherKey(), alice.getCurrentMACKey()); state.setCharlieIP(charlie.getRemoteIPAddress()); state.setCharliePort(charlie.getRemotePort()); state.setCharlieIntroKey(charlieIntroKey); @@ -858,9 +885,18 @@ class PeerTestManager { * packet verifying participation. * * testInfo IP/port ignored + * + * @param fromPeer non-null if an associated session was found, otherwise null + * @param inSession true if authenticated in-session * @param state non-null */ - private void receiveFromCharlieAsBob(RemoteHostId from, PeerTestState state) { + private void receiveFromCharlieAsBob(RemoteHostId from, PeerState charlie, boolean inSession, PeerTestState state) { + if (!inSession || charlie == null) { + if (_log.shouldWarn()) + _log.warn("Received from charlie (" + from + ") as bob w/o session"); + return; + } + long now = _context.clock().now(); if (state.getReceiveCharlieTime() > now - (RESEND_TIMEOUT / 2)) { if (_log.shouldLog(Log.WARN)) @@ -876,9 +912,10 @@ class PeerTestManager { state.setReceiveCharlieTime(now); state.setLastSendTime(now); + // In-session as of 0.9.52 UDPPacket packet = _packetBuilder.buildPeerTestToAlice(state.getAliceIP(), state.getAlicePort(), - state.getAliceIntroKey(), state.getCharlieIntroKey(), - state.getNonce()); + state.getAliceCipherKey(), state.getAliceMACKey(), + state.getCharlieIntroKey(), state.getNonce()); if (_log.shouldLog(Log.DEBUG)) _log.debug("Receive from Charlie, sending Alice back the OK: " + state); diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestState.java b/router/java/src/net/i2p/router/transport/udp/PeerTestState.java index d9e883656e..3d03f82609 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestState.java @@ -23,6 +23,8 @@ class PeerTestState { private InetAddress _aliceIPFromCharlie; private int _alicePortFromCharlie; private SessionKey _aliceIntroKey; + private SessionKey _aliceCipherKey; + private SessionKey _aliceMACKey; private SessionKey _charlieIntroKey; private SessionKey _bobCipherKey; private SessionKey _bobMACKey; @@ -85,12 +87,42 @@ class PeerTestState { public SessionKey getAliceIntroKey() { return _aliceIntroKey; } public void setAliceIntroKey(SessionKey key) { _aliceIntroKey = key; } + + /** + * @since 0.9.52 + */ + public SessionKey getAliceCipherKey() { return _aliceCipherKey; } + + /** + * @since 0.9.52 + */ + public SessionKey getAliceMACKey() { return _aliceMACKey; } + + /** + * @param ck cipher key + * @param mk MAC key + * @since 0.9.52 + */ + public void setAliceKeys(SessionKey ck, SessionKey mk) { + _aliceCipherKey = ck; + _aliceMACKey = mk; + } + public SessionKey getCharlieIntroKey() { return _charlieIntroKey; } public void setCharlieIntroKey(SessionKey key) { _charlieIntroKey = key; } + public SessionKey getBobCipherKey() { return _bobCipherKey; } - public void setBobCipherKey(SessionKey key) { _bobCipherKey = key; } public SessionKey getBobMACKey() { return _bobMACKey; } - public void setBobMACKey(SessionKey key) { _bobMACKey = key; } + + /** + * @param ck cipher key + * @param mk MAC key + * @since 0.9.52 + */ + public void setBobKeys(SessionKey ck, SessionKey mk) { + _bobCipherKey = ck; + _bobMACKey = mk; + } /** when did this test begin? */ public long getBeginTime() { return _beginTime; } -- GitLab