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.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classSSU 2 Charlie only.private classSSU 1 or 2.private classSimple fix for RI getting there before PeerTest.private classWait for RI.private classCollects the payloads of one peer test reply.private classFor when we are Bob or Charlie: SSU 1 Bob/Charlie and SSU 2 Bob forget about charlie's nonce after a short while.private static final classLast hash whose RouterInfo lookup missed, with the time it was recorded.Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Map<Long, PeerTestState> Map of Long(nonce) to PeerTestState for tests currently in progress (as Bob/Charlie).private final RouterContextThe router context.private PeerTestStateCurrent test we are running (as Alice), or null.private booleanTrue when the current test round is done.private final LogLogger for SSU peer tests.private final PacketBuilder2Builder for SSU2 peer test packets.Test being run as Alice.private static final ThreadLocal<ChaChaPolyCipherState> Per-thread ChaChaPoly scratch for decrypting out-of-session Peer Test packets.private PeerTestManager.RIMissSingle-entry negative cache for "no RouterInfo for this Peer Test".private final IPThrottlerThrottles the rate of peer tests.private final UDPTransportThe SSU transport we test through.(package private) static final intNo retransmit needed.(package private) static final intRetransmit msg 1 to Bob.(package private) static final intSend msg 6 to Charlie.private static final booleanWhether the SSU2 symmetric NAT test runs.private static final intTests running as Bob/Charlie.private static final intLongest we will keep track of the test as Bob to forward the response from Charlie.private static final intLongest we will keep track of a Charlie nonce for.private static final longLargest possible peer test nonce.private static final intFor the throttler.private static final intRecent test records kept.private static final intRelays allowed per test for Alice.private static final longMaximum clock skew accepted in a test.private static final intLongest a single peer test may take.private static final InetAddressMarker IP for pending SSU2 introducers.private static final intMarker port for pending SSU2 introducers.private static final intInitial timeout; ContinueTimer adds backoff.private static final longHow long after msg 4 we may send msg 6 without having seen msg 5.private static final longHow long a "no RouterInfo for this Peer Test" answer is reused.private static final longInterval between throttle cleanups.private static final byte[]Used to wipe the reused per-thread cipher's key schedule after use.Constructor Summary
ConstructorsConstructorDescriptionPeerTestManager(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.Method Summary
Modifier and TypeMethodDescription(package private) static intdecideRetransmit(long bobTime, long charlieTime, int charliePort, boolean haveCharlieIntroKey, long now) Which retransmit (if any) the ContinueTimer should make.private booleanexpired(PeerTestState state) Has the given test (which may be null) run past its time budget?private voidfail()Reset all state and call testComplete().(package private) static RouterAddressgetAddress(List<RouterAddress> addrs, boolean isIPv6) Get an address out of a list of addresses.private RouterAddressgetAddress(RouterInfo ri, boolean isIPv6) Get an address out of a RI.(package private) static SessionKeyGet an intro key out of an address.private voidhonorStatus(CommSystemFacade.Status status, boolean isIPv6) Depending upon the status, fire off different events (using received port/ip/etc as necessary).private RouterInfoLook up the RouterInfo a Peer Test message refers to, with a short negative cache.voidreceiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, int status, Hash h, byte[] data) Entry point for all in-session incoming packets.private voidreceiveTest(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-7private booleanreceiveTest(RemoteHostId from, PeerState2 fromPeer, int msg, Hash h, byte[] data, int retryCount) Status 0 only, Msg 2 and 4 only, SSU 2 only.voidreceiveTest(RemoteHostId from, UDPPacket packet) Entry point for all out-of-session packets, messages 5-7 only.booleanThe next few methods are for when we are Alice.private voidsendRejectToAlice(int reason, byte[] data, PeerState2 alice) Send reject to Alice.private voidsendRIandPT(RouterInfo ri, int status, Hash hash, byte[] data, PeerState2 to, long now) Send RI and Peer Test.private voidsendTestToBob(PeerTestState test) SSU 2.private voidMessage 6.private static StringThe bare cause from a netdb store rejection, stripped of the "Invalid NetDbStore attempt" wrapper, capability flags and router ID.private voidEvaluate 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.private voidtestComplete(PeerTestState test) Complete the given test.
Field Details
_context
The router context._log
Logger for SSU peer tests._transport
The SSU transport we test through._packetBuilder2
Builder for SSU2 peer test packets._activeTests
Map of Long(nonce) to PeerTestState for tests currently in progress (as Bob/Charlie)._currentTest
Current test we are running (as Alice), or null. Volatile: cleared bytestComplete()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 _currentTestCompleteTrue when the current test round is done._recentTests
_throttle
Throttles the rate of peer tests.MAX_RELAYED_PER_TEST_ALICE
private static final int MAX_RELAYED_PER_TEST_ALICERelays allowed per test for Alice.- See Also:
MAX_CHARLIE_LIFETIME
private static final int MAX_CHARLIE_LIFETIMELongest we will keep track of a Charlie nonce for.- See Also:
MAX_BOB_LIFETIME
private static final int MAX_BOB_LIFETIMELongest 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_TESTSTests running as Bob/Charlie.- See Also:
MAX_RECENT_TESTS
MAX_PER_IP
THROTTLE_CLEAN_TIME
private static final long THROTTLE_CLEAN_TIMEInterval between throttle cleanups.- See Also:
RESEND_TIMEOUT
private static final int RESEND_TIMEOUTInitial timeout; ContinueTimer adds backoff.- See Also:
MAX_TEST_TIME
private static final int MAX_TEST_TIMELongest a single peer test may take.- See Also:
MAX_SKEW
private static final long MAX_SKEWMaximum clock skew accepted in a test.- See Also:
MAX_NONCE
PENDING_IP
Marker IP for pending SSU2 introducers.PENDING_PORT
private static final int PENDING_PORTMarker port for pending SSU2 introducers.- See Also:
ENABLE_SSU2_SYMNAT_TEST
private static final boolean ENABLE_SSU2_SYMNAT_TESTWhether the SSU2 symmetric NAT test runs.- See Also:
_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_KEYUsed 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_TTLHow long a "no RouterInfo for this Peer Test" answer is reused. @since 0.9.71+.- See Also:
_testRIMiss
Single-entry negative cache for "no RouterInfo for this Peer Test".ACTION_NONE
ACTION_SEND_BOB
static final int ACTION_SEND_BOBRetransmit msg 1 to Bob. @since 0.9.71+.- See Also:
ACTION_SEND_CHARLIE
static final int ACTION_SEND_CHARLIESend msg 6 to Charlie. @since 0.9.71+.- See Also:
SYMNAT_GRACE
private static final long SYMNAT_GRACEHow long after msg 4 we may send msg 6 without having seen msg 5. @since 0.9.71+.- See Also:
Constructor Details
PeerTestManager
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 contexttransport- the UDP transport
Method Details
runTest
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 0charlieTime- when msg 5 was received, or 0charliePort- Charlie's reported port, orPENDING_PORTif firewalledhaveCharlieIntroKey- whether msg 4 supplied Charlie's intro keynow- the current time, in milliseconds- Returns:
ACTION_NONE,ACTION_SEND_BOBorACTION_SEND_CHARLIE
expired
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
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
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
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, andrunTest(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
Depending upon the status, fire off different events (using received port/ip/etc as necessary).- Parameters:
status- the test outcome to report to the transportisIPv6- Is the change an IPv6 change?
receiveTest
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-nullpacket- 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-nullfromPeer- non-null if an associated session was found, otherwise nullmsg- 1-7status- 0 = accept, 1-255 = rejecth- Alice or Charlie hash for msg 2 and 4, null for msg 1, 3, 5-7data- 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 senderfromPeer- peer state of the sendermsg- message number, 2 or 4h- hash of the RouterInfo the test refers todata- signed peer test data, kept for the retryretryCount- retries so far, 0 on first receipt; capped at 5- Returns:
- true if RI found, false to delay and retry.
lookupRIForTest
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-7SSU 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-nullfromPeer- non-null if an associated session was found, otherwise nullmsg- 1-7status- 0 = accept, 1-255 = rejecth- Alice or Charlie hash for msg 2 and 4, null for msg 1, 3, 5-7data- excludes flag, includes signatureaddrBlockIP- only used for msgs 5-7, otherwise nulladdrBlockPort- only used for msgs 5-7, otherwise 0
sendRejectToAlice
Send reject to Alice. We are Bob. SSU2 only.- Parameters:
reason- reject reason code to senddata- received peer test data echoed in the rejectalice- peer state to send the reject to
getAddress
Get an address out of a RI. SSU2 only.- Parameters:
ri- the router info to pick an address fromisIPv6- true to look for IPv6 addresses, false for IPv4- Returns:
- address or null
getAddress
Get an address out of a list of addresses. SSU2 only.- Parameters:
addrs- the list of addressesisIPv6- whether to look for IPv6 addresses- Returns:
- address or null
getIntroKey
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 notstatus- -1 for msg 2, nonnegative for msg 4hash- alice for msg 2, charlie for msg 4data- signed peer test datato- charlie for msg 2, alice for msg 4now- the current time in ms for packet expiration- Throws:
IOException- if to peer is dead
storeFailCause
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