java.lang.Object
net.i2p.router.transport.udp.PeerTestManager

class PeerTestManagerextends Object
Entry points are runTest(PeerState) to start a new test as Alice, and receiveTest(RemoteHostId, UDPPacket) for all received test packets.

IPv6 info: All Alice-Bob and Alice-Charlie communication is via IPv4. The Bob-Charlie communication may be via IPv6, however Charlie must be IPv4-capable. The IP address (of Alice) in the message must be IPv4 if present, as we only support testing of IPv4. Testing of IPv6 could be added in the future.

From udp.html on the website.

The automation of collaborative reachability testing for peers is enabled by a sequence of PeerTest messages. With its proper execution, a peer will be able to determine their own reachability and may update its behavior accordingly. The testing process is quite simple:

        Alice                  Bob                  Charlie

    runTest()
    sendTestToBob()     receiveFromAliceAsBob()
    PeerTest ------------------->

                        sendTestToCharlie()       receiveFromBobAsCharlie()
                             PeerTest-------------------->

                        receiveFromCharlieAsBob()
                                <-------------------PeerTest

    receiveTestReply()
         <-------------------PeerTest

    receiveTestReply()
         <------------------------------------------PeerTest

                                                  receiveFromAliceAsCharlie()
    PeerTest------------------------------------------>

    receiveTestReply()
         <------------------------------------------PeerTest

Each of the PeerTest messages carry a nonce identifying the test series itself, as initialized by Alice. If Alice doesn't get a particular message that she expects, she will retransmit accordingly, and based upon the data received or the messages missing, she will know her reachability. The various end states that may be reached are as follows:

  • If she doesn't receive a response from Bob, she will retransmit up to a certain number of times, but if no response ever arrives, she will know that her firewall or NAT is somehow misconfigured, rejecting all inbound UDP packets even in direct response to an outbound packet. Alternately, Bob may be down or unable to get Charlie to reply.
  • If Alice doesn't receive a PeerTest message with the expected nonce from a third party (Charlie), she will retransmit her initial request to Bob up to a certain number of times, even if she has received Bob's reply already. If Charlie's first message still doesn't get through but Bob's does, she knows that she is behind a NAT or firewall that is rejecting unsolicited connection attempts and that port forwarding is not operating properly (the IP and port that Bob offered up should be forwarded).
  • If Alice receives Bob's PeerTest message and both of Charlie's PeerTest messages but the enclosed IP and port numbers in Bob's and Charlie's second messages don't match, she knows that she is behind a symmetric NAT, rewriting all of her outbound packets with different 'from' ports for each peer contacted. She will need to explicitly forward a port and always have that port exposed for remote connectivity, ignoring further port discovery.
  • If Alice receives Charlie's first message but not his second, she will retransmit her PeerTest message to Charlie up to a certain number of times, but if no response is received she knows that Charlie is either confused or no longer online.

Alice should choose Bob arbitrarily from known peers who seem to be capable of participating in peer tests. Bob in turn should choose Charlie arbitrarily from peers that he knows who seem to be capable of participating in peer tests and who are on a different IP from both Bob and Alice. If the first error condition occurs (Alice doesn't get PeerTest messages from Bob), Alice may decide to designate a new peer as Bob and try again with a different nonce.

Alice's introduction key is included in all of the PeerTest messages so that she doesn't need to already have an established session with Bob and so that Charlie can contact her without knowing any additional information. Alice may go on to establish a session with either Bob or Charlie, but it is not required.

  • Field Details

    • _context

      private final RouterContext _context
      The router context.
    • _log

      private final Log _log
      Logger for SSU peer tests.
    • _transport

      private final UDPTransport _transport
      The SSU transport we test through.
    • _packetBuilder2

      private final PacketBuilder2 _packetBuilder2
      Builder for SSU2 peer test packets.
    • _activeTests

      private final Map<Long, PeerTestState> _activeTests
      Map of Long(nonce) to PeerTestState for tests currently in progress (as Bob/Charlie).
    • _currentTest

      private volatile PeerTestState _currentTest
      Current test we are running (as Alice), or null. Volatile: cleared by testComplete() on the UDP handle threads as well as set under the instance lock by runTest(), and ContinueTimer reads it after dropping the lock.
    • _currentTestComplete

      private volatile boolean _currentTestComplete
      True when the current test round is done.
    • _recentTests

      private final Queue<Long> _recentTests
      Test being run as Alice.
    • _throttle

      private final IPThrottler _throttle
      Throttles the rate of peer tests.
    • MAX_RELAYED_PER_TEST_ALICE

      private static final int MAX_RELAYED_PER_TEST_ALICE
      Relays allowed per test for Alice.
      See Also:
    • MAX_CHARLIE_LIFETIME

      private static final int MAX_CHARLIE_LIFETIME
      Longest we will keep track of a Charlie nonce for.
      See Also:
    • MAX_BOB_LIFETIME

      private static final int MAX_BOB_LIFETIME
      Longest we will keep track of the test as Bob to forward the response from Charlie.
      See Also:
    • MAX_ACTIVE_TESTS

      private static final int MAX_ACTIVE_TESTS
      Tests running as Bob/Charlie.
      See Also:
    • MAX_RECENT_TESTS

      private static final int MAX_RECENT_TESTS
      Recent test records kept.
      See Also:
    • MAX_PER_IP

      private static final int MAX_PER_IP
      For the throttler.
      See Also:
    • THROTTLE_CLEAN_TIME

      private static final long THROTTLE_CLEAN_TIME
      Interval between throttle cleanups.
      See Also:
    • RESEND_TIMEOUT

      private static final int RESEND_TIMEOUT
      Initial timeout; ContinueTimer adds backoff.
      See Also:
    • MAX_TEST_TIME

      private static final int MAX_TEST_TIME
      Longest a single peer test may take.
      See Also:
    • MAX_SKEW

      private static final long MAX_SKEW
      Maximum clock skew accepted in a test.
      See Also:
    • MAX_NONCE

      private static final long MAX_NONCE
      Largest possible peer test nonce.
      See Also:
    • PENDING_IP

      private static final InetAddress PENDING_IP
      Marker IP for pending SSU2 introducers.
    • PENDING_PORT

      private static final int PENDING_PORT
      Marker port for pending SSU2 introducers.
      See Also:
    • ENABLE_SSU2_SYMNAT_TEST

      private static final boolean ENABLE_SSU2_SYMNAT_TEST
      Whether the SSU2 symmetric NAT test runs.
      See Also:
    • _testCipher

      private static final ThreadLocal<ChaChaPolyCipherState> _testCipher
      Per-thread ChaChaPoly scratch for decrypting out-of-session Peer Test packets. Every such packet uses the same static intro key and sets the nonce from the packet number, so one instance per thread is safe.
    • ZERO_INTRO_KEY

      private static final byte[] ZERO_INTRO_KEY
      Used to wipe the reused per-thread cipher's key schedule after use. @since 0.9.71+
    • TEST_RI_MISS_TTL

      private static final long TEST_RI_MISS_TTL
      How long a "no RouterInfo for this Peer Test" answer is reused. @since 0.9.71+.
      See Also:
    • _testRIMiss

      private volatile PeerTestManager.RIMiss _testRIMiss
      Single-entry negative cache for "no RouterInfo for this Peer Test".
    • ACTION_NONE

      static final int ACTION_NONE
      No retransmit needed. @since 0.9.71+.
      See Also:
    • ACTION_SEND_BOB

      static final int ACTION_SEND_BOB
      Retransmit msg 1 to Bob. @since 0.9.71+.
      See Also:
    • ACTION_SEND_CHARLIE

      static final int ACTION_SEND_CHARLIE
      Send msg 6 to Charlie. @since 0.9.71+.
      See Also:
    • SYMNAT_GRACE

      private static final long SYMNAT_GRACE
      How long after msg 4 we may send msg 6 without having seen msg 5. @since 0.9.71+.
      See Also:
  • Constructor Details

    • PeerTestManager

      public PeerTestManager(RouterContext context, UDPTransport transport)
      Have seen peer tests (as Alice) get stuck (_currentTest != null) so I've thrown some synchronizization on the methods; don't know the root cause or whether this fixes it.
      Parameters:
      context - the router context
      transport - the UDP transport
  • Method Details

    • runTest

      public boolean runTest(PeerState bob)
      The next few methods are for when we are Alice.
      Parameters:
      bob - IPv4 only
      Returns:
      true if we successfully started a test
    • decideRetransmit

      static int decideRetransmit(long bobTime, long charlieTime, int charliePort, boolean haveCharlieIntroKey, long now)
      Which retransmit (if any) the ContinueTimer should make. Pure decision logic, with the test state passed in as scalars so it is testable without a running test.
      Parameters:
      bobTime - when msg 4 was received, or 0
      charlieTime - when msg 5 was received, or 0
      charliePort - Charlie's reported port, or PENDING_PORT if firewalled
      haveCharlieIntroKey - whether msg 4 supplied Charlie's intro key
      now - the current time, in milliseconds
      Returns:
      ACTION_NONE, ACTION_SEND_BOB or ACTION_SEND_CHARLIE
    • expired

      private boolean expired(PeerTestState state)
      Has the given test (which may be null) run past its time budget?
      Parameters:
      state - the test being tracked, or null when no test is running
      Returns:
      true if the test is null or expired
    • sendTestToBob

      private void sendTestToBob(PeerTestState test)
      SSU 2. We are Alice. Builds and enqueues msg 1. Packet construction can block, so this runs without the instance lock (from ContinueTimer) and takes the state as a parameter.
      Parameters:
      test - the current test state, non-null
    • sendTestToCharlie

      private void sendTestToCharlie(PeerTestState test)
      Message 6. SSU 2. We are Alice. Packet construction can block, so this runs without the instance lock (from ContinueTimer) and takes the state as a parameter.
      Parameters:
      test - the current test state; test.getCharlieIntroKey() must be non-null
    • fail

      private void fail()
      Reset all state and call testComplete(). We are Alice.

      call from a synchronized method.

    • testComplete

      private void testComplete()
      Evaluate the info we have and act accordingly, since the test has either timed out or we have successfully received the second PeerTest from a Charlie.

      call from a synchronized method

      Old SSU 1 test result state machine: ref: SSU 1 doc http://i2p-projekt.i2p/en/docs/transport/ssu

       If Alice does not get msg 5:  FIREWALLED
      
       If Alice gets msg 5:
       .. If Alice does not get msg 4:  UNKNOWN (says the SSU 1 spec,
       but Java I2P proceeds for SSU 1)
       .. If Alice does not get msg 7:  UNKNOWN
       .. If Alice gets msgs 4/5/7 and IP/port match:  OK
       .. If Alice gets msgs 4/5/7 and IP/port do not match:  SYMNAT
      

      New SSU 1/2 test result state machine:

       If Alice does not get msg 5:
       .. If Alice does not get msg 4:  UNKNOWN
       .. If Alice does not get msg 7:  UNKNOWN
       .. If Alice gets msgs 4/7 and IP/port match:  FIREWALLED
       .. If Alice gets msgs 4/7 and IP matches, port does not match:  SYMNAT, but needs confirmation with 2nd test
       .. If Alice gets msgs 4/7 and IP does not match, port matches:  FIREWALLED, address change?
       .. If Alice gets msgs 4/7 and both IP and port do not match:  SYMNAT, address change?
      
       If Alice gets msg 5:
       .. If Alice does not get msg 4:  OK unless currently SYMNAT, else UNKNOWN
       (in SSU2 have to stop here anyway)
       .. If Alice does not get msg 7:  OK unless currently SYMNAT, else UNKNOWN
       .. If Alice gets msgs 4/5/7 and IP/port match:  OK
       .. If Alice gets msgs 4/5/7 and IP matches, port does not match:  OK, charlie is probably sym. natted
       .. If Alice gets msgs 4/5/7 and IP does not match, port matches:  OK, address change?
       .. If Alice gets msgs 4/5/7 and both IP and port do not match:  OK, address change?
      

      Simplified version:

       4 5 7  Result             Notes
       -----  ------             -----
       n n n  UNKNOWN
       y n n  FIREWALLED           (unless currently SYMNAT)
       n y n  OK                   (unless currently SYMNAT, which is unlikely)
       y y n  OK                   (unless currently SYMNAT, which is unlikely)
       n n y  n/a                  (can't send msg 6)
       y n y  FIREWALLED or SYMNAT (requires sending msg 6 w/o rcv msg 5)
       n y y  n/a                  (can't send msg 6)
       y y y  OK
      
    • testComplete

      private void testComplete(PeerTestState test)
      Complete the given test.

      The test is passed in rather than re-read from _currentTest, so a caller that decided to complete a specific test cannot end up completing its successor: PeerTestManager.ContinueTimer.timeReached() releases the instance lock to send, and runTest(PeerState) can install a new test in that window. Re-reading here would publish a reachability status against the wrong test.

      Parameters:
      test - the test being completed, or null to do nothing
    • honorStatus

      private void honorStatus(CommSystemFacade.Status status, boolean isIPv6)
      Depending upon the status, fire off different events (using received port/ip/etc as necessary).
      Parameters:
      status - the test outcome to report to the transport
      isIPv6 - Is the change an IPv6 change?
    • receiveTest

      public void receiveTest(RemoteHostId from, UDPPacket packet)
      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.

      Parameters:
      from - non-null
      packet - header already decrypted
    • receiveTest

      public void receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, int status, Hash h, byte[] data)
      Entry point for all in-session incoming packets.

      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, Bob, or Charlie.

      Parameters:
      from - non-null
      fromPeer - non-null if an associated session was found, otherwise null
      msg - 1-7
      status - 0 = accept, 1-255 = reject
      h - Alice or Charlie hash for msg 2 and 4, null for msg 1, 3, 5-7
      data - excludes flag, includes signature
    • receiveTest

      private boolean receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, Hash h, byte[] data, int retryCount)
      Status 0 only, Msg 2 and 4 only, SSU 2 only. Bob should have sent us the RI, but maybe it's in the block after this, or maybe it's in a different packet. Check for RI, if not found, return true to retry, unless retryCount is at the limit. Creates the timer if retryCount == 0.

      We are Alice for msg 4, Charlie for msg 2.

      Parameters:
      from - remote host id of the sender
      fromPeer - peer state of the sender
      msg - message number, 2 or 4
      h - hash of the RouterInfo the test refers to
      data - signed peer test data, kept for the retry
      retryCount - retries so far, 0 on first receipt; capped at 5
      Returns:
      true if RI found, false to delay and retry.
    • lookupRIForTest

      private RouterInfo lookupRIForTest(Hash h)
      Look up the RouterInfo a Peer Test message refers to, with a short negative cache.

      DelayTest retries on a 50ms doubling backoff, so a miss would otherwise be re-probed on every retry. The cache is single-entry: it only collapses the closely spaced retries of one hash, and concurrent tests for different hashes do not share it. Only misses are cached, so a hit is never stale.

      Parameters:
      h - the hash to look up, non-null
      Returns:
      the RouterInfo, or null if not found (or looked up too recently)
    • receiveTest

      private void receiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, int status, Hash h, byte[] data, byte[] addrBlockIP, int addrBlockPort)
      Called from above for in-session 1-4 or the PTCallback via processPayload() for out-of-session 5-7

      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, Bob, or Charlie.

      Parameters:
      from - non-null
      fromPeer - non-null if an associated session was found, otherwise null
      msg - 1-7
      status - 0 = accept, 1-255 = reject
      h - Alice or Charlie hash for msg 2 and 4, null for msg 1, 3, 5-7
      data - excludes flag, includes signature
      addrBlockIP - only used for msgs 5-7, otherwise null
      addrBlockPort - only used for msgs 5-7, otherwise 0
    • sendRejectToAlice

      private void sendRejectToAlice(int reason, byte[] data, PeerState2 alice)
      Send reject to Alice. We are Bob. SSU2 only.
      Parameters:
      reason - reject reason code to send
      data - received peer test data echoed in the reject
      alice - peer state to send the reject to
    • getAddress

      private RouterAddress getAddress(RouterInfo ri, boolean isIPv6)
      Get an address out of a RI. SSU2 only.
      Parameters:
      ri - the router info to pick an address from
      isIPv6 - true to look for IPv6 addresses, false for IPv4
      Returns:
      address or null
    • getAddress

      static RouterAddress getAddress(List<RouterAddress> addrs, boolean isIPv6)
      Get an address out of a list of addresses. SSU2 only.
      Parameters:
      addrs - the list of addresses
      isIPv6 - whether to look for IPv6 addresses
      Returns:
      address or null
    • getIntroKey

      static SessionKey getIntroKey(RouterAddress ra)
      Get an intro key out of an address. SSU2 only.
      Parameters:
      ra - the router address
      Returns:
      the intro key or null
    • sendRIandPT

      private void sendRIandPT(RouterInfo ri, int status, Hash hash, byte[] data, PeerState2 to, long now) throws IOException
      Send RI and Peer Test. SSU2 only. We are Bob.

      Msg 2 Bob to Charlie with Alice's RI Msg 4 Bob to Alice with Charlie's RI.

      Parameters:
      ri - may be null, but hopefully not
      status - -1 for msg 2, nonnegative for msg 4
      hash - alice for msg 2, charlie for msg 4
      data - signed peer test data
      to - charlie for msg 2, alice for msg 4
      now - the current time in ms for packet expiration
      Throws:
      IOException - if to peer is dead
    • storeFailCause

      private static String storeFailCause(IllegalArgumentException iae)
      The bare cause from a netdb store rejection, stripped of the "Invalid NetDbStore attempt" wrapper, capability flags and router ID.
      Parameters:
      iae - the rejection from the netdb
      Returns:
      the cause, for log output