package net.i2p.router.tunnel;
import java.util.HashSet;
import java.util.Set;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
import net.i2p.router.JobImpl;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* When a message arrives at the outbound tunnel endpoint, this distributor
* honors the instructions.
*/
class OutboundMessageDistributor {
private final RouterContext _context;
private final int _priority;
private final Log _log;
// following only for somebody else's OBEP, not for zero-hop
private final Set<Hash> _toRouters;
private int _newRouterCount;
private long _newRouterTime;
private static final long MAX_DISTRIBUTE_TIME = 15*1000;
// This is probably too high, to be reduced later
private static final int MAX_ROUTERS_PER_PERIOD = 60;
private static final long NEW_ROUTER_PERIOD = 30*1000;
/**
* @param priority OutNetMessage.PRIORITY_PARTICIPATING for somebody else's OBEP, or
* OutNetMessage.PRIORITY_MY_DATA for our own zero-hop OBGW/EP
*/
public OutboundMessageDistributor(RouterContext ctx, int priority) {
_context = ctx;
_priority = priority;
_log = ctx.logManager().getLog(OutboundMessageDistributor.class);
if (priority <= OutNetMessage.PRIORITY_PARTICIPATING) {
_toRouters = new HashSet<Hash>(4);
_toRouters.add(ctx.routerHash());
} else {
_toRouters = null;
}
// all createRateStat() in TunnelDispatcher
}
public void distribute(I2NPMessage msg, Hash target) {
distribute(msg, target, null);
}
public void distribute(I2NPMessage msg, Hash target, TunnelId tunnel) {
if (shouldDrop(target)) {
_context.statManager().addRateData("tunnel.dropAtOBEP", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Drop msg at OBEP (new conn throttle) to " + target + ' ' + msg);
return;
}
RouterInfo info = _context.netDb().lookupRouterInfoLocally(target);
if (info == null) {
if (_log.shouldLog(Log.INFO))
_log.info("outbound distributor to " + target
+ "." + (tunnel != null ? tunnel.getTunnelId() + "" : "")
+ ": no info locally, searching...");
// TODO - should we set the search timeout based on the message timeout,
// or is that a bad idea due to clock skews?
_context.netDb().lookupRouterInfo(target, new DistributeJob(_context, msg, target, tunnel), null, MAX_DISTRIBUTE_TIME);
return;
} else {
distribute(msg, info, tunnel);
}
}
/**
* Throttle msgs to unconnected routers after we hit
* the limit of new routers in a given time period.
* @since 0.9.12
*/
private boolean shouldDrop(Hash target) {
if (_toRouters == null)
return false;
synchronized(this) {
if (_toRouters.contains(target))
return false;
// haven't sent to this router before
long now = _context.clock().now();
if (_newRouterTime < now - NEW_ROUTER_PERIOD) {
_newRouterCount = 0;
_newRouterTime = now;
} else if (_newRouterCount >= MAX_ROUTERS_PER_PERIOD) {
if (!_context.commSystem().isEstablished(target))
return true; //drop
}
_newRouterCount++;
_toRouters.add(target);
}
return false;
}
private void distribute(I2NPMessage msg, RouterInfo target, TunnelId tunnel) {
I2NPMessage m = msg;
if (tunnel != null) {
TunnelGatewayMessage t = new TunnelGatewayMessage(_context);
t.setMessage(msg);
t.setTunnelId(tunnel);
t.setMessageExpiration(m.getMessageExpiration());
m = t;
}
if (_context.routerHash().equals(target.getIdentity().calculateHash())) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("queueing inbound message to ourselves: " + m);
// TODO if UnknownI2NPMessage, convert it.
// See FragmentHandler.receiveComplete()
_context.inNetMessagePool().add(m, null, null);
return;
} else {
OutNetMessage out = new OutNetMessage(_context, m, _context.clock().now() + MAX_DISTRIBUTE_TIME, _priority, target);
if (_log.shouldLog(Log.DEBUG))
_log.debug("queueing outbound message to " + target.getIdentity().calculateHash());
_context.outNetMessagePool().add(out);
}
}
private class DistributeJob extends JobImpl {
private final I2NPMessage _message;
private final Hash _target;
private final TunnelId _tunnel;
public DistributeJob(RouterContext ctx, I2NPMessage msg, Hash target, TunnelId id) {
super(ctx);
_message = msg;
_target = target;
_tunnel = id;
}
public String getName() { return "OBEP distribute after lookup"; }
public void runJob() {
RouterInfo info = getContext().netDb().lookupRouterInfoLocally(_target);
int stat;
if (info != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("outbound distributor to " + _target
+ "." + (_tunnel != null ? _tunnel.getTunnelId() + "" : "")
+ ": found on search");
distribute(_message, info, _tunnel);
stat = 1;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("outbound distributor to " + _target
+ "." + (_tunnel != null ? _tunnel.getTunnelId() + "" : "")
+ ": NOT found on search");
stat = 0;
}
_context.statManager().addRateData("tunnel.distributeLookupSuccess", stat);
}
}
}