Transports: Track IPv4/v6 reachability separately (ticket #1458)

Don't include NTCP conns established too long ago in clock skew vector
Hide unestablished outbound NTCP conns from /peers view
Add per-transport status to /peers
Put status description instead of code into event log reachability changes
This commit is contained in:
zzz
2015-04-29 12:50:33 +00:00
parent 0f18686243
commit 629f7f05c7
6 changed files with 195 additions and 79 deletions

View File

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

View File

@@ -84,11 +84,14 @@ public class NTCPTransport extends TransportImpl {
* TODO periodically update via CSFI.NetMonitor?
*/
private boolean _haveIPv6Address;
private long _lastInboundIPv4;
private long _lastInboundIPv6;
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip";
private static final String PROP_ADVANCED = "routerconsole.advanced";
public static final int DEFAULT_COST = 10;
/** this is rarely if ever used, default is to bind to wildcard address */
@@ -210,6 +213,10 @@ public class NTCPTransport extends TransportImpl {
synchronized (_conLock) {
old = _conByIdent.put(peer, con);
}
if (con.isIPv6())
_lastInboundIPv6 = con.getCreated();
else
_lastInboundIPv4 = con.getCreated();
return old;
}
@@ -231,6 +238,7 @@ public class NTCPTransport extends TransportImpl {
con = new NTCPConnection(_context, this, ident, addr);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send on a new con: " + con + " at " + addr + " for " + ih);
// Note that outbound conns go in the map BEFORE establishment
_conByIdent.put(ih, con);
} else {
// race, RI changed out from under us
@@ -528,7 +536,11 @@ public class NTCPTransport extends TransportImpl {
return active;
}
/** @param skew in seconds */
/**
* A positive number means our clock is ahead of theirs.
*
* @param skew in seconds
*/
void setLastBadSkew(long skew) {
_lastBadSkew = skew;
}
@@ -536,13 +548,18 @@ public class NTCPTransport extends TransportImpl {
/**
* Return our peer clock skews on this transport.
* Vector composed of Long, each element representing a peer skew in seconds.
* A positive number means our clock is ahead of theirs.
*/
@Override
public Vector<Long> getClockSkews() {
Vector<Long> skews = new Vector<Long>();
// Omit ones established too long ago,
// since the skew is only set at startup (or after a meta message)
// and won't include effects of later offset adjustments
long tooOld = _context.clock().now() - 10*60*1000;
for (NTCPConnection con : _conByIdent.values()) {
if (con.isEstablished())
if (con.isEstablished() && con.getCreated() > tooOld)
skews.addElement(Long.valueOf(con.getClockSkew()));
}
@@ -622,11 +639,11 @@ public class NTCPTransport extends TransportImpl {
*
* Doesn't actually restart unless addr is non-null and
* the port is different from the current listen port.
* If addr is null, removes all addresses.
* If addr is null, removes IPv4 addresses only.
*
* If we had interface addresses before, we lost them.
*
* @param addr may be null
* @param addr may be null to indicate remove the IPv4 address only
*/
private synchronized void restartListening(RouterAddress addr) {
if (addr != null) {
@@ -637,7 +654,16 @@ public class NTCPTransport extends TransportImpl {
replaceAddress(addr);
// UDPTransport.rebuildExternalAddress() calls router.rebuildRouterInfo()
} else {
replaceAddress(null);
// can't do this, want to remove IPv4 only
//replaceAddress(null);
for (RouterAddress ra : _currentAddresses) {
byte[] ip = ra.getIP();
if (ip != null && ip.length == 4) {
// COWAL
_currentAddresses.remove(ra);
}
}
_lastInboundIPv4 = 0;
}
}
@@ -920,6 +946,7 @@ public class NTCPTransport extends TransportImpl {
/**
* UDP changed addresses, tell NTCP and (possibly) restart
*
* @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only
* @since IPv6 moved from CSFI.notifyReplaceAddress()
*/
@Override
@@ -958,20 +985,21 @@ public class NTCPTransport extends TransportImpl {
* UDP changed addresses, tell NTCP and restart.
* Port may be set to indicate requested port even if ip is null.
*
* @param ip previously validated
* @param ip previously validated; may be null to indicate IPv4 failure or port info only
* @since IPv6 moved from CSFI.notifyReplaceAddress()
*/
private synchronized void externalAddressReceived(byte[] ip, int port) {
// FIXME just take first IPv4 address for now
// FIXME just take first address for now
// FIXME if SSU set to hostname, NTCP will be set to IP
RouterAddress oldAddr = getCurrentAddress(false);
boolean isIPv6 = ip != null && ip.length == 16;
RouterAddress oldAddr = getCurrentAddress(isIPv6);
if (_log.shouldLog(Log.INFO))
_log.info("Changing NTCP Address? was " + oldAddr);
OrderedProperties newProps = new OrderedProperties();
int cost;
if (oldAddr == null) {
cost = getDefaultCost(ip != null && ip.length == 16);
cost = getDefaultCost(isIPv6);
} else {
cost = oldAddr.getCost();
newProps.putAll(oldAddr.getOptionsMap());
@@ -1163,16 +1191,81 @@ public class NTCPTransport extends TransportImpl {
* We have to be careful here because much of the router console code assumes
* that the reachability status is really just the UDP status.
*
* This only returns OK, DISABLED, or UNKNOWN for IPv4 and IPv6.
* We leave the FIREWALLED status for UDP.
*
* Previously returned short, now enum as of 0.9.20
*/
public Status getReachabilityStatus() {
// If we have an IPv4 address
if (isAlive() && getCurrentAddress(false) != null) {
for (NTCPConnection con : _conByIdent.values()) {
if (con.isInbound())
return Status.OK;
}
if (!isAlive())
return Status.UNKNOWN;
TransportUtil.IPv6Config config = getIPv6Config();
boolean v4Disabled, v6Disabled;
if (config == IPV6_DISABLED) {
v4Disabled = false;
v6Disabled = true;
} else if (config == IPV6_ONLY) {
v4Disabled = true;
v6Disabled = false;
} else {
v4Disabled = false;
v6Disabled = false;
}
boolean hasV4 = getCurrentAddress(false) != null;
// or use _haveIPv6Addrnss ??
boolean hasV6 = getCurrentAddress(true) != null;
if (!hasV4 && !hasV6)
return Status.UNKNOWN;
long now = _context.clock().now();
boolean v4OK = hasV4 && !v4Disabled && now - _lastInboundIPv4 < 10*60*1000;
boolean v6OK = hasV6 && !v6Disabled && now - _lastInboundIPv6 < 30*60*1000;
if (v4OK) {
if (v6OK)
return Status.OK;
if (v6Disabled)
return Status.OK;
if (!hasV6)
return Status.IPV4_OK_IPV6_UNKNOWN;
}
if (v6OK) {
if (v4Disabled)
return Status.IPV4_DISABLED_IPV6_OK;
if (!hasV4)
return Status.IPV4_UNKNOWN_IPV6_OK;
}
for (NTCPConnection con : _conByIdent.values()) {
if (con.isInbound()) {
if (con.isIPv6()) {
if (hasV6)
v6OK = true;
} else {
if (hasV4)
v4OK = true;
}
if (v4OK) {
if (v6OK)
return Status.OK;
if (v6Disabled)
return Status.OK;
if (!hasV6)
return Status.IPV4_OK_IPV6_UNKNOWN;
}
if (v6OK) {
if (v4Disabled)
return Status.IPV4_DISABLED_IPV6_OK;
if (!hasV4)
return Status.IPV4_UNKNOWN_IPV6_OK;
}
}
}
if (v4OK)
return Status.IPV4_OK_IPV6_UNKNOWN;
if (v6OK)
return Status.IPV4_UNKNOWN_IPV6_OK;
if (v4Disabled)
return Status.IPV4_DISABLED_IPV6_UNKNOWN;
if (v6Disabled)
return Status.UNKNOWN;
return Status.UNKNOWN;
}
@@ -1197,6 +1290,8 @@ public class NTCPTransport extends TransportImpl {
NTCPConnection.releaseResources();
replaceAddress(null);
_endpoints.clear();
_lastInboundIPv4 = 0;
_lastInboundIPv6 = 0;
}
public static final String STYLE = "NTCP";
@@ -1215,10 +1310,19 @@ public class NTCPTransport extends TransportImpl {
long totalSend = 0;
long totalRecv = 0;
if (!_context.getBooleanProperty(PROP_ADVANCED)) {
for (Iterator<NTCPConnection> iter = peers.iterator(); iter.hasNext(); ) {
// outbound conns get put in the map before they are established
if (!iter.next().isEstablished())
iter.remove();
}
}
StringBuilder buf = new StringBuilder(512);
buf.append("<h3 id=\"ntcpcon\">").append(_("NTCP connections")).append(": ").append(peers.size());
buf.append(". ").append(_("Limit")).append(": ").append(getMaxConnections());
buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration2(_pumper.getIdleTimeout()));
buf.append(". ").append(_("Status")).append(": ").append(_(getReachabilityStatus().toStatusString()));
buf.append(".</h3>\n" +
"<table>\n" +
"<tr><th><a href=\"#def.peer\">").append(_("Peer")).append("</a></th>" +

View File

@@ -705,7 +705,8 @@ class EstablishmentManager {
_transport.addRemotePeerState(peer);
_transport.inboundConnectionReceived();
boolean isIPv6 = state.getSentIP().length == 16;
_transport.inboundConnectionReceived(isIPv6);
_transport.setIP(remote.calculateHash(), state.getSentIP());
_context.statManager().addRateData("udp.inboundEstablishTime", state.getLifetime(), 0);

View File

@@ -448,16 +448,16 @@ class PeerTestManager {
if ( (test.getAlicePort() == test.getAlicePortFromCharlie()) &&
(test.getAliceIP() != null) && (test.getAliceIPFromCharlie() != null) &&
(test.getAliceIP().equals(test.getAliceIPFromCharlie())) ) {
status = Status.OK;
status = Status.IPV4_OK_IPV6_UNKNOWN;
} else {
status = Status.DIFFERENT;
status = Status.IPV4_SNAT_IPV6_UNKNOWN;
}
} else if (test.getReceiveCharlieTime() > 0) {
// we received only one message from charlie
status = Status.UNKNOWN;
} else if (test.getReceiveBobTime() > 0) {
// we received a message from bob but no messages from charlie
status = Status.REJECT_UNSOLICITED;
status = Status.IPV4_FIREWALLED_IPV6_UNKNOWN;
} else {
// we never received anything from bob - he is either down,
// ignoring us, or unable to get a Charlie to respond

View File

@@ -258,10 +258,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_context.statManager().createRateStat("udp.alreadyConnected", "What is the lifetime of a reestablished session", "udp", RATES);
_context.statManager().createRateStat("udp.droppedPeer", "How long ago did we receive from a dropped peer (duration == session lifetime", "udp", RATES);
_context.statManager().createRateStat("udp.droppedPeerInactive", "How long ago did we receive from a dropped peer (duration == session lifetime)", "udp", RATES);
_context.statManager().createRateStat("udp.statusOK", "How many times the peer test returned OK", "udp", RATES);
_context.statManager().createRateStat("udp.statusDifferent", "How many times the peer test returned different IP/ports", "udp", RATES);
_context.statManager().createRateStat("udp.statusReject", "How many times the peer test returned reject unsolicited", "udp", RATES);
_context.statManager().createRateStat("udp.statusUnknown", "How many times the peer test returned an unknown result", "udp", RATES);
//_context.statManager().createRateStat("udp.statusOK", "How many times the peer test returned OK", "udp", RATES);
//_context.statManager().createRateStat("udp.statusDifferent", "How many times the peer test returned different IP/ports", "udp", RATES);
//_context.statManager().createRateStat("udp.statusReject", "How many times the peer test returned reject unsolicited", "udp", RATES);
//_context.statManager().createRateStat("udp.statusUnknown", "How many times the peer test returned an unknown result", "udp", RATES);
_context.statManager().createRateStat("udp.addressTestInsteadOfUpdate", "How many times we fire off a peer test of ourselves instead of adjusting our own reachable address?", "udp", RATES);
_context.statManager().createRateStat("udp.addressUpdated", "How many times we adjust our own reachable IP address", "udp", RATES);
_context.statManager().createRateStat("udp.proactiveReestablish", "How long a session was idle for when we proactively reestablished it", "udp", RATES);
@@ -662,9 +662,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
private static final int ALLOW_IP_CHANGE_INTERVAL = 2*60*1000;
void inboundConnectionReceived() {
// use OS clock since its an ordering thing, not a time thing
_lastInboundReceivedOn = System.currentTimeMillis();
void inboundConnectionReceived(boolean isIPv6) {
if (isIPv6) {
if (_currentOurV6Address != null)
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
} else {
// use OS clock since its an ordering thing, not a time thing
_lastInboundReceivedOn = System.currentTimeMillis();
}
}
// temp prevent multiples
@@ -725,8 +730,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
boolean changed = changeAddress(ip, port);
// Assume if we have an interface with a public IP that we aren't firewalled.
// If this is wrong, the peer test will figure it out and change the status.
if (changed && ip.length == 4 && source == SOURCE_INTERFACE)
setReachabilityStatus(Status.OK);
if (changed && source == SOURCE_INTERFACE) {
if (ip.length == 4)
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
else if (ip.length == 16)
// TODO should we set both to unknown and wait for an inbound v6 conn,
// since there's no v6 testing?
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
}
}
/**
@@ -744,7 +755,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_log.warn("UPnP has failed to open the SSU port: " + port + " reason: " + reason);
}
if (success && ip != null && getExternalIP() != null)
setReachabilityStatus(Status.OK);
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
}
/**
@@ -2092,6 +2103,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
switch (status) {
case REJECT_UNSOLICITED:
case DIFFERENT:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Require introducers, because our status is " + status);
return true;
@@ -2289,6 +2302,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
/**
* Return our peer clock skews on this transport.
* Vector composed of Long, each element representing a peer skew in seconds.
* A positive number means our clock is ahead of theirs.
*/
@Override
public Vector<Long> getClockSkews() {
@@ -2348,6 +2362,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
buf.append("<h3 id=\"udpcon\">").append(_("UDP connections")).append(": ").append(peers.size());
buf.append(". ").append(_("Limit")).append(": ").append(getMaxConnections());
buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration2(_expireTimeout));
buf.append(". ").append(_("Status")).append(": ").append(_(_reachabilityStatus.toStatusString()));
buf.append(".</h3>\n");
buf.append("<table>\n");
buf.append("<tr><th class=\"smallhead\" nowrap><a href=\"#def.peer\">").append(_("Peer")).append("</a><br>");
@@ -2779,58 +2794,49 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
}
private void locked_setReachabilityStatus(Status status) {
private void locked_setReachabilityStatus(Status newStatus) {
Status old = _reachabilityStatus;
long now = _context.clock().now();
switch (status) {
case OK:
// TODO if OK but internal port != external port, should we have
// a different status state? ...as we don't know if the TCP
// port will be mapped the same way or not...
// Right now, we assume it is and hope for the best for TCP.
_context.statManager().addRateData("udp.statusOK", 1);
_reachabilityStatus = status;
_reachabilityStatusLastUpdated = now;
break;
case DIFFERENT:
_context.statManager().addRateData("udp.statusDifferent", 1);
_reachabilityStatus = status;
_reachabilityStatusLastUpdated = now;
break;
case REJECT_UNSOLICITED:
_context.statManager().addRateData("udp.statusReject", 1);
// if old != unsolicited && now - lastUpdated > STATUS_GRACE_PERIOD)
//
// fall through...
case DISCONNECTED:
case HOSED:
_reachabilityStatus = status;
_reachabilityStatusLastUpdated = now;
break;
case UNKNOWN:
default:
_context.statManager().addRateData("udp.statusUnknown", 1);
//if (now - _reachabilityStatusLastUpdated < STATUS_GRACE_PERIOD) {
// _testEvent.forceRun();
// SimpleTimer.getInstance().addEvent(_testEvent, 5*1000);
//} else {
// _reachabilityStatus = status;
// _reachabilityStatusLastUpdated = now;
//}
break;
}
// merge new status into old
Status status = Status.merge(old, newStatus);
_testEvent.setLastTested();
if (status != Status.UNKNOWN) {
if (status != old)
_reachabilityStatusUnchanged = 0;
else
_reachabilityStatusUnchanged++;
// now modify if we are IPv6 only
TransportUtil.IPv6Config config = getIPv6Config();
if (config == IPV6_ONLY) {
if (status == Status.IPV4_UNKNOWN_IPV6_OK)
status = Status.IPV4_DISABLED_IPV6_OK;
else if (status == Status.IPV4_UNKNOWN_IPV6_FIREWALLED)
status = Status.IPV4_DISABLED_IPV6_FIREWALLED;
else if (status == Status.UNKNOWN)
status = Status.IPV4_DISABLED_IPV6_UNKNOWN;
}
if ( (status != old) && (status != Status.UNKNOWN) ) {
if (status != Status.UNKNOWN) {
// now modify if we have no IPv6 address
if (_currentOurV6Address == null) {
if (status == Status.IPV4_OK_IPV6_UNKNOWN)
status = Status.OK;
else if (status == Status.IPV4_FIREWALLED_IPV6_UNKNOWN)
status = Status.REJECT_UNSOLICITED;
else if (status == Status.IPV4_SNAT_IPV6_UNKNOWN)
status = Status.DIFFERENT;
}
if (status != old) {
_reachabilityStatusUnchanged = 0;
long now = _context.clock().now();
_reachabilityStatusLastUpdated = now;
_reachabilityStatus = status;
} else {
_reachabilityStatusUnchanged++;
}
}
if (status != old) {
if (_log.shouldLog(Log.WARN))
_log.warn("Old status: " + old + " New status: " + status + " from: ", new Exception("traceback"));
_log.warn("Old status: " + old + " New status: " + status +
" Caused by update: " + newStatus +
" from: ", new Exception("traceback"));
if (old != Status.UNKNOWN)
_context.router().eventLog().addEvent(EventLog.REACHABILITY, status.toStatusString());
_context.router().eventLog().addEvent(EventLog.REACHABILITY,
"from " + _(old.toStatusString()) + " to " + _(status.toStatusString()));
// Always rebuild when the status changes, even if our address hasn't changed,
// as rebuildExternalAddress() calls replaceAddress() which calls CSFI.notifyReplaceAddress()
// which will start up NTCP inbound when we transition to OK.
@@ -2838,7 +2844,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
rebuildExternalAddress();
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Status unchanged: " + _reachabilityStatus + " (" + _reachabilityStatusUnchanged + " consecutive times), last updated " +
_log.info("Status unchanged: " + _reachabilityStatus +
" after update: " + newStatus +
" (unchanged " + _reachabilityStatusUnchanged + " consecutive times), last updated " +
DataHelper.formatDuration(_context.clock().now() - _reachabilityStatusLastUpdated) + " ago");
}
}