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 d805f4b1b..af9c19338 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -1462,7 +1462,7 @@ class EstablishmentManager { if (charlie == null) { if (_log.shouldDebug()) _log.debug("Dup or unknown RelayResponse: " + nonce); - return; // already established + return; // already established, or we were Bob and got a dup from Charlie } if (charlie.getVersion() != 2) return; 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 9fd8ec5da..df21eaaa8 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -35,52 +35,7 @@ import net.i2p.util.VersionComparator; /** * Keep track of inbound and outbound introductions. - * - * IPv6 info: Alice-Bob communication may be via IPv4 or IPv6. - * Bob-Charlie communication must be via established IPv4 session as that's the only way - * that Bob knows Charlie's IPv4 address to give it to Alice. - * Alice-Charlie communication is via IPv4. - * If Alice-Bob is over IPv6, Alice must include her IPv4 address in - * the RelayRequest message. - * - * From udp.html on the website: - -

Indirect session establishment by means of a third party introduction -is necessary for efficient NAT traversal. Charlie, a router behind a -NAT or firewall which does not allow unsolicited inbound UDP packets, -first contacts a few peers, choosing some to serve as introducers. Each -of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction -tag - a 4 byte random number - which he then makes available to the public -as methods of contacting him. Alice, a router who has Charlie's published -contact methods, first sends a RelayRequest packet to one or more of the -introducers, asking each to introduce her to Charlie (offering the -introduction tag to identify Charlie). Bob then forwards a RelayIntro -packet to Charlie including Alice's public IP and port number, then sends -Alice back a RelayResponse packet containing Charlie's public IP and port -number. When Charlie receives the RelayIntro packet, he sends off a small -random packet to Alice's IP and port (poking a hole in his NAT/firewall), -and when Alice receives Bob's RelayResponse packet, she begins a new -full direction session establishment with the specified IP and port.

-

-Alice first connects to introducer Bob, who relays the request to Charlie. -

-
-        Alice                         Bob                  Charlie
-    RelayRequest ---------------------->
-         <-------------- RelayResponse    RelayIntro ----------->
-         <-------------------------------------------- HolePunch (data ignored)
-    SessionRequest -------------------------------------------->
-         <-------------------------------------------- SessionCreated
-    SessionConfirmed ------------------------------------------>
-         <-------------------------------------------- DeliveryStatusMessage
-         <-------------------------------------------- DatabaseStoreMessage
-    DatabaseStoreMessage -------------------------------------->
-    Data <--------------------------------------------------> Data
-
- -

-After the hole punch, the session is established between Alice and Charlie as in a direct establishment. -

+ * See http://i2p-projekt.i2p/spec/ssu2 for documentation. */ class IntroductionManager { private final RouterContext _context; @@ -116,6 +71,12 @@ class IntroductionManager { private static final String MIN_IPV6_INTRODUCER_VERSION = "0.9.50"; private static final long MAX_SKEW = 2*60*1000; + /** + * See receiveRelayIntro() + * @since 0.9.65 + */ + public enum RelayIntroResult { REPLIED, DELAYED, DROPPED } + public IntroductionManager(RouterContext ctx, UDPTransport transport) { _context = ctx; _log = ctx.logManager().getLog(IntroductionManager.class); @@ -632,10 +593,16 @@ class IntroductionManager { * * SSU 2 only. * + * @return DELAYED if awaiting Alice RI, DROPPED on fatal error, or REPLIED if we replied to Bob with a RelayResponse * @since 0.9.55 */ - void receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data) { - receiveRelayIntro(bob, alice, data, 0); + RelayIntroResult receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data) { + RelayIntroResult rv = receiveRelayIntro(bob, alice, data, 0); + if (_log.shouldInfo()) + _log.info("Receive relay intro from bob " + bob + + " for alice " + alice.toBase64() + + " result " + rv); + return rv; } /** @@ -647,10 +614,10 @@ class IntroductionManager { * * SSU 2 only. * - * @return true if RI found, false to delay and retry. + * @return DELAYED if should retry (no RI), DROPPED on fatal error, or REPLIED if we replied to Bob with a RelayResponse * @since 0.9.55 */ - private boolean receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, int retryCount) { + private RelayIntroResult receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, int retryCount) { RouterInfo aliceRI = null; if (retryCount >= 5) { // last chance @@ -662,11 +629,11 @@ class IntroductionManager { _log.info("Delay after " + retryCount + " retries, no RI for " + alice.toBase64()); if (retryCount == 0) new DelayIntro(bob, alice, data); - return false; + return RelayIntroResult.DELAYED; } } - receiveRelayIntro(bob, alice, data, aliceRI); - return true; + boolean rv = receiveRelayIntro(bob, alice, data, aliceRI); + return rv ? RelayIntroResult.REPLIED : RelayIntroResult.DROPPED; } /** @@ -689,8 +656,8 @@ class IntroductionManager { } public void timeReached() { - boolean ok = receiveRelayIntro(bob, alice, data, ++count); - if (!ok) + RelayIntroResult result = receiveRelayIntro(bob, alice, data, ++count); + if (result == RelayIntroResult.DELAYED) reschedule(DELAY << count); } } @@ -722,9 +689,11 @@ class IntroductionManager { * * SSU 2 only. * + * @return true if we replied with a relay response, false if we dropped it; + * this does not indicate a successful response code * @since 0.9.55 */ - private void receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, RouterInfo aliceRI) { + private boolean receiveRelayIntro(PeerState2 bob, Hash alice, byte[] data, RouterInfo aliceRI) { long nonce = DataHelper.fromLong(data, 0, 4); long tag = DataHelper.fromLong(data, 4, 4); long time = DataHelper.fromLong(data, 8, 4) * 1000; @@ -733,19 +702,19 @@ class IntroductionManager { if (skew > MAX_SKEW || skew < 0 - MAX_SKEW) { if (_log.shouldWarn()) _log.warn("Too skewed for relay intro from " + bob); - return; + return false; } int ver = data[12] & 0xff; if (ver != 2) { if (_log.shouldWarn()) _log.warn("Bad relay intro version " + ver + " from " + bob); - return; + return false; } int iplen = data[13] & 0xff; if (iplen != 6 && iplen != 18) { if (_log.shouldWarn()) _log.warn("Bad IP length " + iplen + " from " + bob); - return; + return false; } boolean isIPv6 = iplen == 18; int testPort = (int) DataHelper.fromLong(data, 14, 2); @@ -755,7 +724,7 @@ class IntroductionManager { try { aliceIP = InetAddress.getByAddress(testIP); } catch (UnknownHostException uhe) { - return; + return false; } SessionKey aliceIntroKey = null; @@ -828,7 +797,7 @@ class IntroductionManager { if (data == null) { if (_log.shouldWarn()) _log.warn("sig fail"); - return; + return false; } try { UDPPacket packet = _builder2.buildRelayResponse(data, bob); @@ -839,7 +808,7 @@ class IntroductionManager { _transport.send(packet); bob.setLastSendTime(now); } catch (IOException ioe) { - return; + return false; } if (rcode == SSU2Util.RELAY_ACCEPT) { // send hole punch with the same data we sent to Bob @@ -850,6 +819,7 @@ class IntroductionManager { UDPPacket packet = _builder2.buildHolePunch(aliceIP, testPort, aliceIntroKey, sendId, rcvId, data); _transport.send(packet); } + return true; } /** 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 6dec4a923..033b4f450 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java @@ -734,9 +734,12 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback } public void gotRelayIntro(Hash aliceHash, byte[] data) { - _transport.getIntroManager().receiveRelayIntro(this, aliceHash, data); + IntroductionManager.RelayIntroResult result = _transport.getIntroManager().receiveRelayIntro(this, aliceHash, data); // Relay blocks are ACK-eliciting - messagePartiallyReceived(); + // but we will usually respond to Bob immediately with a relay response, which includes the ack, + // so only schedule an ack if we didn't respond + if (result != IntroductionManager.RelayIntroResult.REPLIED) + messagePartiallyReceived(); } public void gotPeerTest(int msg, int status, Hash h, byte[] data) {