SSU: Add support for IPv6 SSU Peer Testing

(ticket #1752; proposal #126)
In PeerTestManager, this is simply the removal of v6 restrictions,
and the tracking of whether we are testing v4 or v6.
In UDPTransport, track v4 and v6 peer tests separately.
This commit is contained in:
zzz
2016-06-25 21:25:12 +00:00
parent 2506f6b143
commit 2c3311b471
5 changed files with 170 additions and 76 deletions

View File

@ -1,3 +1,6 @@
2016-06-26 zzz
* SSU peer testing: Add implementation (ticket #1752; proposal #126)
2016-06-22 zzz 2016-06-22 zzz
* SSU peer testing: * SSU peer testing:
- Forget the test and don't keep retransmitting to Charlie - Forget the test and don't keep retransmitting to Charlie

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 2; public final static long BUILD = 3;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";

View File

@ -1,6 +1,7 @@
package net.i2p.router.transport.udp; package net.i2p.router.transport.udp;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
@ -187,7 +188,7 @@ class PeerTestManager {
_log.warn("Not running test with Bob too close to us " + bobIP); _log.warn("Not running test with Bob too close to us " + bobIP);
return; return;
} }
PeerTestState test = new PeerTestState(ALICE, PeerTestState test = new PeerTestState(ALICE, bobIP instanceof Inet6Address,
_context.random().nextLong(MAX_NONCE), _context.random().nextLong(MAX_NONCE),
_context.clock().now()); _context.clock().now());
test.setBobIP(bobIP); test.setBobIP(bobIP);
@ -315,7 +316,9 @@ class PeerTestManager {
// The reply is from Bob // The reply is from Bob
int ipSize = testInfo.readIPSize(); int ipSize = testInfo.readIPSize();
if (ipSize != 4) { boolean expectV6 = test.isIPv6();
if ((!expectV6 && ipSize != 4) ||
(expectV6 && ipSize != 16)) {
// There appears to be a bug where Bob is sending us a zero-length IP. // There appears to be a bug where Bob is sending us a zero-length IP.
// We could proceed without setting the IP, but then when Charlie // We could proceed without setting the IP, but then when Charlie
// sends us his message, we will think we are behind a symmetric NAT // sends us his message, we will think we are behind a symmetric NAT
@ -364,7 +367,7 @@ class PeerTestManager {
// why are we doing this instead of calling testComplete() ? // why are we doing this instead of calling testComplete() ?
_currentTestComplete = true; _currentTestComplete = true;
_context.statManager().addRateData("udp.statusKnownCharlie", 1); _context.statManager().addRateData("udp.statusKnownCharlie", 1);
honorStatus(Status.UNKNOWN); honorStatus(Status.UNKNOWN, test.isIPv6());
_currentTest = null; _currentTest = null;
return; return;
} }
@ -377,8 +380,11 @@ class PeerTestManager {
throw new UnknownHostException("port 0"); throw new UnknownHostException("port 0");
test.setAlicePortFromCharlie(testPort); test.setAlicePortFromCharlie(testPort);
byte ip[] = new byte[testInfo.readIPSize()]; byte ip[] = new byte[testInfo.readIPSize()];
if (ip.length != 4) int ipSize = ip.length;
throw new UnknownHostException("not IPv4"); boolean expectV6 = test.isIPv6();
if ((!expectV6 && ipSize != 4) ||
(expectV6 && ipSize != 16))
throw new UnknownHostException("bad sz - expect v6? " + expectV6 + " act sz: " + ipSize);
testInfo.readIP(ip, 0); testInfo.readIP(ip, 0);
InetAddress addr = InetAddress.getByAddress(ip); InetAddress addr = InetAddress.getByAddress(ip);
test.setAliceIPFromCharlie(addr); test.setAliceIPFromCharlie(addr);
@ -442,22 +448,24 @@ class PeerTestManager {
// return; // return;
// } // }
boolean isIPv6 = test.isIPv6();
Status status; Status status;
if (test.getAlicePortFromCharlie() > 0) { if (test.getAlicePortFromCharlie() > 0) {
// we received a second message from charlie // we received a second message from charlie
if ( (test.getAlicePort() == test.getAlicePortFromCharlie()) && if ( (test.getAlicePort() == test.getAlicePortFromCharlie()) &&
(test.getAliceIP() != null) && (test.getAliceIPFromCharlie() != null) && (test.getAliceIP() != null) && (test.getAliceIPFromCharlie() != null) &&
(test.getAliceIP().equals(test.getAliceIPFromCharlie())) ) { (test.getAliceIP().equals(test.getAliceIPFromCharlie())) ) {
status = Status.IPV4_OK_IPV6_UNKNOWN; status = isIPv6 ? Status.IPV4_UNKNOWN_IPV6_OK : Status.IPV4_OK_IPV6_UNKNOWN;
} else { } else {
status = Status.IPV4_SNAT_IPV6_UNKNOWN; // we don't have a SNAT state for IPv6
status = isIPv6 ? Status.IPV4_UNKNOWN_IPV6_FIREWALLED : Status.IPV4_SNAT_IPV6_UNKNOWN;
} }
} else if (test.getReceiveCharlieTime() > 0) { } else if (test.getReceiveCharlieTime() > 0) {
// we received only one message from charlie // we received only one message from charlie
status = Status.UNKNOWN; status = Status.UNKNOWN;
} else if (test.getReceiveBobTime() > 0) { } else if (test.getReceiveBobTime() > 0) {
// we received a message from bob but no messages from charlie // we received a message from bob but no messages from charlie
status = Status.IPV4_FIREWALLED_IPV6_UNKNOWN; status = isIPv6 ? Status.IPV4_UNKNOWN_IPV6_FIREWALLED : Status.IPV4_FIREWALLED_IPV6_UNKNOWN;
} else { } else {
// we never received anything from bob - he is either down, // we never received anything from bob - he is either down,
// ignoring us, or unable to get a Charlie to respond // ignoring us, or unable to get a Charlie to respond
@ -467,7 +475,7 @@ class PeerTestManager {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Test complete: " + test); _log.info("Test complete: " + test);
honorStatus(status); honorStatus(status, isIPv6);
if (forgetTest) if (forgetTest)
_currentTest = null; _currentTest = null;
} }
@ -476,11 +484,12 @@ class PeerTestManager {
* Depending upon the status, fire off different events (using received port/ip/etc as * Depending upon the status, fire off different events (using received port/ip/etc as
* necessary). * necessary).
* *
* @param isIPv6 Is the change an IPv6 change?
*/ */
private void honorStatus(Status status) { private void honorStatus(Status status, boolean isIPv6) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Test results: status = " + status); _log.info("Test results (IPv6? " + isIPv6 + "): status = " + status);
_transport.setReachabilityStatus(status); _transport.setReachabilityStatus(status, isIPv6);
} }
/** /**
@ -518,7 +527,7 @@ class PeerTestManager {
if ((testPort > 0 && (!TransportUtil.isValidPort(testPort))) || if ((testPort > 0 && (!TransportUtil.isValidPort(testPort))) ||
(testIP != null && (testIP != null &&
((!_transport.isValid(testIP)) || ((!_transport.isValid(testIP)) ||
testIP.length != 4 || (testIP.length != 4 && testIP.length != 16) ||
_context.blocklist().isBlocklisted(testIP)))) { _context.blocklist().isBlocklisted(testIP)))) {
// spoof check, and don't respond to privileged ports // spoof check, and don't respond to privileged ports
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
@ -645,10 +654,11 @@ class PeerTestManager {
*/ */
private void receiveFromBobAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) { private void receiveFromBobAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
long now = _context.clock().now(); long now = _context.clock().now();
int sz = testInfo.readIPSize();
boolean isNew = false; boolean isNew = false;
if (state == null) { if (state == null) {
isNew = true; isNew = true;
state = new PeerTestState(CHARLIE, nonce, now); state = new PeerTestState(CHARLIE, sz == 16, nonce, now);
} else { } else {
if (state.getReceiveBobTime() > now - (RESEND_TIMEOUT / 2)) { if (state.getReceiveBobTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
@ -658,12 +668,13 @@ class PeerTestManager {
} }
// TODO should only do most of this if isNew // TODO should only do most of this if isNew
int sz = testInfo.readIPSize();
byte aliceIPData[] = new byte[sz]; byte aliceIPData[] = new byte[sz];
try { try {
testInfo.readIP(aliceIPData, 0); testInfo.readIP(aliceIPData, 0);
if (sz != 4) boolean expectV6 = state.isIPv6();
throw new UnknownHostException("not IPv4"); if ((!expectV6 && sz != 4) ||
(expectV6 && sz != 16))
throw new UnknownHostException("bad sz - expect v6? " + expectV6 + " act sz: " + sz);
int alicePort = testInfo.readPort(); int alicePort = testInfo.readPort();
if (alicePort == 0) if (alicePort == 0)
throw new UnknownHostException("port 0"); throw new UnknownHostException("port 0");
@ -731,22 +742,27 @@ class PeerTestManager {
// we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info // we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info
PeerState charlie; PeerState charlie;
RouterInfo charlieInfo = null; RouterInfo charlieInfo = null;
int sz = from.getIP().length;
boolean isIPv6 = sz == 16;
if (state == null) { // pick a new charlie if (state == null) { // pick a new charlie
if (from.getIP().length != 4) { //if (from.getIP().length != 4) {
if (_log.shouldLog(Log.WARN)) // if (_log.shouldLog(Log.WARN))
_log.warn("PeerTest over IPv6 from Alice as Bob? " + from); // _log.warn("PeerTest over IPv6 from Alice as Bob? " + from);
return; // return;
} //}
charlie = _transport.pickTestPeer(CHARLIE, from); charlie = _transport.pickTestPeer(CHARLIE, isIPv6, from);
} else { } else {
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort())); charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
} }
if (charlie != null) if (charlie == null) {
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
if ( (charlie == null) || (charlieInfo == null) ) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie"); _log.warn("Unable to pick a charlie (no peer), IPv6? " + isIPv6);
return;
}
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
if (charlieInfo == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie (no RI), IPv6? " + isIPv6);
return; return;
} }
@ -761,14 +777,14 @@ class PeerTestManager {
RouterAddress raddr = _transport.getTargetAddress(charlieInfo); RouterAddress raddr = _transport.getTargetAddress(charlieInfo);
if (raddr == null) { if (raddr == null) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie"); _log.warn("Unable to pick a charlie (no addr), IPv6? " + isIPv6);
return; return;
} }
UDPAddress addr = new UDPAddress(raddr); UDPAddress addr = new UDPAddress(raddr);
byte[] ikey = addr.getIntroKey(); byte[] ikey = addr.getIntroKey();
if (ikey == null) { if (ikey == null) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie"); _log.warn("Unable to pick a charlie (no ikey), IPv6? " + isIPv6);
return; return;
} }
SessionKey charlieIntroKey = new SessionKey(ikey); SessionKey charlieIntroKey = new SessionKey(ikey);
@ -780,7 +796,7 @@ class PeerTestManager {
boolean isNew = false; boolean isNew = false;
if (state == null) { if (state == null) {
isNew = true; isNew = true;
state = new PeerTestState(BOB, nonce, now); state = new PeerTestState(BOB, isIPv6, nonce, now);
} else { } else {
if (state.getReceiveAliceTime() > now - (RESEND_TIMEOUT / 2)) { if (state.getReceiveAliceTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))

View File

@ -12,6 +12,7 @@ import net.i2p.data.SessionKey;
class PeerTestState { class PeerTestState {
private final long _testNonce; private final long _testNonce;
private final Role _ourRole; private final Role _ourRole;
private final boolean _isIPv6;
private InetAddress _aliceIP; private InetAddress _aliceIP;
private int _alicePort; private int _alicePort;
private InetAddress _bobIP; private InetAddress _bobIP;
@ -33,8 +34,9 @@ class PeerTestState {
public enum Role {ALICE, BOB, CHARLIE}; public enum Role {ALICE, BOB, CHARLIE};
public PeerTestState(Role role, long nonce, long now) { public PeerTestState(Role role, boolean isIPv6, long nonce, long now) {
_ourRole = role; _ourRole = role;
_isIPv6 = isIPv6;
_testNonce = nonce; _testNonce = nonce;
_beginTime = now; _beginTime = now;
} }
@ -44,6 +46,12 @@ class PeerTestState {
/** Are we Alice, bob, or Charlie. */ /** Are we Alice, bob, or Charlie. */
public Role getOurRole() { return _ourRole; } public Role getOurRole() { return _ourRole; }
/**
* Is this an IPv6 test?
* @since 0.9.27
*/
public boolean isIPv6() { return _isIPv6; }
/** /**
* If we are Alice, this will contain the IP that Bob says we * If we are Alice, this will contain the IP that Bob says we
* can be reached at - the IP Charlie says we can be reached * can be reached at - the IP Charlie says we can be reached

View File

@ -95,7 +95,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* Do we have a public IPv6 address? * Do we have a public IPv6 address?
* TODO periodically update via CSFI.NetMonitor? * TODO periodically update via CSFI.NetMonitor?
*/ */
private boolean _haveIPv6Address; private volatile boolean _haveIPv6Address;
private long _lastInboundIPv6; private long _lastInboundIPv6;
/** do we need to rebuild our external router address asap? */ /** do we need to rebuild our external router address asap? */
@ -219,6 +219,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/ */
private static final String MIN_SIGTYPE_VERSION = "0.9.17"; private static final String MIN_SIGTYPE_VERSION = "0.9.17";
/**
* IPv6 Peer Testing supported
*/
///////////////////////////////////// Testing only, set to 0.9.27 before release ////////////////////////////////////////
private static final String MIN_V6_PEER_TEST_VERSION = "0.9.26";
public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) { public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) {
super(ctx); super(ctx);
@ -535,7 +541,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
// change to firewalled maybe? but we don't have any test to restore // change to firewalled maybe? but we don't have any test to restore
// a v6 address after it's removed. // a v6 address after it's removed.
_lastInboundIPv6 = _context.clock().now(); _lastInboundIPv6 = _context.clock().now();
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK); if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else { } else {
if (!isIPv4Firewalled()) if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN); setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
@ -546,7 +553,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
for (InetAddress ia : bindToAddrs) { for (InetAddress ia : bindToAddrs) {
if (ia.getAddress().length == 16) { if (ia.getAddress().length == 16) {
_lastInboundIPv6 = _context.clock().now(); _lastInboundIPv6 = _context.clock().now();
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK); if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else { } else {
if (!isIPv4Firewalled()) if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN); setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
@ -763,12 +771,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
void inboundConnectionReceived(boolean isIPv6) { void inboundConnectionReceived(boolean isIPv6) {
if (isIPv6) { if (isIPv6) {
// FIXME we need to check and time out after an hour of no inbound ipv6,
// change to firewalled maybe? but we don't have any test to restore
// a v6 address after it's removed.
_lastInboundIPv6 = _context.clock().now(); _lastInboundIPv6 = _context.clock().now();
if (_currentOurV6Address != null) // former workaround for lack of IPv6 peer testing
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK); //if (_currentOurV6Address != null)
// setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else { } else {
// Introduced connections are still inbound, this is not evidence // Introduced connections are still inbound, this is not evidence
// that we are not firewalled. // that we are not firewalled.
@ -845,7 +851,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
} else if (ip.length == 16) { } else if (ip.length == 16) {
// TODO should we set both to unknown and wait for an inbound v6 conn, // TODO should we set both to unknown and wait for an inbound v6 conn,
// since there's no v6 testing? // since there's no v6 testing?
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK); if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} }
} }
} }
@ -1007,8 +1014,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
} }
if (fireTest) { if (fireTest) {
// always false, commented out above
_context.statManager().addRateData("udp.addressTestInsteadOfUpdate", 1); _context.statManager().addRateData("udp.addressTestInsteadOfUpdate", 1);
_testEvent.forceRunImmediately(); _testEvent.forceRunImmediately(isIPv6);
} else if (updated) { } else if (updated) {
_context.statManager().addRateData("udp.addressUpdated", 1); _context.statManager().addRateData("udp.addressUpdated", 1);
Map<String, String> changes = new HashMap<String, String>(); Map<String, String> changes = new HashMap<String, String>();
@ -1061,7 +1069,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
} }
// deadlock thru here ticket #1699 // deadlock thru here ticket #1699
_context.router().rebuildRouterInfo(); _context.router().rebuildRouterInfo();
_testEvent.forceRunImmediately(); _testEvent.forceRunImmediately(isIPv6);
} }
return updated; return updated;
} }
@ -1329,7 +1337,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
status != Status.IPV4_DISABLED_IPV6_FIREWALLED && status != Status.IPV4_DISABLED_IPV6_FIREWALLED &&
status != Status.DISCONNECTED && status != Status.DISCONNECTED &&
_reachabilityStatusUnchanged < 7) { _reachabilityStatusUnchanged < 7) {
_testEvent.forceRunSoon(); // IPv4 only for now
_testEvent.forceRunSoon(false);
} }
} }
return true; return true;
@ -3076,17 +3085,31 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
} }
} }
void setReachabilityStatus(Status status) { /**
* IPv4 only
*/
private void setReachabilityStatus(Status status) {
setReachabilityStatus(status, false);
}
/**
* @since 0.9.27
* @param isIPv6 Is the change an IPv6 change?
*/
void setReachabilityStatus(Status status, boolean isIPv6) {
synchronized (_rebuildLock) { synchronized (_rebuildLock) {
locked_setReachabilityStatus(status); locked_setReachabilityStatus(status, isIPv6);
} }
} }
private void locked_setReachabilityStatus(Status newStatus) { /**
* @param isIPv6 Is the change an IPv6 change?
*/
private void locked_setReachabilityStatus(Status newStatus, boolean isIPv6) {
Status old = _reachabilityStatus; Status old = _reachabilityStatus;
// merge new status into old // merge new status into old
Status status = Status.merge(old, newStatus); Status status = Status.merge(old, newStatus);
_testEvent.setLastTested(); _testEvent.setLastTested(isIPv6);
// now modify if we are IPv6 only // now modify if we are IPv6 only
TransportUtil.IPv6Config config = getIPv6Config(); TransportUtil.IPv6Config config = getIPv6Config();
if (config == IPV6_ONLY) { if (config == IPV6_ONLY) {
@ -3099,7 +3122,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
} }
if (status != Status.UNKNOWN) { if (status != Status.UNKNOWN) {
// now modify if we have no IPv6 address // now modify if we have no IPv6 address
if (_currentOurV6Address == null) { if (_currentOurV6Address == null && !_haveIPv6Address) {
if (status == Status.IPV4_OK_IPV6_UNKNOWN) if (status == Status.IPV4_OK_IPV6_UNKNOWN)
status = Status.OK; status = Status.OK;
else if (status == Status.IPV4_FIREWALLED_IPV6_UNKNOWN) else if (status == Status.IPV4_FIREWALLED_IPV6_UNKNOWN)
@ -3175,19 +3198,21 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
/** /**
* Pick a Bob (if we are Alice) or a Charlie (if we are Bob). * Pick a Bob (if we are Alice) or a Charlie (if we are Bob).
* *
* For Bob (as called from PeerTestEvent below), returns an established IPv4 peer. * For Bob (as called from PeerTestEvent below), returns an established IPv4/v6 peer.
* While the protocol allows Alice to select an unestablished Bob, we don't support that. * While the protocol allows Alice to select an unestablished Bob, we don't support that.
* *
* For Charlie (as called from PeerTestManager), returns an established IPv4 or IPv6 peer. * For Charlie (as called from PeerTestManager), returns an established IPv4 or IPv6 peer.
* (doesn't matter how Bob and Charlie communicate) * (doesn't matter how Bob and Charlie communicate)
* *
* Any returned peer must advertise an IPv4 address to prove it is IPv4-capable. * Any returned peer must advertise an IPv4 address to prove it is IPv4-capable.
* Ditto for v6.
* *
* @param peerRole BOB or CHARLIE only * @param peerRole The role of the peer we are looking for, BOB or CHARLIE only (NOT our role)
* @param isIPv6 true to get a v6-capable peer back
* @param dontInclude may be null * @param dontInclude may be null
* @return IPv4 peer or null * @return IPv4 peer or null
*/ */
PeerState pickTestPeer(PeerTestState.Role peerRole, RemoteHostId dontInclude) { PeerState pickTestPeer(PeerTestState.Role peerRole, boolean isIPv6, RemoteHostId dontInclude) {
if (peerRole == ALICE) if (peerRole == ALICE)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
List<PeerState> peers = new ArrayList<PeerState>(_peersByIdent.values()); List<PeerState> peers = new ArrayList<PeerState>(_peersByIdent.values());
@ -3195,21 +3220,32 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
PeerState peer = iter.next(); PeerState peer = iter.next();
if ( (dontInclude != null) && (dontInclude.equals(peer.getRemoteHostId())) ) if ( (dontInclude != null) && (dontInclude.equals(peer.getRemoteHostId())) )
continue; continue;
// enforce IPv4 connection if we are ALICE looking for a BOB // enforce IPv4/v6 connection if we are ALICE looking for a BOB
byte[] ip = peer.getRemoteIP(); byte[] ip = peer.getRemoteIP();
if (peerRole == BOB && ip.length != 4) if (peerRole == BOB) {
if ((!isIPv6 && ip.length != 4) ||
(isIPv6 && ip.length != 16))
continue; continue;
// enforce IPv4 advertised for all }
// enforce IPv4/v6 advertised for all
RouterInfo peerInfo = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer()); RouterInfo peerInfo = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer());
if (peerInfo == null) if (peerInfo == null)
continue; continue;
if (isIPv6) {
String v = peerInfo.getVersion();
if (VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0)
continue;
}
ip = null; ip = null;
List<RouterAddress> addrs = getTargetAddresses(peerInfo); List<RouterAddress> addrs = getTargetAddresses(peerInfo);
for (RouterAddress addr : addrs) { for (RouterAddress addr : addrs) {
ip = addr.getIP(); ip = addr.getIP();
if (ip != null && ip.length == 4) if (ip != null) {
if ((!isIPv6 && ip.length != 4) ||
(isIPv6 && ip.length != 16))
break; break;
} }
}
if (ip == null) if (ip == null)
continue; continue;
if (isTooClose(ip)) if (isTooClose(ip))
@ -3221,7 +3257,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private boolean shouldTest() { private boolean shouldTest() {
return ! (_context.router().isHidden() || return ! (_context.router().isHidden() ||
isIPv4Firewalled()); (isIPv4Firewalled() && isIPv6Firewalled()));
//String val = _context.getProperty(PROP_SHOULD_TEST); //String val = _context.getProperty(PROP_SHOULD_TEST);
//return ( (val != null) && ("true".equals(val)) ); //return ( (val != null) && ("true".equals(val)) );
} }
@ -3233,47 +3269,67 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private boolean _alive; private boolean _alive;
/** when did we last test our reachability */ /** when did we last test our reachability */
private final AtomicLong _lastTested = new AtomicLong(); private final AtomicLong _lastTested = new AtomicLong();
private boolean _forceRun; private final AtomicLong _lastTestedV6 = new AtomicLong();
private static final int NO_FORCE = 0, FORCE_IPV4 = 1, FORCE_IPV6 = 2;
private int _forceRun;
PeerTestEvent() { PeerTestEvent() {
super(_context.simpleTimer2()); super(_context.simpleTimer2());
} }
public synchronized void timeReached() { public synchronized void timeReached() {
// just for IPv6 for now
if (shouldTest()) { if (shouldTest()) {
long sinceRun = _context.clock().now() - _lastTested.get(); long now = _context.clock().now();
if ( (_forceRun && sinceRun >= MIN_TEST_FREQUENCY) || (sinceRun >= TEST_FREQUENCY) ) { long sinceRunV4 = now - _lastTested.get();
locked_runTest(); long sinceRunV6 = now - _lastTestedV6.get();
if (_forceRun == FORCE_IPV4 && sinceRunV4 >= MIN_TEST_FREQUENCY) {
locked_runTest(false);
} else if (_haveIPv6Address &&_forceRun == FORCE_IPV6 && sinceRunV6 >= MIN_TEST_FREQUENCY) {
locked_runTest(true);
} else if (sinceRunV4 >= TEST_FREQUENCY) {
locked_runTest(false);
} else if (_haveIPv6Address && sinceRunV6 >= TEST_FREQUENCY) {
locked_runTest(true);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("PTE timeReached(), no test run, last v4 test: " + new java.util.Date(_lastTested.get()) +
" last v6 test: " + new java.util.Date(_lastTestedV6.get()));
} }
} }
if (_alive) { if (_alive) {
long delay = (TEST_FREQUENCY / 2) + _context.random().nextInt(TEST_FREQUENCY); long delay = (TEST_FREQUENCY / 2) + _context.random().nextInt(TEST_FREQUENCY);
// if we have 2 addresses, give IPv6 a chance also
if (_haveIPv6Address)
delay /= 2;
schedule(delay); schedule(delay);
} }
} }
private void locked_runTest() { private void locked_runTest(boolean isIPv6) {
PeerState bob = pickTestPeer(BOB, null); PeerState bob = pickTestPeer(BOB, isIPv6, null);
if (bob != null) { if (bob != null) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Running periodic test with bob = " + bob); _log.info("Running periodic test with bob = " + bob);
_testManager.runTest(bob.getRemoteIPAddress(), bob.getRemotePort(), bob.getCurrentCipherKey(), bob.getCurrentMACKey()); _testManager.runTest(bob.getRemoteIPAddress(), bob.getRemotePort(), bob.getCurrentCipherKey(), bob.getCurrentMACKey());
setLastTested(); setLastTested(isIPv6);
} else { } else {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Unable to run a periodic test, as there are no peers with the capacity required"); _log.warn("Unable to run peer test, no peers available - v6? " + isIPv6);
} }
_forceRun = false; _forceRun = NO_FORCE;
} }
/** /**
* Run within the next 45 seconds at the latest * Run within the next 45 seconds at the latest
* @since 0.9.13 * @since 0.9.13
*/ */
public synchronized void forceRunSoon() { public synchronized void forceRunSoon(boolean isIPv6) {
if (isIPv4Firewalled()) if (!isIPv6 && isIPv4Firewalled())
return; return;
_forceRun = true; if (isIPv6 && isIPv6Firewalled())
return;
_forceRun = isIPv6 ? FORCE_IPV6 : FORCE_IPV4;
reschedule(MIN_TEST_FREQUENCY); reschedule(MIN_TEST_FREQUENCY);
} }
@ -3282,11 +3338,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* Run within the next 5 seconds at the latest * Run within the next 5 seconds at the latest
* @since 0.9.13 * @since 0.9.13
*/ */
public synchronized void forceRunImmediately() { public synchronized void forceRunImmediately(boolean isIPv6) {
if (isIPv4Firewalled()) if (!isIPv6 && isIPv4Firewalled())
return; return;
if (isIPv6 && isIPv6Firewalled())
return;
if (isIPv6)
_lastTestedV6.set(0);
else
_lastTested.set(0); _lastTested.set(0);
_forceRun = true; _forceRun = isIPv6 ? FORCE_IPV6 : FORCE_IPV4;
reschedule(5*1000); reschedule(5*1000);
} }
@ -3304,9 +3365,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* Set the last-tested timer to now * Set the last-tested timer to now
* @since 0.9.13 * @since 0.9.13
*/ */
public void setLastTested() { public void setLastTested(boolean isIPv6) {
// do not synchronize - deadlock with PeerTestManager // do not synchronize - deadlock with PeerTestManager
_lastTested.set(_context.clock().now()); long now = _context.clock().now();
if (isIPv6)
_lastTestedV6.set(now);
else
_lastTested.set(now);
if (_log.shouldLog(Log.DEBUG))
_log.debug("PTE.setLastTested() - v6? " + isIPv6, new Exception());
} }
} }