Tunnels: Immediately fail outbound tunnels when

we can't connect to the first hop
by attaching an onSendFailJob in OutboundSender.
Check if failed in isValidTunnel()
This commit is contained in:
zzz
2021-12-03 12:51:01 -05:00
parent 2a900a8c5b
commit d03c690724
8 changed files with 91 additions and 15 deletions

View File

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

View File

@@ -6,6 +6,7 @@ import net.i2p.data.i2np.TunnelDataMessage;
import net.i2p.router.JobImpl;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.tunnel.pool.PooledTunnelCreatorConfig;
import net.i2p.util.Log;
/**
@@ -18,19 +19,22 @@ import net.i2p.util.Log;
class OutboundReceiver implements TunnelGateway.Receiver {
private final RouterContext _context;
private final Log _log;
private final TunnelCreatorConfig _config;
private final PooledTunnelCreatorConfig _config;
private RouterInfo _nextHopCache;
private final int _priority;
// same job used for all messages
private final JobImpl _sendFailJob;
private static final long MAX_LOOKUP_TIME = 15*1000;
private static final int PRIORITY = OutNetMessage.PRIORITY_MY_DATA;
public OutboundReceiver(RouterContext ctx, TunnelCreatorConfig cfg) {
public OutboundReceiver(RouterContext ctx, PooledTunnelCreatorConfig cfg) {
_context = ctx;
_log = ctx.logManager().getLog(OutboundReceiver.class);
_config = cfg;
_nextHopCache = _context.netDb().lookupRouterInfoLocally(_config.getPeer(1));
_priority = PRIORITY + cfg.getPriority();
_sendFailJob = new SendFailedJob(ctx);
// all createRateStat() in TunnelDispatcher
}
@@ -42,10 +46,11 @@ class OutboundReceiver implements TunnelGateway.Receiver {
if (_log.shouldLog(Log.DEBUG))
_log.debug("received encrypted, sending out " + _config + ": " + msg);
RouterInfo ri = _nextHopCache;
if (ri == null)
if (ri == null) {
ri = _context.netDb().lookupRouterInfoLocally(_config.getPeer(1));
if (ri != null) {
_nextHopCache = ri;
}
if (ri != null) {
send(msg, ri);
return msg.getUniqueId();
} else {
@@ -54,7 +59,7 @@ class OutboundReceiver implements TunnelGateway.Receiver {
_log.warn("lookup of " + _config.getPeer(1)
+ " required for " + msg);
_context.netDb().lookupRouterInfo(_config.getPeer(1), new SendJob(_context, msg),
new FailedJob(_context), MAX_LOOKUP_TIME);
new LookupFailedJob(_context), MAX_LOOKUP_TIME);
return -1;
}
}
@@ -72,6 +77,8 @@ class OutboundReceiver implements TunnelGateway.Receiver {
if (_log.shouldLog(Log.DEBUG))
_log.debug("forwarding encrypted data out " + _config + ": " + msg.getUniqueId());
OutNetMessage m = new OutNetMessage(_context, msg, msg.getMessageExpiration(), _priority, ri);
// set a job to fail the tunnel if we can't send the message
m.setOnFailedSendJob(_sendFailJob);
_context.outNetMessagePool().add(m);
_config.incrementProcessedMessages();
}
@@ -102,9 +109,13 @@ class OutboundReceiver implements TunnelGateway.Receiver {
_context.statManager().addRateData("tunnel.outboundLookupSuccess", stat);
}
}
private class FailedJob extends JobImpl {
public FailedJob(RouterContext ctx) {
/**
* Immediately fail the tunnel if the lookup fails.
* This should be very rare, we should always have the RI locally.
*/
private class LookupFailedJob extends JobImpl {
public LookupFailedJob(RouterContext ctx) {
super(ctx);
}
@@ -115,6 +126,27 @@ class OutboundReceiver implements TunnelGateway.Receiver {
_log.warn("lookup of " + _config.getPeer(1)
+ " failed for " + _config);
_context.statManager().addRateData("tunnel.outboundLookupSuccess", 0);
_config.tunnelFailedFirstHop();
}
}
/**
* Immediately fail the tunnel if the send fails
*
* @since 0.9.53
*/
private class SendFailedJob extends JobImpl {
public SendFailedJob(RouterContext ctx) {
super(ctx);
}
public String getName() { return "OBGW send fail"; }
public void runJob() {
if (_log.shouldWarn())
_log.warn("send to " + _config.getPeer(1)
+ " failed for " + _config);
_config.tunnelFailedFirstHop();
}
}
}

View File

@@ -210,7 +210,7 @@ public abstract class TunnelCreatorConfig implements TunnelInfo {
// _peakThroughput[i] = kBps*60;
}
****/
/**
* The tunnel failed a test, so (maybe) stop using it
*
@@ -220,7 +220,22 @@ public abstract class TunnelCreatorConfig implements TunnelInfo {
return _failures.incrementAndGet() <= MAX_CONSECUTIVE_TEST_FAILURES;
}
/**
* The tunnel failed completely, so definitely stop using it
*
* @since 0.9.53
*/
public void tunnelFailedCompletely() {
_failures.addAndGet(MAX_CONSECUTIVE_TEST_FAILURES + 1);
}
/**
* Has the tunnel failed completely?
*
* @since 0.9.53
*/
public boolean getTunnelFailed() { return _failures.get() > MAX_CONSECUTIVE_TEST_FAILURES; }
public int getTunnelFailures() { return _failures.get(); }
public void testSuccessful(int ms) {

View File

@@ -24,6 +24,7 @@ import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.Service;
import net.i2p.router.peermanager.PeerProfile;
import net.i2p.router.tunnel.pool.PooledTunnelCreatorConfig;
import net.i2p.util.Log;
/**
@@ -247,7 +248,7 @@ public class TunnelDispatcher implements Service {
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinOutbound(TunnelCreatorConfig cfg) {
public boolean joinOutbound(PooledTunnelCreatorConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Outbound built successfully: " + cfg);
TunnelGateway gw;

View File

@@ -9,7 +9,7 @@ import net.i2p.router.tunnel.TunnelCreatorConfig;
/**
* Data about a tunnel we created
*/
class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
public class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
private final TunnelPool _pool;
/**
@@ -47,6 +47,20 @@ class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
return rv;
}
/**
* We failed to contact the first hop for an outbound tunnel,
* so immediately stop using it.
* For outbound non-zero-hop tunnels only.
*
* @since 0.9.53
*/
public void tunnelFailedFirstHop() {
if (isInbound() || getLength() <= 1)
return;
tunnelFailedCompletely();
_pool.tunnelFailed(this, getPeer(1));
}
/**
* @return non-null
*/

View File

@@ -536,8 +536,6 @@ public class TunnelPool {
* Remove the tunnel.
*/
private void fail(TunnelInfo cfg) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": Tunnel failed: " + cfg);
LeaseSet ls = null;
synchronized (_tunnels) {
boolean removed = _tunnels.remove(cfg);
@@ -546,7 +544,9 @@ public class TunnelPool {
if (_settings.isInbound() && !_settings.isExploratory())
ls = locked_buildNewLeaseSet();
}
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": Tunnel failed: " + cfg);
_manager.tunnelFailed();
_lifetimeProcessed += cfg.getProcessedMessagesCount();

View File

@@ -335,6 +335,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
}
public boolean isValidTunnel(Hash client, TunnelInfo tunnel) {
if (tunnel.getTunnelFailed())
return false;
if (tunnel.getExpiration() < _context.clock().now())
return false;
TunnelPool pool;