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 af3707ea1cf92358be3178ec5a35453f8e7fe320..2a80676c7e2ba3c13cdc1c865ae41906610ab541 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -1296,6 +1296,8 @@ class EstablishmentManager { /** * We are Alice, we sent a RelayRequest to Bob and got a response back. + * + * SSU 1 only. */ void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) { long nonce = reader.getRelayResponseReader().readNonce(); @@ -1353,6 +1355,18 @@ class EstablishmentManager { notifyActivity(); } + /** + * We are Alice, we sent a RelayRequest to Bob and got a RelayResponse back. + * + * SSU 2 only. + * + * @param data 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 + } + /** * Called from UDPReceiver. * Accelerate response to RelayResponse if we haven't sent it yet. 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 e3d8147044065ef5992560409c7f546f9b0852f1..327e26e4b504e97b45d3626a173fe8409327bf1e 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -86,6 +86,8 @@ class IntroductionManager { private final Map<Long, PeerState> _outbound; /** map of relay tag to PeerState who have given us introduction tags */ private final Map<Long, PeerState> _inbound; + /** map of relay nonce to alice PeerState who requested it */ + private final Map<Long, PeerState2> _nonceToAlice; private final Set<InetAddress> _recentHolePunches; private long _lastHolePunchClean; @@ -117,6 +119,7 @@ class IntroductionManager { _builder2 = transport.getBuilder2(); _outbound = new ConcurrentHashMap<Long, PeerState>(MAX_OUTBOUND); _inbound = new ConcurrentHashMap<Long, PeerState>(MAX_INBOUND); + _nonceToAlice = (_builder2 != null) ? new ConcurrentHashMap<Long, PeerState2>(MAX_INBOUND) : null; _recentHolePunches = new HashSet<InetAddress>(16); ctx.statManager().createRateStat("udp.receiveRelayIntro", "How often we get a relayed request for us to talk to someone?", "udp", UDPTransport.RATES); ctx.statManager().createRateStat("udp.receiveRelayRequest", "How often we receive a good request to relay to someone else?", "udp", UDPTransport.RATES); @@ -669,7 +672,6 @@ class IntroductionManager { * @since 0.9.55 */ void receiveRelayRequest(PeerState2 alice, byte[] data) { - long tag = DataHelper.fromLong(data, 4, 4); long time = DataHelper.fromLong(data, 8, 4) * 1000; long now = _context.clock().now(); long skew = time - now; @@ -684,6 +686,8 @@ class IntroductionManager { _log.warn("Bad relay req version " + ver + " from " + alice); return; } + long nonce = DataHelper.fromLong(data, 0, 4); + long tag = DataHelper.fromLong(data, 4, 4); PeerState charlie = _outbound.get(Long.valueOf(tag)); RouterInfo aliceRI = null; int rcode; @@ -700,7 +704,15 @@ class IntroductionManager { SigningPublicKey spk = aliceRI.getIdentity().getSigningPublicKey(); if (SSU2Util.validateSig(_context, SSU2Util.RELAY_REQUEST_PROLOGUE, _context.routerHash(), charlie.getRemotePeer(), data, spk)) { - rcode = SSU2Util.RELAY_ACCEPT; + // save tag-to-alice mapping so we can forward the reply from charlie + PeerState2 old = _nonceToAlice.putIfAbsent(Long.valueOf(nonce), alice); + if (old != null && !old.equals(alice)) { + // dup tag + rcode = SSU2Util.RELAY_REJECT_BOB_UNSPEC; + } else { + rcode = SSU2Util.RELAY_ACCEPT; + } + // TODO add timer to remove from _nonceToAlice } else { if (_log.shouldWarn()) _log.warn("Signature failed relay intro\n" + aliceRI); @@ -721,17 +733,21 @@ class IntroductionManager { dbsm.setEntry(aliceRI); dbsm.setMessageExpiration(now + 10*1000); _transport.send(dbsm, charlie); - packet = _builder2.buildRelayIntro(data, (PeerState2) charlie); + // put alice hash in intro data + byte[] idata = new byte[1 + Hash.HASH_LENGTH + data.length]; + //idata[0] = 0; // flag + System.arraycopy(alice.getRemotePeer().getData(), 0, idata, 1, Hash.HASH_LENGTH); + System.arraycopy(data, 0, idata, 1 + Hash.HASH_LENGTH, data.length); + packet = _builder2.buildRelayIntro(idata, (PeerState2) charlie); } else { // send rejection to Alice SigningPrivateKey spk = _context.keyManager().getSigningPrivateKey(); - long nonce = DataHelper.fromLong(data, 0, 4); int iplen = data[13] & 0xff; int testPort = (int) DataHelper.fromLong(data, 14, 2); byte[] testIP = new byte[iplen - 2]; System.arraycopy(data, 16, testIP, 0, iplen - 2); data = SSU2Util.createRelayResponseData(_context, _context.routerHash(), rcode, - nonce, testIP, testPort, spk); + nonce, testIP, testPort, spk, 0); if (data == null) { if (_log.shouldWarn()) _log.warn("sig fail"); @@ -828,9 +844,17 @@ class IntroductionManager { // generate our signed data // we sign it even if rejecting, not required though + long token; + if (rcode == SSU2Util.RELAY_ACCEPT) { + RemoteHostId aliceID = new RemoteHostId(testIP, testPort); + EstablishmentManager.Token tok = _transport.getEstablisher().getInboundToken(aliceID); + token = tok.token; + } else { + token = 0; + } SigningPrivateKey spk = _context.keyManager().getSigningPrivateKey(); data = SSU2Util.createRelayResponseData(_context, bob.getRemotePeer(), rcode, - nonce, testIP, testPort, spk); + nonce, testIP, testPort, spk, token); if (data == null) { if (_log.shouldWarn()) _log.warn("sig fail"); @@ -864,6 +888,40 @@ class IntroductionManager { * @since 0.9.55 */ void receiveRelayResponse(PeerState2 peer, int status, byte[] data) { + long nonce = DataHelper.fromLong(data, 0, 4); + long time = DataHelper.fromLong(data, 4, 4) * 1000; + long now = _context.clock().now(); + long skew = time - now; + if (skew > MAX_SKEW || skew < 0 - MAX_SKEW) { + if (_log.shouldWarn()) + _log.warn("Too skewed for relay resp from " + peer); + return; + } + int ver = data[8] & 0xff; + if (ver != 2) { + if (_log.shouldWarn()) + _log.warn("Bad relay intro version " + ver + " from " + peer); + return; + } + // Look up nonce to determine if we are Alice or Bob + PeerState2 alice = _nonceToAlice.remove(Long.valueOf(nonce)); + if (alice != null) { + // We are Bob, send to Alice + // We don't check the signature here + byte[] idata = new byte[2 + data.length]; + //idata[0] = 0; // flag + idata[1] = (byte) status; + System.arraycopy(data, 0, idata, 2, data.length); + UDPPacket packet = _builder2.buildRelayResponse(idata, alice); + if (_log.shouldDebug()) + _log.debug("Send relay response " + " nonce " + nonce + " to " + alice); + _transport.send(packet); + } else { + // We are Alice, give to EstablishmentManager to check sig and process + if (_log.shouldDebug()) + _log.debug("Got relay response " + " nonce " + nonce + " from " + peer); + _transport.getEstablisher().receiveRelayResponse(peer, nonce, status, data); + } } /** 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 d70f8ff9a6cd4ff56e70f34668fe492df486cbba..158b6d79f2b2eec6a3de44b9c89bc74b25408375 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -723,6 +723,7 @@ class PacketBuilder2 { * From Alice to Bob. * In-session. * + * @param signedData flag + signed data * @return null on failure */ UDPPacket buildRelayRequest(byte[] signedData, PeerState2 bob) { @@ -737,6 +738,7 @@ class PacketBuilder2 { * From Bob to Charlie. * In-session. * + * @param signedData flag + alice hash + signed data * @return null on failure */ UDPPacket buildRelayIntro(byte[] signedData, PeerState2 charlie) { @@ -750,6 +752,7 @@ class PacketBuilder2 { * From Charlie to Bob or Bob to Alice. * In-session. * + * @param signedData flag + response code + signed data + optional token * @param state Alice or Bob * @return null on failure */ diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java index 4f4878d55f3e3be080e70999931b7b7a6513bcf1..d98f6b8a30acfaf221478151cb1ebe596956b206 100644 --- a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java +++ b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java @@ -227,22 +227,25 @@ final class SSU2Util { public static byte[] createRelayRequestData(I2PAppContext ctx, Hash h, Hash h2, long nonce, long tag, byte[] ip, int port, SigningPrivateKey spk) { - int datalen = 17 + ip.length; - byte[] data = new byte[datalen + spk.getType().getSigLen()]; - //data[0] = 0; // flag - DataHelper.toLong(data, 1, 4, nonce); - DataHelper.toLong(data, 5, 4, tag); - DataHelper.toLong(data, 9, 4, ctx.clock().now() / 1000); - data[13] = 2; // version - data[14] = (byte) (ip.length + 2); - DataHelper.toLong(data, 15, 2, port); - System.arraycopy(ip, 0, data, 17, ip.length); + int datalen = 16 + ip.length; + byte[] data = new byte[datalen]; + DataHelper.toLong(data, 0, 4, nonce); + DataHelper.toLong(data, 4, 4, tag); + DataHelper.toLong(data, 8, 4, ctx.clock().now() / 1000); + data[12] = 2; // version + data[13] = (byte) (ip.length + 2); + DataHelper.toLong(data, 14, 2, port); + System.arraycopy(ip, 0, data, 16, ip.length); Signature sig = sign(ctx, RELAY_REQUEST_PROLOGUE, h, h2, data, datalen, spk); if (sig == null) return null; + int len = 1 + datalen + spk.getType().getSigLen(); + byte[] rv = new byte[len]; + //rv[0] = 0; // flag + System.arraycopy(data, 0, rv, 1, data.length); byte[] s = sig.getData(); - System.arraycopy(s, 0, data, datalen, s.length); - return data; + System.arraycopy(s, 0, rv, 1 + datalen, s.length); + return rv; } /** @@ -250,28 +253,36 @@ final class SSU2Util { * * @param h Bob hash to be included in sig, not included in data * @param ip non-null + * @param token if nonzero, append it * @return null on failure * @since 0.9.55 */ public static byte[] createRelayResponseData(I2PAppContext ctx, Hash h, int code, long nonce, byte[] ip, int port, - SigningPrivateKey spk) { - int datalen = 14 + ip.length; - byte[] data = new byte[datalen + spk.getType().getSigLen()]; - //data[0] = 0; // flag - data[1] = (byte) code; - DataHelper.toLong(data, 2, 4, nonce); - DataHelper.toLong(data, 6, 4, ctx.clock().now() / 1000); - data[10] = 2; // version - data[11] = (byte) (ip.length + 2); - DataHelper.toLong(data, 12, 2, port); - System.arraycopy(ip, 0, data, 14, ip.length); + SigningPrivateKey spk, long token) { + int datalen = 12 + ip.length; + byte[] data = new byte[datalen]; + DataHelper.toLong(data, 0, 4, nonce); + DataHelper.toLong(data, 4, 4, ctx.clock().now() / 1000); + data[8] = 2; // version + data[9] = (byte) (ip.length + 2); + DataHelper.toLong(data, 10, 2, port); + System.arraycopy(ip, 0, data, 12, ip.length); Signature sig = sign(ctx, RELAY_RESPONSE_PROLOGUE, h, null, data, datalen, spk); if (sig == null) return null; + int len = 2 + datalen + spk.getType().getSigLen(); + if (token != 0) + len += 8; + byte[] rv = new byte[len]; + //rv[0] = 0; // flag + rv[1] = (byte) code; + System.arraycopy(data, 0, rv, 2, data.length); byte[] s = sig.getData(); - System.arraycopy(s, 0, data, datalen, s.length); - return data; + System.arraycopy(s, 0, rv, 2 + datalen, s.length); + if (token != 0) + DataHelper.toLong8(rv, 2 + datalen + s.length, token); + return rv; } /**