From 281bf6809845a7ed102630d61c618f88165cd809 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Sat, 23 Apr 2022 15:57:00 -0400 Subject: [PATCH] SSU2: Peer Test fixes part 2 Don't send message 5 unless we sent accept in message 3 Fix packet length for messages 5-7 Pass messages 5-7 from PacketHandler to PeerTestManager Decrypt and process messages 5-7 Handle messages 4 and 5 in either order Don't set test complete after message 5 Build data block for message 6 Log tweaks --- .../router/transport/udp/PacketBuilder2.java | 8 +- .../router/transport/udp/PacketHandler.java | 4 +- .../router/transport/udp/PeerTestManager.java | 205 ++++++++++++++++-- 3 files changed, 192 insertions(+), 25 deletions(-) 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 0515da2224..8ea9e76ac6 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -647,6 +647,7 @@ class PacketBuilder2 { UDPPacket packet = buildLongPacketHeader(sendID, n, PEER_TEST_FLAG_BYTE, rcvID, token); Block block = new SSU2Payload.PeerTestBlock(6, 0, null, signedData); byte[] ik = introKey.getData(); + packet.getPacket().setLength(LONG_HEADER_SIZE); encryptPeerTest(packet, ik, n, ik, ik, toIP.getAddress(), toPort, block); setTo(packet, toIP, toPort); packet.setMessageType(TYPE_TFA); @@ -684,6 +685,7 @@ class PacketBuilder2 { int msgNum = firstSend ? 5 : 7; Block block = new SSU2Payload.PeerTestBlock(msgNum, 0, null, signedData); byte[] ik = introKey.getData(); + packet.getPacket().setLength(LONG_HEADER_SIZE); encryptPeerTest(packet, ik, n, ik, ik, aliceIP.getAddress(), alicePort, block); setTo(packet, aliceIP, alicePort); packet.setMessageType(TYPE_TTA); @@ -977,12 +979,12 @@ class PacketBuilder2 { pkt.setLength(pkt.getLength() + len + MAC_LEN); } catch (RuntimeException re) { if (!_log.shouldWarn()) - _log.error("Bad retry msg out", re); + _log.error("Bad retry/test msg out", re); throw re; } catch (GeneralSecurityException gse) { if (!_log.shouldWarn()) - _log.error("Bad retry msg out", gse); - throw new RuntimeException("Bad retry msg out", gse); + _log.error("Bad retry/test msg out", gse); + throw new RuntimeException("Bad retry/test msg out", gse); } SSU2Header.encryptLongHeader(packet, hdrKey1, hdrKey2); } 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 143f9b35ec..01499cc984 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -913,8 +913,8 @@ class PacketHandler { _establisher.receiveSessionConfirmed(state, packet); } else if (type == SSU2Util.PEER_TEST_FLAG_BYTE) { if (_log.shouldDebug()) - _log.debug("Got a Peer Test on " + state); - // TODO + _log.debug("Got a Peer Test"); + _testManager.receiveTest(from, packet); } else { if (_log.shouldWarn()) _log.warn("Got unknown message " + header + " on " + state); 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 33febe5f39..25124f9ad1 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -1,5 +1,6 @@ package net.i2p.router.transport.udp; +import java.net.DatagramPacket; import java.net.InetAddress; import java.net.Inet6Address; import java.net.UnknownHostException; @@ -9,6 +10,8 @@ import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; +import com.southernstorm.noise.protocol.ChaChaPolyCipherState; + import net.i2p.data.Base64; import net.i2p.data.DataHelper; import net.i2p.data.Hash; @@ -16,15 +19,18 @@ import net.i2p.data.SessionKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.i2np.DatabaseStoreMessage; +import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterInfo; import net.i2p.router.CommSystemFacade.Status; import net.i2p.router.RouterContext; import static net.i2p.router.transport.udp.PeerTestState.Role.*; +import static net.i2p.router.transport.udp.SSU2Util.*; import net.i2p.router.transport.TransportImpl; import net.i2p.router.transport.TransportUtil; import net.i2p.util.Addresses; import net.i2p.util.Log; +import net.i2p.util.HexDump; import net.i2p.util.SimpleTimer; /** @@ -310,14 +316,16 @@ class PeerTestManager { } /** - * SSU 1 or 2. We are Alice. + * Message 6. SSU 1 or 2. We are Alice. * Call from a synchronized method. */ private void sendTestToCharlie() { PeerTestState test = _currentTest; + if (test == null) + return; if (!expired()) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Sending test to Charlie: " + test); + _log.debug("Sending msg 6 to Charlie: " + test); test.setLastSendTime(_context.clock().now()); UDPPacket packet; if (test.getBob().getVersion() == 1) { @@ -328,7 +336,18 @@ class PeerTestManager { long nonce = test.getNonce(); long sendId = (nonce << 32) | nonce; long rcvId = ~sendId; - byte[] data = null; // TODO + InetAddress addr = test.getAliceIP(); + int alicePort = test.getAlicePort(); + byte[] aliceIP = addr.getAddress(); + int iplen = aliceIP.length; + byte[] data = new byte[13 + iplen]; + data[0] = 1; // alice + data[1] = 2; // version + DataHelper.toLong(data, 2, 4, nonce); + DataHelper.toLong(data, 6, 4, _context.clock().now() / 1000); + data[10] = (byte) iplen; + System.arraycopy(aliceIP, 0, data, 11, iplen); + DataHelper.toLong(data, 11 + iplen, 2, alicePort); packet = _packetBuilder2.buildPeerTestFromAlice(test.getCharlieIP(), test.getCharliePort(), test.getCharlieIntroKey(), sendId, rcvId, data); @@ -727,6 +746,49 @@ class PeerTestManager { } } + /** + * Entry point for all out-of-session packets, messages 5-7 only. + * + * SSU 2 only. + * + * Receive a test message of some sort from the given peer, queueing up any packet + * that should be sent in response, or if its a reply to our own current testing, + * adjusting our test state. + * + * We could be Alice or Charlie. + * + * @param from non-null + * @param packet header already decrypted + * @since 0.9.54 + */ + public void receiveTest(RemoteHostId from, UDPPacket packet) { + DatagramPacket pkt = packet.getPacket(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + byte data[] = pkt.getData(); + long rcvConnID = DataHelper.fromLong8(data, off); + long sendConnID = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET); + int type = data[off + TYPE_OFFSET] & 0xff; + if (type != PEER_TEST_FLAG_BYTE) + return; + byte[] introKey = _transport.getSSU2StaticIntroKey(); + try { + // decrypt in-place + ChaChaPolyCipherState chacha = new ChaChaPolyCipherState(); + chacha.initializeKey(introKey, 0); + long n = DataHelper.fromLong(data, off + PKT_NUM_OFFSET, 4); + chacha.setNonce(n); + chacha.decryptWithAd(data, off, LONG_HEADER_SIZE, + data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE); + int payloadLen = len - (LONG_HEADER_SIZE + MAC_LEN); + SSU2Payload.PayloadCallback cb = new PTCallback(from); + SSU2Payload.processPayload(_context, cb, data, off + LONG_HEADER_SIZE, payloadLen, false); + } catch (Exception e) { + if (_log.shouldWarn()) + _log.warn("Bad PeerTest packet:\n" + HexDump.dump(data, off, len), e); + } + } + /** * Entry point for all incoming packets. * @@ -744,6 +806,7 @@ class PeerTestManager { * @param status 0 = accept, 1-255 = reject * @param h Alice or Charlie hash for msg 2 and 4, null for msg 1, 3, 5-7 * @param data excludes flag, includes signature + * @since 0.9.54 */ public void receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, int status, Hash h, byte[] data) { PeerTestState.Role role; @@ -994,15 +1057,21 @@ class PeerTestManager { return; } UDPPacket packet = _packetBuilder2.buildPeerTestToBob(rcode, data, fromPeer); + if (_log.shouldDebug()) + _log.debug("Send msg 3 response " + rcode + " nonce " + lNonce + " to " + fromPeer); _transport.send(packet); - // send msg 5 - long rcvId = (nonce << 32) | nonce; - long sendId = ~rcvId; - // send the same data we sent to Bob - packet = _packetBuilder2.buildPeerTestToAlice(aliceIP, testPort, - aliceIntroKey, true, - sendId, rcvId, data); - _transport.send(packet); + if (rcode == SSU2Util.TEST_ACCEPT) { + // send msg 5 + if (_log.shouldDebug()) + _log.debug("Send msg 5 to " + Addresses.toString(testIP, testPort)); + long rcvId = (nonce << 32) | nonce; + long sendId = ~rcvId; + // send the same data we sent to Bob + packet = _packetBuilder2.buildPeerTestToAlice(aliceIP, testPort, + aliceIntroKey, true, + sendId, rcvId, data); + _transport.send(packet); + } break; } @@ -1092,9 +1161,20 @@ class PeerTestManager { testComplete(); return; } - state.setCharlie(charlieIP, testPort, h); - state.setCharlieIntroKey(charlieIntroKey); - // delay, await msg 5 + test.setCharlie(charlieIP, testPort, h); + test.setCharlieIntroKey(charlieIntroKey); + if (test.getReceiveCharlieTime() > 0) { + // send msg 6 + if (_log.shouldDebug()) + _log.debug("Send msg 6 to charlie on " + test); + synchronized(this) { + sendTestToCharlie(); + } + } else { + // delay, await msg 5 + if (_log.shouldDebug()) + _log.debug("Got msg 4 before msg 5 on " + test); + } break; } @@ -1111,15 +1191,22 @@ class PeerTestManager { try { InetAddress addr = InetAddress.getByAddress(testIP); test.setAliceIPFromCharlie(addr); - if (test.getReceiveBobTime() > 0) - testComplete(); } catch (UnknownHostException uhe) { if (_log.shouldWarn()) _log.warn("Charlie @ " + from + " said we were an invalid IP address: " + uhe.getMessage(), uhe); _context.statManager().addRateData("udp.testBadIP", 1); } - synchronized(this) { - sendTestToCharlie(); + if (test.getCharlieIntroKey() != null) { + // send msg 6 + if (_log.shouldDebug()) + _log.debug("Send msg 6 to charlie on " + test); + synchronized(this) { + sendTestToCharlie(); + } + } else { + // we haven't gotten message 4 yet + if (_log.shouldDebug()) + _log.debug("Got msg 5 before msg 4 on " + test); } break; } @@ -1128,6 +1215,7 @@ class PeerTestManager { case 6: { state.setReceiveAliceTime(now); state.setLastSendTime(now); + // send msg 7 long rcvId = (nonce << 32) | nonce; long sendId = ~rcvId; InetAddress addr = state.getAliceIP(); @@ -1142,6 +1230,8 @@ class PeerTestManager { data[10] = (byte) iplen; System.arraycopy(aliceIP, 0, data, 11, iplen); DataHelper.toLong(data, 11 + iplen, 2, alicePort); + if (_log.shouldDebug()) + _log.debug("Send msg 7 to alice on " + state); UDPPacket packet = _packetBuilder2.buildPeerTestToAlice(addr, alicePort, state.getAliceIntroKey(), false, sendId, rcvId, data); @@ -1166,8 +1256,6 @@ class PeerTestManager { try { InetAddress addr = InetAddress.getByAddress(testIP); test.setAliceIPFromCharlie(addr); - if (test.getReceiveBobTime() > 0) - testComplete(); } catch (UnknownHostException uhe) { if (_log.shouldWarn()) _log.warn("Charlie @ " + from + " said we were an invalid IP address: " + uhe.getMessage(), uhe); @@ -1520,4 +1608,81 @@ class PeerTestManager { _activeTests.remove(Long.valueOf(_nonce)); } } + + /** + * @since 0.9.54 + */ + private class PTCallback implements SSU2Payload.PayloadCallback { + private final RemoteHostId _from; + public long _timeReceived; + public byte[] _aliceIP; + public int _alicePort; + + public PTCallback(RemoteHostId from) { + _from = from; + } + + public void gotDateTime(long time) { + _timeReceived = time; + } + + public void gotOptions(byte[] options, boolean isHandshake) {} + + public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotAddress(byte[] ip, int port) { + _aliceIP = ip; + _alicePort = port; + } + + public void gotRelayTagRequest() { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotRelayTag(long tag) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotRelayRequest(byte[] data) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotRelayResponse(int status, byte[] data) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotRelayIntro(Hash aliceHash, byte[] data) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotPeerTest(int msg, int status, Hash h, byte[] data) { + receiveTest(_from, null, msg, status, h, data); + } + + public void gotToken(long token, long expires) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotI2NP(I2NPMessage msg) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotFragment(byte[] data, int off, int len, long messageId,int frag, boolean isLast) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotACK(long ackThru, int acks, byte[] ranges) { + throw new IllegalStateException("Bad block in PT"); + } + + public void gotTermination(int reason, long count) { + throw new IllegalStateException("Bad block in PT"); + } + } } -- GitLab