use of net.i2p.router.TunnelInfo in project i2p.i2p by i2p.
the class StoreJob method sendStoreThroughClient.
/**
* Send a leaseset store message out the client tunnel,
* with the reply to come back through a client tunnel.
* Stores are garlic encrypted to hide the identity from the OBEP.
*
* This makes it harder for an exploratory OBEP or IBGW to correlate it
* with one or more destinations. Since we are publishing the leaseset,
* it's easy to find out that an IB tunnel belongs to this dest, and
* it isn't much harder to do the same for an OB tunnel.
*
* As a side benefit, client tunnels should be faster and more reliable than
* exploratory tunnels.
*
* @param msg must contain a leaseset
* @since 0.7.10
*/
private void sendStoreThroughClient(DatabaseStoreMessage msg, RouterInfo peer, long expiration) {
long token = 1 + getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
Hash client = msg.getKey();
Hash to = peer.getIdentity().getHash();
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel(client, to);
if (replyTunnel == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No reply inbound tunnels available!");
fail();
return;
}
TunnelId replyTunnelId = replyTunnel.getReceiveTunnelId(0);
msg.setReplyToken(token);
msg.setReplyTunnel(replyTunnelId);
msg.setReplyGateway(replyTunnel.getPeer(0));
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send(dbStore) w/ token expected " + token);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel(client, to);
if (outTunnel != null) {
I2NPMessage sent;
boolean shouldEncrypt = supportsEncryption(peer);
if (shouldEncrypt) {
// garlic encrypt
MessageWrapper.WrappedMessage wm = MessageWrapper.wrap(getContext(), msg, client, peer);
if (wm == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Fail garlic encrypting from: " + client);
fail();
return;
}
sent = wm.getMessage();
_state.addPending(to, wm);
} else {
_state.addPending(to);
// now that almost all floodfills are at 0.7.10,
// just refuse to store unencrypted to older ones.
_state.replyTimeout(to);
getContext().jobQueue().addJob(new WaitJob(getContext()));
return;
}
SendSuccessJob onReply = new SendSuccessJob(getContext(), peer, outTunnel, sent.getMessageSize());
FailedJob onFail = new FailedJob(getContext(), peer, getContext().clock().now());
StoreMessageSelector selector = new StoreMessageSelector(getContext(), getJobId(), peer, token, expiration);
if (_log.shouldLog(Log.DEBUG)) {
if (shouldEncrypt)
_log.debug("sending encrypted store to " + peer.getIdentity().getHash() + " through " + outTunnel + ": " + sent);
else
_log.debug("sending store to " + peer.getIdentity().getHash() + " through " + outTunnel + ": " + sent);
// _log.debug("Expiration is " + new Date(sent.getMessageExpiration()));
}
getContext().messageRegistry().registerPending(selector, onReply, onFail);
getContext().tunnelDispatcher().dispatchOutbound(sent, outTunnel.getSendTunnelId(0), null, to);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No outbound tunnels to send a dbStore out - delaying...");
// continueSending() above did an addPending() so remove it here.
// This means we will skip the peer next time, can't be helped for now
// without modding StoreState
_state.replyTimeout(to);
Job waiter = new WaitJob(getContext());
waiter.getTiming().setStartAfter(getContext().clock().now() + 3 * 1000);
getContext().jobQueue().addJob(waiter);
// fail();
}
}
use of net.i2p.router.TunnelInfo in project i2p.i2p by i2p.
the class TunnelPool method selectTunnel.
private TunnelInfo selectTunnel(boolean allowRecurseOnFail) {
boolean avoidZeroHop = getSettings().getLength() > 0 && getSettings().getLength() + getSettings().getLengthVariance() > 0;
long period = curPeriod();
synchronized (_tunnels) {
if (_lastSelectionPeriod == period) {
if ((_lastSelected != null) && (_lastSelected.getExpiration() > period) && (_tunnels.contains(_lastSelected)))
return _lastSelected;
}
_lastSelectionPeriod = period;
_lastSelected = null;
if (_tunnels.isEmpty()) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": No tunnels to select from");
} else {
Collections.shuffle(_tunnels, _context.random());
// if there are nonzero hop tunnels and the zero hop tunnels are fallbacks,
// avoid the zero hop tunnels
TunnelInfo backloggedTunnel = null;
if (avoidZeroHop) {
for (int i = 0; i < _tunnels.size(); i++) {
TunnelInfo info = _tunnels.get(i);
if ((info.getLength() > 1) && (info.getExpiration() > _context.clock().now())) {
// avoid outbound tunnels where the 1st hop is backlogged
if (_settings.isInbound() || !_context.commSystem().isBacklogged(info.getPeer(1))) {
_lastSelected = info;
return info;
} else {
backloggedTunnel = info;
}
}
}
// return a random backlogged tunnel
if (backloggedTunnel != null) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": All tunnels are backlogged");
return backloggedTunnel;
}
}
// randomly
for (int i = 0; i < _tunnels.size(); i++) {
TunnelInfo info = _tunnels.get(i);
if (info.getExpiration() > _context.clock().now()) {
// avoid outbound tunnels where the 1st hop is backlogged
if (_settings.isInbound() || info.getLength() <= 1 || !_context.commSystem().isBacklogged(info.getPeer(1))) {
// _log.debug("Selecting tunnel: " + info + " - " + _tunnels);
_lastSelected = info;
return info;
} else {
backloggedTunnel = info;
}
}
}
// return a random backlogged tunnel
if (backloggedTunnel != null)
return backloggedTunnel;
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": after " + _tunnels.size() + " tries, no unexpired ones were found: " + _tunnels);
}
}
if (_alive && _settings.getAllowZeroHop())
buildFallback();
if (allowRecurseOnFail)
return selectTunnel(false);
else
return null;
}
use of net.i2p.router.TunnelInfo in project i2p.i2p by i2p.
the class TunnelPool method selectTunnel.
/**
* Return the tunnel from the pool that is XOR-closet to the target.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* Does not check for backlogged next peer.
* Does not return an expired tunnel.
*
* @return null on failure
* @since 0.8.10
*/
TunnelInfo selectTunnel(Hash closestTo) {
boolean avoidZeroHop = getSettings().getLength() > 0 && getSettings().getLength() + getSettings().getLengthVariance() > 0;
TunnelInfo rv = null;
synchronized (_tunnels) {
if (!_tunnels.isEmpty()) {
if (_tunnels.size() > 1)
Collections.sort(_tunnels, new TunnelInfoComparator(closestTo, avoidZeroHop));
for (TunnelInfo info : _tunnels) {
if (info.getExpiration() > _context.clock().now()) {
rv = info;
break;
}
}
}
}
if (rv != null) {
_context.statManager().addRateData("tunnel.matchLease", closestTo.equals(rv.getFarEnd()) ? 1 : 0);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": No tunnels to select from");
}
return rv;
}
use of net.i2p.router.TunnelInfo in project i2p.i2p by i2p.
the class TunnelPool method countHowManyToBuild.
/**
* Gather the data to see how many tunnels to build, and then actually compute that value (delegated to
* the countHowManyToBuild function below)
*/
int countHowManyToBuild() {
if (!isAlive()) {
return 0;
}
int wanted = getAdjustedTotalQuantity();
boolean allowZeroHop = ((getSettings().getLength() + getSettings().getLengthVariance()) <= 0);
/**
* This algorithm builds based on the previous average length of time it takes
* to build a tunnel. This average is kept in the _buildRateName stat.
* It is a separate stat for each type of pool, since in and out building use different methods,
* as do exploratory and client pools,
* and each pool can have separate length and length variance settings.
* We add one minute to the stat for safety (two for exploratory tunnels).
*
* We linearly increase the number of builds per expiring tunnel from
* 1 to PANIC_FACTOR as the time-to-expire gets shorter.
*
* The stat will be 0 for first 10m of uptime so we will use the older, conservative algorithm
* below instead. This algorithm will take about 30m of uptime to settle down.
* Or, if we are building more than 33% of the time something is seriously wrong,
* we also use the conservative algorithm instead
*/
final String rateName = buildRateName();
// Compute the average time it takes us to build a single tunnel of this type.
int avg = 0;
RateStat rs = _context.statManager().getRate(rateName);
if (rs == null) {
// Create the RateStat here rather than at the top because
// the user could change the length settings while running
_context.statManager().createRequiredRateStat(rateName, "Tunnel Build Frequency", "Tunnels", new long[] { TUNNEL_LIFETIME });
rs = _context.statManager().getRate(rateName);
}
if (rs != null) {
Rate r = rs.getRate(TUNNEL_LIFETIME);
if (r != null)
avg = (int) (TUNNEL_LIFETIME * r.getAverageValue() / wanted);
}
if (avg > 0 && avg < TUNNEL_LIFETIME / 3) {
// if we're taking less than 200s per tunnel to build
// how many builds to kick off when time gets short
final int PANIC_FACTOR = 4;
// one minute safety factor
avg += 60 * 1000;
if (_settings.isExploratory())
// two minute safety factor
avg += 60 * 1000;
long now = _context.clock().now();
int expireSoon = 0;
int expireLater = 0;
int[] expireTime;
int fallback = 0;
synchronized (_tunnels) {
expireTime = new int[_tunnels.size()];
for (int i = 0; i < _tunnels.size(); i++) {
TunnelInfo info = _tunnels.get(i);
if (allowZeroHop || (info.getLength() > 1)) {
int timeToExpire = (int) (info.getExpiration() - now);
if (timeToExpire > 0 && timeToExpire < avg) {
expireTime[expireSoon++] = timeToExpire;
} else {
expireLater++;
}
} else if (info.getExpiration() - now > avg) {
fallback++;
}
}
}
int inProgress;
synchronized (_inProgress) {
inProgress = _inProgress.size();
}
int remainingWanted = (wanted - expireLater) - inProgress;
if (allowZeroHop)
remainingWanted -= fallback;
int rv = 0;
int latesttime = 0;
if (remainingWanted > 0) {
if (remainingWanted > expireSoon) {
// for tunnels completely missing
rv = PANIC_FACTOR * (remainingWanted - expireSoon);
remainingWanted = expireSoon;
}
// the other ones are extras
for (int i = 0; i < remainingWanted; i++) {
int latestidx = 0;
// given the small size of the array this is efficient enough
for (int j = 0; j < expireSoon; j++) {
if (expireTime[j] > latesttime) {
latesttime = expireTime[j];
latestidx = j;
}
}
expireTime[latestidx] = 0;
if (latesttime > avg / 2)
rv += 1;
else
rv += 2 + ((PANIC_FACTOR - 2) * (((avg / 2) - latesttime) / (avg / 2)));
}
}
if (rv > 0 && _log.shouldLog(Log.DEBUG))
_log.debug("New Count: rv: " + rv + " allow? " + allowZeroHop + " avg " + avg + " latesttime " + latesttime + " soon " + expireSoon + " later " + expireLater + " std " + wanted + " inProgress " + inProgress + " fallback " + fallback + " for " + toString());
_context.statManager().addRateData(rateName, rv + inProgress, 0);
return rv;
}
// fixed, conservative algorithm - starts building 3 1/2 - 6m before expiration
// (210 or 270s) + (0..90s random)
// + _settings.getRebuildPeriod() + _expireSkew;
long expireAfter = _context.clock().now() + _expireSkew;
int expire30s = 0;
int expire90s = 0;
int expire150s = 0;
int expire210s = 0;
int expire270s = 0;
int expireLater = 0;
int fallback = 0;
synchronized (_tunnels) {
for (int i = 0; i < _tunnels.size(); i++) {
TunnelInfo info = _tunnels.get(i);
if (allowZeroHop || (info.getLength() > 1)) {
long timeToExpire = info.getExpiration() - expireAfter;
if (timeToExpire <= 0) {
// consider it unusable
} else if (timeToExpire <= 30 * 1000) {
expire30s++;
} else if (timeToExpire <= 90 * 1000) {
expire90s++;
} else if (timeToExpire <= 150 * 1000) {
expire150s++;
} else if (timeToExpire <= 210 * 1000) {
expire210s++;
} else if (timeToExpire <= 270 * 1000) {
expire270s++;
} else {
expireLater++;
}
} else if (info.getExpiration() > expireAfter) {
fallback++;
}
}
}
int inProgress = 0;
synchronized (_inProgress) {
inProgress = _inProgress.size();
for (int i = 0; i < inProgress; i++) {
PooledTunnelCreatorConfig cfg = _inProgress.get(i);
if (cfg.getLength() <= 1)
fallback++;
}
}
int rv = countHowManyToBuild(allowZeroHop, expire30s, expire90s, expire150s, expire210s, expire270s, expireLater, wanted, inProgress, fallback);
_context.statManager().addRateData(rateName, (rv > 0 || inProgress > 0) ? 1 : 0, 0);
return rv;
}
use of net.i2p.router.TunnelInfo in project i2p.i2p by i2p.
the class TunnelPool method locked_buildNewLeaseSet.
/**
* Build a leaseSet with the required tunnels that aren't about to expire.
* Caller must synchronize on _tunnels.
*
* @return null on failure
*/
protected LeaseSet locked_buildNewLeaseSet() {
if (!_alive)
return null;
int wanted = Math.min(_settings.getQuantity(), LeaseSet.MAX_LEASES);
if (_tunnels.size() < wanted) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": Not enough tunnels (" + _tunnels.size() + ", wanted " + wanted + ")");
// see comment below
if (_tunnels.isEmpty())
return null;
}
// + _settings.getRebuildPeriod();
long expireAfter = _context.clock().now();
TunnelInfo zeroHopTunnel = null;
Lease zeroHopLease = null;
TreeSet<Lease> leases = new TreeSet<Lease>(new LeaseComparator());
for (int i = 0; i < _tunnels.size(); i++) {
TunnelInfo tunnel = _tunnels.get(i);
if (tunnel.getExpiration() <= expireAfter)
// expires too soon, skip it
continue;
if (tunnel.getLength() <= 1) {
// Keep only the one that expires the latest.
if (zeroHopTunnel != null) {
if (zeroHopTunnel.getExpiration() > tunnel.getExpiration())
continue;
if (zeroHopLease != null)
leases.remove(zeroHopLease);
}
zeroHopTunnel = tunnel;
}
TunnelId inId = tunnel.getReceiveTunnelId(0);
Hash gw = tunnel.getPeer(0);
if ((inId == null) || (gw == null)) {
_log.error(toString() + ": broken? tunnel has no inbound gateway/tunnelId? " + tunnel);
continue;
}
Lease lease = new Lease();
// bugfix
// ExpireJob reduces the expiration, which causes a 2nd leaseset with the same lease
// to have an earlier expiration, so it isn't stored.
// Get the "real" expiration from the gateway hop config,
// HopConfig expirations are the same as the "real" expiration and don't change
// see configureNewTunnel()
lease.setEndDate(new Date(((TunnelCreatorConfig) tunnel).getConfig(0).getExpiration()));
lease.setTunnelId(inId);
lease.setGateway(gw);
leases.add(lease);
// remember in case we want to remove it for a later-expiring zero-hopper
if (tunnel.getLength() <= 1)
zeroHopLease = lease;
}
// Do we want a config option for this, or are there times when we shouldn't do this?
if (leases.size() < wanted) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": Not enough leases (" + leases.size() + ", wanted " + wanted + ")");
if (leases.isEmpty())
return null;
}
LeaseSet ls = new LeaseSet();
Iterator<Lease> iter = leases.iterator();
int count = Math.min(leases.size(), wanted);
for (int i = 0; i < count; i++) ls.addLease(iter.next());
if (_log.shouldLog(Log.INFO))
_log.info(toString() + ": built new leaseSet: " + ls);
return ls;
}
Aggregations