![JAR search and dependency download from the Maven repository](/logo.png)
net.i2p.router.tunnel.TunnelDispatcher Maven / Gradle / Ivy
package net.i2p.router.tunnel;
import java.io.IOException;
import java.io.Writer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelDataMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
import net.i2p.data.i2np.VariableTunnelBuildMessage;
import net.i2p.data.i2np.VariableTunnelBuildReplyMessage;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterThrottleImpl;
import net.i2p.router.Service;
import net.i2p.router.peermanager.PeerProfile;
import net.i2p.router.tunnel.pool.PooledTunnelCreatorConfig;
import net.i2p.util.Log;
/**
* Handle the actual processing and forwarding of messages through the
* various tunnels.
*
*
* For each type of tunnel, it creates a chain of handlers, as follows:
*
* Following tunnels are created by us:
*
* Outbound Gateway > 0 hops:
* PumpedTunnelGateway
* BatchedRouterPreprocessor -> OutboundSender -> OutboundReceiver -> OutNetMessagePool
*
* Outbound zero-hop Gateway+Endpoint:
* TunnelGatewayZeroHop
* OutboundMessageDistributor -> OutNetMessagePool
*
* Inbound Endpoint > 0 hops:
* TunnelParticipant
* RouterFragmentHandler -> InboundEndpointProcessor -> InboundMessageDistributor -> InNetMessagePool
*
* Inbound zero-hop Gateway+Endpoint:
* TunnelGatewayZeroHop
* InboundMessageDistributor -> InNetMessagePool
*
*
* Following tunnels are NOT created by us:
*
* Participant (not gateway or endpoint)
* TunnelParticipant
* HopProcessor -> OutNetMessagePool
*
* Outbound Endpoint > 0 hops:
* OutboundTunnelEndpoint
* RouterFragmentHandler -> HopProcessor -> OutboundMessageDistributor -> OutNetMessagePool
*
* Inbound Gateway > 0 hops:
* ThrottledPumpedTunnelGateway
* BatchedRouterPreprocessor -> InboundSender -> InboundGatewayReceiver -> OutNetMessagePool
*
*
*/
public class TunnelDispatcher implements Service {
private final RouterContext _context;
private final Log _log;
/** us */
private final ConcurrentHashMap _outboundGateways;
private final ConcurrentHashMap _outboundEndpoints;
/** regular participant or IBEP of our own tunnel */
private final ConcurrentHashMap _participants;
/** regular IBGW or our own zero-hop inbound */
private final ConcurrentHashMap _inboundGateways;
/** anything we did not create - IBGW, OBEP, or middle hop */
private final ConcurrentHashMap _participatingConfig;
/** what is the date/time on which the last non-locally-created tunnel expires? */
private long _lastParticipatingExpiration;
private BloomFilterIVValidator _validator;
private final LeaveTunnel _leaveJob;
/** what is the date/time we last deliberately dropped a tunnel? **/
//private long _lastDropTime;
private final TunnelGatewayPumper _pumper;
private final Object _joinParticipantLock = new Object();
/** for shouldDropParticipatingMessage() */
enum Location {OBEP, PARTICIPANT, IBGW}
private static final long[] RATES = { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 };
/** Creates a new instance of TunnelDispatcher */
public TunnelDispatcher(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(TunnelDispatcher.class);
_outboundGateways = new ConcurrentHashMap();
_outboundEndpoints = new ConcurrentHashMap();
_participants = new ConcurrentHashMap();
_inboundGateways = new ConcurrentHashMap();
_participatingConfig = new ConcurrentHashMap();
_pumper = new TunnelGatewayPumper(ctx);
_leaveJob = new LeaveTunnel(ctx);
ctx.statManager().createRequiredRateStat("tunnel.participatingTunnels",
"Tunnels routed for others", "Tunnels",
new long[] { 60*1000, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("tunnel.dispatchOutboundPeer",
"How many messages we send out a tunnel targetting a peer?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.dispatchOutboundTunnel",
"How many messages we send out a tunnel targetting a tunnel?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.dispatchInbound",
"How many messages we send through our tunnel gateway?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.dispatchParticipant",
"How many messages we send through a tunnel we are participating in?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.dispatchEndpoint",
"How many messages we receive as the outbound endpoint of a tunnel?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinOutboundGateway",
"How many tunnels we join as the outbound gateway?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinOutboundGatewayZeroHop",
"How many zero hop tunnels we join as the outbound gateway?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinInboundEndpoint",
"How many tunnels we join as the inbound endpoint?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinInboundEndpointZeroHop",
"How many zero hop tunnels we join as the inbound endpoint?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinParticipant",
"How many tunnels we join as a participant?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinOutboundEndpoint",
"How many tunnels we join as the outbound endpoint?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.joinInboundGateway",
"How many tunnels we join as the inbound gateway?", "Tunnels",
new long[] { 10*60*1000l, 60*60*1000l });
//ctx.statManager().createRateStat("tunnel.dispatchGatewayTime",
// "How long it takes to dispatch a TunnelGatewayMessage", "Tunnels",
// new long[] { 60*1000l, 60*60*1000l });
//ctx.statManager().createRateStat("tunnel.dispatchDataTime",
// "How long it takes to dispatch a TunnelDataMessage", "Tunnels",
// new long[] { 60*1000l, 60*60*1000l });
//ctx.statManager().createRateStat("tunnel.dispatchOutboundTime",
// "How long it takes to dispatch an outbound message", "Tunnels",
// new long[] { 60*1000l, 60*60*1000l });
//ctx.statManager().createRateStat("tunnel.dispatchOutboundZeroHopTime",
// "How long it takes to dispatch an outbound message through a zero hop tunnel", "Tunnels",
// new long[] { 60*60*1000l });
ctx.statManager().createRequiredRateStat("tunnel.participatingBandwidth",
"Participating traffic received (Bytes/sec)", "Tunnels",
new long[] { 60*1000l, 60*10*1000l });
ctx.statManager().createRequiredRateStat("tunnel.participatingBandwidthOut",
"Participating traffic sent (Bytes/sec)", "Tunnels",
new long[] { 60*1000l, 60*10*1000l });
ctx.statManager().createRateStat("tunnel.participatingMessageDropped",
"Dropped for exceeding share limit", "Tunnels",
new long[] { 60*1000l, 60*10*1000l });
// count for console
ctx.statManager().createRequiredRateStat("tunnel.participatingMessageCount",
"Number of 1KB participating messages", "Tunnels",
new long[] { 60*1000l, 60*10*1000l, 60*60*1000l });
// estimate for RouterThrottleImpl
ctx.statManager().createRequiredRateStat("tunnel.participatingMessageCountAvgPerTunnel",
"Estimate of participating messages per tunnel lifetime", "Tunnels",
new long[] { 60*1000, 20*60*1000 });
ctx.statManager().createRateStat("tunnel.ownedMessageCount",
"How many messages are sent through a tunnel we created (period == failures)?", "Tunnels",
new long[] { 60*1000l, 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.failedCompletelyMessages",
"How many messages are sent through a tunnel that failed prematurely (period == failures)?", "Tunnels",
new long[] { 60*1000l, 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.failedPartiallyMessages",
"How many messages are sent through a tunnel that only failed partially (period == failures)?", "Tunnels",
new long[] { 60*1000l, 10*60*1000l, 60*60*1000l });
// following are for BatchedPreprocessor
ctx.statManager().createRateStat("tunnel.batchMultipleCount", "How many messages are batched into a tunnel message", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchDelay", "How many messages were pending when the batching waited", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchDelaySent", "How many messages were flushed when the batching delay completed", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchCount", "How many groups of messages were flushed together", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchDelayAmount", "How long we should wait before flushing the batch", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchFlushRemaining", "How many messages remain after flushing", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.writeDelay", "How long after a message reaches the gateway is it processed (lifetime is size)", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchSmallFragments", "How many outgoing pad bytes are in small fragments?",
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.batchFullFragments", "How many outgoing tunnel messages use the full data area?",
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l });
ctx.statManager().createRateStat("tunnel.batchFragmentation", "Avg. number of fragments per msg", "Tunnels", new long[] { 10*60*1000, 60*60*1000 });
// following is for OutboundMessageDistributor
ctx.statManager().createRateStat("tunnel.distributeLookupSuccess", "Was a deferred lookup successful?", "Tunnels", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("tunnel.dropAtOBEP", "New conn throttle", "Tunnels", new long[] { 60*60*1000 });
// following is for OutboundReceiver
ctx.statManager().createRateStat("tunnel.outboundLookupSuccess", "Was a deferred lookup successful?", "Tunnels", new long[] { 60*60*1000 });
// following is for InboundGatewayReceiver
ctx.statManager().createRateStat("tunnel.inboundLookupSuccess", "Was a deferred lookup successful?", "Tunnels", new long[] { 60*60*1000 });
// following is for TunnelParticipant
ctx.statManager().createRateStat("tunnel.participantLookupSuccess", "Was a deferred lookup successful?", "Tunnels", new long[] { 60*60*1000 });
// following is for BuildMessageProcessor
ctx.statManager().createRateStat("tunnel.buildRequestDup", "How frequently we get dup build request messages", "Tunnels", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("tunnel.buildRequestBadReplyKey", "Build requests with bad reply keys", "Tunnels", new long[] { 60*60*1000 });
// following are for FragmentHandler
ctx.statManager().createRateStat("tunnel.smallFragments", "How many pad bytes are in small fragments?",
"Tunnels", RATES);
ctx.statManager().createRateStat("tunnel.fullFragments", "How many tunnel messages use the full data area?",
"Tunnels", RATES);
ctx.statManager().createRateStat("tunnel.fragmentedComplete", "How many fragments were in a completely received message?",
"Tunnels", RATES);
ctx.statManager().createRequiredRateStat("tunnel.fragmentedDropped", "Number of dropped fragments",
"Tunnels", RATES);
ctx.statManager().createRequiredRateStat("tunnel.corruptMessage", "Corrupt messages received",
"Tunnels", RATES);
// following are for InboundMessageDistributor
ctx.statManager().createRateStat("tunnel.dropDangerousClientTunnelMessage", "(lifetime is the I2NP type)", "Tunnels", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("tunnel.dropDangerousExplTunnelMessage", "(lifetime is the I2NP type)", "Tunnels", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("tunnel.handleLoadClove", "When do we receive load test cloves", "Tunnels", new long[] { 60*60*1000 });
// following is for PumpedTunnelGateway
ctx.statManager().createRateStat("tunnel.dropGatewayOverflow", "Dropped message at GW, queue full", "Tunnels", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("tunnel.outboundTunnelEndpointFwdRIDSM", "OBTE Forwarding RI DSM",
"Tunnels", new long[] { 10*60*1000, 60*60*1000 });
}
/** for IBGW */
private TunnelGateway.QueuePreprocessor createPreprocessor(HopConfig cfg) {
//if (true)
return new BatchedRouterPreprocessor(_context, cfg);
//else
// return new TrivialRouterPreprocessor(_context);
}
/** for OBGW */
private TunnelGateway.QueuePreprocessor createPreprocessor(TunnelCreatorConfig cfg) {
//if (true)
return new BatchedRouterPreprocessor(_context, cfg);
//else
// return new TrivialRouterPreprocessor(_context);
}
/**
* We are the outbound gateway - we created this tunnel
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinOutbound(PooledTunnelCreatorConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Outbound built successfully: " + cfg);
TunnelGateway gw;
if (cfg.getLength() > 1) {
TunnelGateway.QueuePreprocessor preproc = createPreprocessor(cfg);
TunnelGateway.Sender sender = new OutboundSender(_context, cfg);
TunnelGateway.Receiver receiver = new OutboundReceiver(_context, cfg);
//TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver);
gw = new PumpedTunnelGateway(_context, preproc, sender, receiver, _pumper);
} else {
gw = new TunnelGatewayZeroHop(_context, cfg);
}
TunnelId outId = cfg.getConfig(0).getSendTunnel();
if (_outboundGateways.putIfAbsent(outId, gw) != null)
return false;
if (cfg.getLength() > 1) {
_context.statManager().addRateData("tunnel.joinOutboundGateway", 1);
_context.messageHistory().tunnelJoined("outbound", cfg);
} else {
_context.statManager().addRateData("tunnel.joinOutboundGatewayZeroHop", 1);
_context.messageHistory().tunnelJoined("outboundZeroHop", cfg);
}
return true;
}
/**
* We are the inbound endpoint - we created this tunnel
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinInbound(TunnelCreatorConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Inbound built successfully: " + cfg);
if (cfg.getLength() > 1) {
TunnelParticipant participant = new TunnelParticipant(_context, new InboundEndpointProcessor(_context, cfg, _validator));
TunnelId recvId = cfg.getConfig(cfg.getLength()-1).getReceiveTunnel();
if (_participants.putIfAbsent(recvId, participant) != null)
return false;
_context.statManager().addRateData("tunnel.joinInboundEndpoint", 1);
_context.messageHistory().tunnelJoined("inboundEndpoint", cfg);
} else {
TunnelGatewayZeroHop gw = new TunnelGatewayZeroHop(_context, cfg);
TunnelId recvId = cfg.getConfig(0).getReceiveTunnel();
if (_inboundGateways.putIfAbsent(recvId, gw) != null)
return false;
_context.statManager().addRateData("tunnel.joinInboundEndpointZeroHop", 1);
_context.messageHistory().tunnelJoined("inboundEndpointZeroHop", cfg);
}
return true;
}
/**
* We are a participant in this tunnel, but not as the endpoint or gateway
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinParticipant(HopConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Joining as participant: " + cfg);
TunnelId recvId = cfg.getReceiveTunnel();
TunnelParticipant participant = new TunnelParticipant(_context, cfg, new HopProcessor(_context, cfg, _validator));
synchronized (_joinParticipantLock) {
if (_participatingConfig.putIfAbsent(recvId, cfg) != null)
return false;
if (_participants.putIfAbsent(recvId, participant) != null) {
_participatingConfig.remove(recvId);
return false;
}
}
_context.messageHistory().tunnelJoined("participant", cfg);
_context.statManager().addRateData("tunnel.joinParticipant", 1);
if (cfg.getExpiration() > _lastParticipatingExpiration)
_lastParticipatingExpiration = cfg.getExpiration();
_leaveJob.add(cfg);
return true;
}
/**
* We are the outbound endpoint in this tunnel, and did not create it
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinOutboundEndpoint(HopConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Joining as OBEP: " + cfg);
TunnelId recvId = cfg.getReceiveTunnel();
OutboundTunnelEndpoint endpoint = new OutboundTunnelEndpoint(_context, cfg, new HopProcessor(_context, cfg, _validator));
synchronized (_joinParticipantLock) {
if (_participatingConfig.putIfAbsent(recvId, cfg) != null)
return false;
if (_outboundEndpoints.putIfAbsent(recvId, endpoint) != null) {
_participatingConfig.remove(recvId);
return false;
}
}
_context.messageHistory().tunnelJoined("outboundEndpoint", cfg);
_context.statManager().addRateData("tunnel.joinOutboundEndpoint", 1);
if (cfg.getExpiration() > _lastParticipatingExpiration)
_lastParticipatingExpiration = cfg.getExpiration();
_leaveJob.add(cfg);
return true;
}
/**
* We are the inbound gateway in this tunnel, and did not create it
*
* @return success; false if Tunnel ID is a duplicate
*/
public boolean joinInboundGateway(HopConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Joining as IBGW: " + cfg);
TunnelGateway.QueuePreprocessor preproc = createPreprocessor(cfg);
TunnelGateway.Sender sender = new InboundSender(_context, cfg);
TunnelGateway.Receiver receiver = new InboundGatewayReceiver(_context, cfg);
//TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver);
TunnelGateway gw = new ThrottledPumpedTunnelGateway(_context, preproc, sender, receiver, _pumper, cfg);
TunnelId recvId = cfg.getReceiveTunnel();
synchronized (_joinParticipantLock) {
if (_participatingConfig.putIfAbsent(recvId, cfg) != null)
return false;
if (_inboundGateways.putIfAbsent(recvId, gw) != null) {
_participatingConfig.remove(recvId);
return false;
}
}
_context.messageHistory().tunnelJoined("inboundGateway", cfg);
_context.statManager().addRateData("tunnel.joinInboundGateway", 1);
if (cfg.getExpiration() > _lastParticipatingExpiration)
_lastParticipatingExpiration = cfg.getExpiration();
_leaveJob.add(cfg);
return true;
}
public int getParticipatingCount() {
return _participatingConfig.size();
}
/**
* Get a new random send tunnel ID that isn't a dup.
* Note that we do not keep track of IDs for pending builds so this
* does not fully prevent joinOutbound() from failing later.
* @since 0.9.5
*/
public TunnelId getNewOBGWID() {
long id;
TunnelId rv;
do {
id = 1 + _context.random().nextLong(TunnelId.MAX_ID_VALUE);
rv = new TunnelId(id);
} while (_outboundGateways.containsKey(rv));
return rv;
}
/**
* Get a new random receive tunnel ID that isn't a dup.
* Not for zero hop tunnels.
* Note that we do not keep track of IDs for pending builds so this
* does not fully prevent joinInbound() from failing later.
* @since 0.9.5
*/
public TunnelId getNewIBEPID() {
long id;
TunnelId rv;
do {
id = 1 + _context.random().nextLong(TunnelId.MAX_ID_VALUE);
rv = new TunnelId(id);
} while (_participants.containsKey(rv));
return rv;
}
/**
* Get a new random receive tunnel ID that isn't a dup.
* For zero hop tunnels only.
* Note that we do not keep track of IDs for pending builds so this
* does not fully prevent joinInbound() from failing later.
* @since 0.9.5
*/
public TunnelId getNewIBZeroHopID() {
long id;
TunnelId rv;
do {
id = 1 + _context.random().nextLong(TunnelId.MAX_ID_VALUE);
rv = new TunnelId(id);
} while (_inboundGateways.containsKey(rv));
return rv;
}
/******* may be used for congestion control later...
public int getParticipatingInboundGatewayCount() {
return _inboundGateways.size();
}
*******/
/** what is the date/time on which the last non-locally-created tunnel expires? */
public long getLastParticipatingExpiration() { return _lastParticipatingExpiration; }
/**
* We no longer want to participate in this tunnel that we created
*/
public void remove(TunnelCreatorConfig cfg) {
if (cfg.isInbound()) {
TunnelId recvId = cfg.getConfig(cfg.getLength()-1).getReceiveTunnel();
if (_log.shouldLog(Log.INFO))
_log.info("removing our own inbound " + cfg);
TunnelParticipant participant = _participants.remove(recvId);
if (participant == null) {
_inboundGateways.remove(recvId);
} else {
// update stats based off getCompleteCount() + getFailedCount()
// skip last hop (us)
for (int i = 0; i < cfg.getLength() - 1; i++) {
Hash peer = cfg.getPeer(i);
PeerProfile profile = _context.profileOrganizer().getProfile(peer);
if (profile != null) {
int ok = participant.getCompleteCount();
int fail = participant.getFailedCount();
profile.getTunnelHistory().incrementProcessed(ok, fail);
}
}
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("removing our own outbound " + cfg);
TunnelId outId = cfg.getConfig(0).getSendTunnel();
TunnelGateway gw = _outboundGateways.remove(outId);
if (gw != null) {
// update stats based on gw.getMessagesSent()
}
}
long msgs = cfg.getProcessedMessagesCount();
int failures = cfg.getTunnelFailures();
boolean failed = cfg.getTunnelFailed();
_context.statManager().addRateData("tunnel.ownedMessageCount", msgs, failures);
if (failed) {
_context.statManager().addRateData("tunnel.failedCompletelyMessages", msgs, failures);
} else if (failures > 0) {
_context.statManager().addRateData("tunnel.failedPartiallyMessages", msgs, failures);
}
}
/**
* No longer participate in the tunnel that someone asked us to be a member of
*
*/
public void remove(HopConfig cfg) {
TunnelId recvId = cfg.getReceiveTunnel();
boolean removed = (null != _participatingConfig.remove(recvId));
if (removed) {
if (_log.shouldLog(Log.INFO))
_log.info("removing " + cfg /* , new Exception() */ );
} else {
// this is normal, this can get called twice
if (_log.shouldLog(Log.DEBUG))
_log.debug("Participating tunnel, but no longer listed in participatingConfig? " + cfg /* , new Exception() */ );
}
removed = (null != _participants.remove(recvId));
if (removed) return;
removed = (null != _inboundGateways.remove(recvId));
if (removed) return;
_outboundEndpoints.remove(recvId);
}
/**
* We are participating in a tunnel (perhaps we're even the endpoint), so
* take the message and do what it says. If there are later hops, that
* means encrypt a layer and forward it on. If there aren't later hops,
* how we handle it depends upon whether we created it or not. If we didn't,
* simply honor the instructions. If we did, unwrap all the layers of
* encryption and honor those instructions (within reason).
*
*/
public void dispatch(TunnelDataMessage msg, Hash recvFrom) {
//long before = System.currentTimeMillis();
TunnelParticipant participant = _participants.get(msg.getTunnelIdObj());
if (participant != null) {
// we are either just a random participant or the inbound endpoint
if (_log.shouldLog(Log.DEBUG))
_log.debug("dispatch to participant " + participant + ": " + msg.getUniqueId() + " from "
+ recvFrom.toBase64().substring(0,4));
_context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId(), "participant");
participant.dispatch(msg, recvFrom);
_context.statManager().addRateData("tunnel.dispatchParticipant", 1);
} else {
OutboundTunnelEndpoint endpoint = _outboundEndpoints.get(msg.getTunnelIdObj());
if (endpoint != null) {
// we are the outobund endpoint
if (_log.shouldLog(Log.DEBUG))
_log.debug("dispatch where we are the outbound endpoint: " + endpoint + ": "
+ msg + " from " + recvFrom.toBase64().substring(0,4));
_context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId(), "outbound endpoint");
endpoint.dispatch(msg, recvFrom);
_context.statManager().addRateData("tunnel.dispatchEndpoint", 1);
} else {
// Somewhat common, probably due to somebody with large clock skew?
_context.messageHistory().droppedTunnelDataMessageUnknown(msg.getUniqueId(), msg.getTunnelId());
int level = (_context.router().getUptime() > 10*60*1000 ? Log.WARN : Log.DEBUG);
if (_log.shouldLog(level))
_log.log(level, "no matching participant/endpoint for id=" + msg.getTunnelId()
+ " expiring in " + DataHelper.formatDuration(msg.getMessageExpiration()-_context.clock().now())
+ ": existing = " + _participants.size() + " / " + _outboundEndpoints.size());
}
}
//long dispatchTime = System.currentTimeMillis() - before;
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Dispatch data time: " + dispatchTime + " participant? " + participant);
//_context.statManager().addRateData("tunnel.dispatchDataTime", dispatchTime, dispatchTime);
}
/** High for now, just to prevent long-lived-message attacks */
private static final long MAX_FUTURE_EXPIRATION = 3*60*1000 + Router.CLOCK_FUDGE_FACTOR;
/**
* We are the inbound tunnel gateway, so encrypt it as necessary and forward
* it on.
*
*/
public void dispatch(TunnelGatewayMessage msg) {
long before = _context.clock().now();
TunnelId id = msg.getTunnelId();
TunnelGateway gw = _inboundGateways.get(id);
I2NPMessage submsg = msg.getMessage();
// The contained message is nulled out when written
if (submsg == null)
throw new IllegalArgumentException("TGM message is null");
if (gw != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("dispatch where we are the inbound gateway: " + gw + ": " + msg);
long minTime = before - Router.CLOCK_FUDGE_FACTOR;
long maxTime = before + MAX_FUTURE_EXPIRATION;
long exp = msg.getMessageExpiration();
long subexp = submsg.getMessageExpiration();
if (exp < minTime || subexp < minTime ||
exp > maxTime || subexp > maxTime) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not dispatching a gateway message for tunnel " + id.getTunnelId()
+ " as the wrapper's expiration is in " + DataHelper.formatDuration(exp - before)
+ " and/or the content's expiration is in " + DataHelper.formatDuration(subexp - before)
+ " with messageId " + id + "/" + submsg.getUniqueId()
+ " messageType: " + submsg.getClass().getSimpleName());
return;
}
//_context.messageHistory().tunnelDispatched("message " + msg.getRawUniqueId() + "/" + msg.getMessage().getRawUniqueId() + " on tunnel "
// + msg.getTunnelId().getTunnelId() + " as inbound gateway");
_context.messageHistory().tunnelDispatched(msg.getUniqueId(),
submsg.getUniqueId(),
id.getTunnelId(), "inbound gateway");
gw.add(msg);
_context.statManager().addRateData("tunnel.dispatchInbound", 1);
} else {
_context.messageHistory().droppedTunnelGatewayMessageUnknown(msg.getUniqueId(), id.getTunnelId());
int level = (_context.router().getUptime() > 10*60*1000 ? Log.WARN : Log.INFO);
if (_log.shouldLog(level))
_log.log(level, "no matching tunnel for id=" + id.getTunnelId()
+ ": gateway message expiring in "
+ DataHelper.formatDuration(msg.getMessageExpiration() - before)
+ "/"
+ DataHelper.formatDuration(submsg.getMessageExpiration() - before)
+ " messageId " + id
+ "/" + msg.getMessage().getUniqueId()
+ " messageType: " + submsg.getClass().getSimpleName()
+ " existing = " + _inboundGateways.size());
}
//long dispatchTime = _context.clock().now() - before;
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Dispatch in gw time: " + dispatchTime + " gateway? " + gw);
//_context.statManager().addRateData("tunnel.dispatchGatewayTime", dispatchTime, dispatchTime);
}
/**
* We are the outbound tunnel gateway (we created it), so wrap up this message
* with instructions to be forwarded to the targetPeer when it reaches the
* endpoint.
*
* @param msg raw message to deliver to the target peer
* @param outboundTunnel tunnel to send the message out, or null for direct
* @param targetPeer peer to receive the message
*/
public void dispatchOutbound(I2NPMessage msg, TunnelId outboundTunnel, Hash targetPeer) {
dispatchOutbound(msg, outboundTunnel, null, targetPeer);
}
/**
* We are the outbound tunnel gateway (we created it), so wrap up this message
* with instructions to be forwarded to the targetTunnel on the targetPeer when
* it reaches the endpoint.
*
* @param msg raw message to deliver to the targetTunnel on the targetPeer
* @param outboundTunnel tunnel to send the message out
* @param targetTunnel tunnel on the targetPeer to deliver the message to, or null for direct
* @param targetPeer gateway to the tunnel to receive the message
*/
public void dispatchOutbound(I2NPMessage msg, TunnelId outboundTunnel, TunnelId targetTunnel, Hash targetPeer) {
if (outboundTunnel == null) throw new IllegalArgumentException("null outbound tunnel?");
long before = _context.clock().now();
TunnelGateway gw = _outboundGateways.get(outboundTunnel);
if (gw != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("dispatch outbound through " + outboundTunnel.getTunnelId()
+ ": " + msg);
if (msg.getMessageExpiration() < before - Router.CLOCK_FUDGE_FACTOR) {
if (_log.shouldLog(Log.ERROR))
_log.error("why are you sending a tunnel message that expired "
+ (before-msg.getMessageExpiration()) + "ms ago? "
+ msg, new Exception("cause"));
return;
} else if (msg.getMessageExpiration() < before) {
// nonfatal, as long as it was remotely created
if (_log.shouldLog(Log.WARN))
_log.warn("why are you sending a tunnel message that expired "
+ (before-msg.getMessageExpiration()) + "ms ago? "
+ msg, new Exception("cause"));
} else if (msg.getMessageExpiration() > before + MAX_FUTURE_EXPIRATION) {
if (_log.shouldLog(Log.ERROR))
_log.error("why are you sending a tunnel message that expires "
+ (msg.getMessageExpiration() - before) + "ms from now? "
+ msg, new Exception("cause"));
return;
}
long tid1 = outboundTunnel.getTunnelId();
long tid2 = (targetTunnel != null ? targetTunnel.getTunnelId() : -1);
_context.messageHistory().tunnelDispatched(msg.getUniqueId(), tid1, tid2, targetPeer, "outbound gateway");
gw.add(msg, targetPeer, targetTunnel);
if (targetTunnel == null)
_context.statManager().addRateData("tunnel.dispatchOutboundPeer", 1);
else
_context.statManager().addRateData("tunnel.dispatchOutboundTunnel", 1);
} else {
_context.messageHistory().droppedTunnelGatewayMessageUnknown(msg.getUniqueId(), outboundTunnel.getTunnelId());
//int level = (_context.router().getUptime() > 10*60*1000 ? Log.ERROR : Log.WARN);
int level = Log.WARN;
if (_log.shouldLog(level))
_log.log(level, "no matching outbound tunnel for id=" + outboundTunnel
+ ": existing = " + _outboundGateways.size(), new Exception("src"));
}
//long dispatchTime = _context.clock().now() - before;
//if (dispatchTime > 1000) {
// if (_log.shouldLog(Log.WARN))
// _log.warn("slow? took " + dispatchTime + " to dispatch " + msg + " out " + outboundTunnel + " in " + gw);
//}
//if (gw instanceof TunnelGatewayZeroHop)
// _context.statManager().addRateData("tunnel.dispatchOutboundZeroHopTime", dispatchTime, dispatchTime);
//else
// _context.statManager().addRateData("tunnel.dispatchOutboundTime", dispatchTime, dispatchTime);
}
/**
* Only for console TunnelRenderer.
* @return a copy
*/
public List listParticipatingTunnels() {
return new ArrayList(_participatingConfig.values());
}
/**
* Generate a current estimate of usage per-participating-tunnel lifetime.
* The router code calls this every 'ms' millisecs.
* This is better than waiting until the tunnel expires to update the rate,
* as we want this to be current because it's an important part of
* the throttle code.
* Stay a little conservative by taking the counts only for tunnels 1-10m old
* and computing the average from that.
*/
public void updateParticipatingStats(int ms) {
long count = 0;
long bw = 0;
//long bwOut = 0;
long tcount = 0;
long tooYoung = _context.clock().now() - 60*1000;
long tooOld = tooYoung - 9*60*1000;
for (HopConfig cfg : _participatingConfig.values()) {
long c = cfg.getAndResetRecentMessagesCount();
bw += c;
//bwOut += cfg.getRecentSentMessagesCount();
long created = cfg.getCreation();
if (created > tooYoung || created < tooOld)
continue;
tcount++;
count += c;
}
// This is an estimate of the average number of participating messages per tunnel
// in a tunnel lifetime, used only by RouterThrottleImpl
// 10 minutes / 50 seconds = 12
if (tcount > 0)
count = count * (10*60*1000 / ms) / tcount;
_context.statManager().addRateData("tunnel.participatingMessageCountAvgPerTunnel", count, ms);
// This is a straight count of the total participating messages, used in the router console
_context.statManager().addRateData("tunnel.participatingMessageCount", bw, ms);
// Bandwidth in bits per second
_context.statManager().addRateData("tunnel.participatingBandwidth", bw*1024/(ms/1000), ms);
// moved to FIFOBandwidthRefiller
//_context.statManager().addRateData("tunnel.participatingBandwidthOut", bwOut*1024/(ms/1000), ms);
_context.statManager().addRateData("tunnel.participatingTunnels", tcount);
}
/**
* Implement random early discard (RED) to enforce the share bandwidth limit.
* For now, this does not enforce the available bandwidth,
* we leave that to Throttle.
* This is similar to the code in ../RouterThrottleImpl.java
* We drop in proportion to how far over the limit we are.
* Perhaps an exponential function would be better?
*
* The drop probability is adjusted for the size of the message.
* At this stage, participants and IBGWs see a standard 1024 byte message.
* OBEPs however may see a wide variety of sizes.
*
* Network-wise, it's most efficient to drop OBEP messages, because they
* are unfragmented and we know their size. Therefore we drop the big ones
* and we drop a single wrapped I2CP message, not a fragment of one or more messages.
* Also, the OBEP is the earliest identifiable hop in the message's path
* (a plain participant could be earlier or later, but on average is later)
*
* @param loc message hop location
* @param type I2NP message type
* @param length the length of the message
*/
public boolean shouldDropParticipatingMessage(Location loc, int type, int length) {
if (length <= 0)
return false;
// increase the drop probability for OBEP,
// (except lower it for tunnel build messages type 21/22/23/24),
// and lower it for IBGW, for network efficiency
float factor;
if (loc == Location.OBEP) {
// we don't need to check for VTBRM/TBRM as that happens at tunnel creation
if (type == VariableTunnelBuildMessage.MESSAGE_TYPE || type == TunnelBuildMessage.MESSAGE_TYPE ||
type == ShortTunnelBuildMessage.MESSAGE_TYPE)
factor = 1 / 1.5f;
else if (type == DatabaseLookupMessage.MESSAGE_TYPE && length < 1024)
factor = 8f;
else
factor = 1.5f;
} else if (loc == Location.IBGW) {
// we don't need to check for VTBM/TBM as that happens at tunnel creation
if (type == VariableTunnelBuildReplyMessage.MESSAGE_TYPE || type == TunnelBuildReplyMessage.MESSAGE_TYPE ||
type == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE)
factor = 1 / (1.5f * 1.5f * 1.5f);
else
factor = 1 / 1.5f;
} else {
factor = 1.0f;
}
boolean reject = ! _context.bandwidthLimiter().sentParticipatingMessage(length, factor);
if (reject) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("Drop part. msg. factor=" + factor
+ ' ' + loc + ' ' + type + ' ' + length);
}
_context.statManager().addRateData("tunnel.participatingMessageDropped", 1);
}
return reject;
}
//private static final int DROP_BASE_INTERVAL = 40 * 1000;
//private static final int DROP_RANDOM_BOOST = 10 * 1000;
/**
* If a router is too overloaded to build its own tunnels,
* the build executor may call this.
*/
/*******
public void dropBiggestParticipating() {
List partTunnels = listParticipatingTunnels();
if ((partTunnels == null) || (partTunnels.isEmpty())) {
if (_log.shouldLog(Log.ERROR))
_log.error("Not dropping tunnel, since partTunnels was null or had 0 items!");
return;
}
long periodWithoutDrop = _context.clock().now() - _lastDropTime;
if (periodWithoutDrop < DROP_BASE_INTERVAL) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not dropping tunnel, since last drop was " + periodWithoutDrop + " ms ago!");
return;
}
HopConfig biggest = null;
HopConfig current = null;
long biggestMessages = 0;
long biggestAge = -1;
double biggestRate = 0;
for (int i=0; i 20) && ((biggest == null) || (currentRate > biggestRate))) {
// Update our profile of the biggest
biggest = current;
biggestMessages = currentMessages;
biggestAge = currentAge;
biggestRate = currentRate;
}
}
if (biggest == null) {
if (_log.shouldLog(Log.ERROR))
_log.error("Not dropping tunnel, since no suitable tunnel was found.");
return;
}
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping tunnel with " + biggestRate + " messages/s and " + biggestMessages +
" messages, last drop was " + (periodWithoutDrop / 1000) + " s ago.");
remove(biggest);
_lastDropTime = _context.clock().now() + _context.random().nextInt(DROP_RANDOM_BOOST);
}
******/
/** startup */
public synchronized void startup() {
// Note that we only use the validator for participants and OBEPs, not IBGWs, so
// this BW estimate will be high by about 33% assuming 2-hop tunnels average
_validator = new BloomFilterIVValidator(_context, getShareBandwidth(_context));
}
/** @return in KBps */
public static int getShareBandwidth(RouterContext ctx) {
int irateKBps = ctx.bandwidthLimiter().getInboundKBytesPerSecond();
int orateKBps = ctx.bandwidthLimiter().getOutboundKBytesPerSecond();
double pct = ctx.router().getSharePercentage();
return (int) (pct * Math.min(irateKBps, orateKBps));
}
public synchronized void shutdown() {
if (_validator != null)
_validator.destroy();
_validator = null;
_pumper.stopPumping();
_outboundGateways.clear();
_outboundEndpoints.clear();
_participants.clear();
_inboundGateways.clear();
_participatingConfig.clear();
_leaveJob.clear();
}
public void restart() {
shutdown();
startup();
}
/** @deprecated moved to router console */
@Deprecated
public void renderStatusHTML(Writer out) throws IOException {}
/**
* Expire participants.
* For efficiency, we keep the HopConfigs in a FIFO, and assume that
* tunnels expire (roughly) in the same order as they are added.
* As tunnels have a fixed expiration from now, that's a good assumption -
* see BuildHandler.handleReq().
*/
private class LeaveTunnel extends JobImpl {
private final LinkedBlockingQueue _configs;
public LeaveTunnel(RouterContext ctx) {
super(ctx);
_configs = new LinkedBlockingQueue();
// 10 min no tunnels accepted + 10 min tunnel expiration
long rejectStartupTime = ctx.getProperty(RouterThrottleImpl.PROP_REJECT_STARTUP_TIME, RouterThrottleImpl.DEFAULT_REJECT_STARTUP_TIME);
getTiming().setStartAfter(ctx.clock().now() + rejectStartupTime + 10*60*1000);
getContext().jobQueue().addJob(LeaveTunnel.this);
}
private static final int LEAVE_BATCH_TIME = 10*1000;
public void add(HopConfig cfg) {
_configs.offer(cfg);
}
public void clear() {
_configs.clear();
}
public String getName() { return "Expire participating tunnels"; }
public void runJob() {
HopConfig cur = null;
long now = getContext().clock().now() + LEAVE_BATCH_TIME; // leave all expiring in next 10 sec
long nextTime = now + 10*60*1000;
while ((cur = _configs.peek()) != null) {
long exp = cur.getExpiration() + (3 * Router.CLOCK_FUDGE_FACTOR / 2) + LEAVE_BATCH_TIME;
if (exp < now) {
_configs.poll();
if (_log.shouldLog(Log.INFO))
_log.info("Expiring " + cur);
remove(cur);
} else {
if (exp < nextTime)
nextTime = exp;
break;
}
}
getTiming().setStartAfter(nextTime);
getContext().jobQueue().addJob(LeaveTunnel.this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy