* StoreJob: Ensure nonzero token

* Tunnels: Connection limit mitigation:
    - Disable tunnel testing
    - Implement closest-to-the-key tunnel selection
    - Use closest-selection in NetDB lookups, stores, and verifies;
      OCMOSJ; and in BuildRequestor
This commit is contained in:
zzz
2011-10-18 19:28:47 +00:00
parent abd823ab95
commit 81093d1342
14 changed files with 347 additions and 92 deletions

View File

@@ -1,3 +1,11 @@
* 2011-10-18 zzz
* StoreJob: Ensure nonzero token
* Tunnels: Connection limit mitigation:
- Disable tunnel testing
- Implement closest-to-the-key tunnel selection
- Use closest-selection in NetDB lookups, stores, and verifies;
OCMOSJ; and in BuildRequestor
* 2011-10-17 zzz
* BuildExecutor: Efficiency tweak
* Console: Hide tunnel lag if tunnel testing is disabled

View File

@@ -30,6 +30,11 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade {
public TunnelInfo selectInboundTunnel(Hash destination) { return null; }
public TunnelInfo selectOutboundTunnel() { return null; }
public TunnelInfo selectOutboundTunnel(Hash destination) { return null; }
public TunnelInfo selectInboundExploratoryTunnel(Hash closestTo) { return null; }
public TunnelInfo selectInboundTunnel(Hash destination, Hash closestTo) { return null; }
public TunnelInfo selectOutboundExploratoryTunnel(Hash closestTo) { return null; }
public TunnelInfo selectOutboundTunnel(Hash destination, Hash closestTo) { return null; }
public boolean isValidTunnel(Hash client, TunnelInfo tunnel) { return false; }
public int getParticipatingCount() { return 0; }
public int getFreeTunnelCount() { return 0; }

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 = 2;
public final static long BUILD = 3;
/** for example "-test" */
public final static String EXTRA = "";

View File

@@ -22,6 +22,7 @@ import net.i2p.router.tunnel.pool.TunnelPool;
*
*/
public interface TunnelManagerFacade extends Service {
/**
* Retrieve the information related to a particular tunnel
*
@@ -29,15 +30,89 @@ public interface TunnelManagerFacade extends Service {
*
*/
TunnelInfo getTunnelInfo(TunnelId id);
/** pick an inbound tunnel not bound to a particular destination */
/**
* Pick a random inbound exploratory tunnel
*
* @return null if none
*/
TunnelInfo selectInboundTunnel();
/** pick an inbound tunnel bound to the given destination */
/**
* Pick a random inbound tunnel from the given destination's pool
*
* @param destination if null, returns inbound exploratory tunnel
* @return null if none
*/
TunnelInfo selectInboundTunnel(Hash destination);
/** pick an outbound tunnel not bound to a particular destination */
/**
* Pick a random outbound exploratory tunnel
*
* @return null if none
*/
TunnelInfo selectOutboundTunnel();
/** pick an outbound tunnel bound to the given destination */
/**
* Pick a random outbound tunnel from the given destination's pool
*
* @param destination if null, returns outbound exploratory tunnel
* @return null if none
*/
TunnelInfo selectOutboundTunnel(Hash destination);
/**
* Pick the inbound exploratory tunnel with the gateway closest to the given hash.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectInboundExploratoryTunnel(Hash closestTo);
/**
* Pick the inbound tunnel with the gateway closest to the given hash
* from the given destination's pool.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param destination if null, returns inbound exploratory tunnel
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectInboundTunnel(Hash destination, Hash closestTo);
/**
* Pick the outbound exploratory tunnel with the endpoint closest to the given hash.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectOutboundExploratoryTunnel(Hash closestTo);
/**
* Pick the outbound tunnel with the endpoint closest to the given hash
* from the given destination's pool.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param destination if null, returns outbound exploratory tunnel
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectOutboundTunnel(Hash destination, Hash closestTo);
/** Is a tunnel a valid member of the pool? */
public boolean isValidTunnel(Hash client, TunnelInfo tunnel);

View File

@@ -835,13 +835,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* Pick an arbitrary outbound tunnel to send the message through, or null if
* there aren't any around
*
* TODO - rather than pick one at random, pick the "closest" to the lease,
* Rather than pick one at random, pick the "closest" to the lease,
* to minimize network OBEP - IBGW connections?
* This would also eliminate a connection when OBEP == IBGW.
* Anonymity issues?
*/
private TunnelInfo selectOutboundTunnel() {
return getContext().tunnelManager().selectOutboundTunnel(_from.calculateHash());
Hash gw = _lease.getGateway();
return getContext().tunnelManager().selectOutboundTunnel(_from.calculateHash(), gw);
}
/**

View File

@@ -53,7 +53,6 @@ class ExploreJob extends SearchJob {
// if this collides with an actual leaseSet's key, neat, but that wouldn't imply we're actually
// attempting to send that lease a message!
super(context, facade, key, null, null, MAX_EXPLORE_TIME, false, false);
_log = context.logManager().getLog(ExploreJob.class);
_peerSelector = (FloodfillPeerSelector) (_facade.getPeerSelector());
}

View File

@@ -19,7 +19,7 @@ import net.i2p.router.TunnelInfo;
import net.i2p.util.Log;
/**
* send a netDb lookup to a random floodfill peer - if it is found, great,
* Send a netDb lookup to a floodfill peer - If it is found, great,
* but if they reply back saying they dont know it, queue up a store of the
* key to a random floodfill peer again (via FloodfillStoreJob)
*
@@ -92,9 +92,9 @@ public class FloodfillVerifyStoreJob extends JobImpl {
// Unless it is an encrypted leaseset.
TunnelInfo outTunnel;
if (_isRouterInfo || getContext().keyRing().get(_key) != null)
outTunnel = getContext().tunnelManager().selectOutboundTunnel();
outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(_target);
else
outTunnel = getContext().tunnelManager().selectOutboundTunnel(_key);
outTunnel = getContext().tunnelManager().selectOutboundTunnel(_key, _target);
if (outTunnel == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No outbound tunnels to verify a store");
@@ -153,9 +153,9 @@ public class FloodfillVerifyStoreJob extends JobImpl {
// Unless it is an encrypted leaseset.
TunnelInfo replyTunnelInfo;
if (_isRouterInfo || getContext().keyRing().get(_key) != null)
replyTunnelInfo = getContext().tunnelManager().selectInboundTunnel();
replyTunnelInfo = getContext().tunnelManager().selectInboundExploratoryTunnel(_target);
else
replyTunnelInfo = getContext().tunnelManager().selectInboundTunnel(_key);
replyTunnelInfo = getContext().tunnelManager().selectInboundTunnel(_key, _target);
if (replyTunnelInfo == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No inbound tunnels to get a reply from!");

View File

@@ -191,8 +191,8 @@ class IterativeSearchJob extends FloodSearchJob {
*/
private void sendQuery(Hash peer) {
DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true);
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundExploratoryTunnel(peer);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(peer);
if ( (replyTunnel == null) || (outTunnel == null) ) {
failed();
return;

View File

@@ -38,17 +38,17 @@ import net.i2p.util.Log;
* It also does not update peer profile stats.
*/
class SearchJob extends JobImpl {
protected Log _log;
protected KademliaNetworkDatabaseFacade _facade;
private SearchState _state;
private Job _onSuccess;
private Job _onFailure;
private long _expiration;
private long _timeoutMs;
private boolean _keepStats;
private boolean _isLease;
protected final Log _log;
protected final KademliaNetworkDatabaseFacade _facade;
private final SearchState _state;
private final Job _onSuccess;
private final Job _onFailure;
private final long _expiration;
private final long _timeoutMs;
private final boolean _keepStats;
private final boolean _isLease;
private Job _pendingRequeueJob;
private PeerSelector _peerSelector;
private final PeerSelector _peerSelector;
private final List _deferredSearches;
private boolean _deferredCleared;
private long _startedOn;
@@ -89,7 +89,7 @@ class SearchJob extends JobImpl {
super(context);
if ( (key == null) || (key.getData() == null) )
throw new IllegalArgumentException("Search for null key? wtf");
_log = getContext().logManager().getLog(SearchJob.class);
_log = getContext().logManager().getLog(getClass());
_facade = facade;
_state = new SearchState(getContext(), key);
_onSuccess = onSuccess;
@@ -98,11 +98,8 @@ class SearchJob extends JobImpl {
_keepStats = keepStats;
_isLease = isLease;
_deferredSearches = new ArrayList(0);
_deferredCleared = false;
_peerSelector = facade.getPeerSelector();
_startedOn = -1;
_floodfillPeersExhausted = false;
_floodfillSearchesOutstanding = 0;
_expiration = getContext().clock().now() + timeoutMs;
getContext().statManager().addRateData("netDb.searchCount", 1, 0);
if (_log.shouldLog(Log.DEBUG))
@@ -405,7 +402,8 @@ class SearchJob extends JobImpl {
*
*/
protected void sendLeaseSearch(RouterInfo router) {
TunnelInfo inTunnel = getInboundTunnelId();
Hash to = router.getIdentity().getHash();
TunnelInfo inTunnel = getContext().tunnelManager().selectInboundExploratoryTunnel(to);
if (inTunnel == null) {
_log.warn("No tunnels to get search replies through! wtf!");
getContext().jobQueue().addJob(new FailedJob(getContext(), router));
@@ -423,12 +421,12 @@ class SearchJob extends JobImpl {
// return;
//}
int timeout = getPerPeerTimeoutMs(router.getIdentity().getHash());
int timeout = getPerPeerTimeoutMs(to);
long expiration = getContext().clock().now() + timeout;
DatabaseLookupMessage msg = buildMessage(inTunnelId, inTunnel.getPeer(0), expiration);
TunnelInfo outTunnel = getOutboundTunnelId();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(to);
if (outTunnel == null) {
_log.warn("No tunnels to send search out through! wtf!");
getContext().jobQueue().addJob(new FailedJob(getContext(), router));
@@ -438,7 +436,7 @@ class SearchJob extends JobImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Sending search to " + router.getIdentity().getHash().toBase64()
_log.debug(getJobId() + ": Sending search to " + to
+ " for " + msg.getSearchKey().toBase64() + " w/ replies through ["
+ msg.getFrom().toBase64() + "] via tunnel ["
+ msg.getReplyTunnel() + "]");
@@ -450,7 +448,7 @@ class SearchJob extends JobImpl {
if (FloodfillNetworkDatabaseFacade.isFloodfill(router))
_floodfillSearchesOutstanding++;
getContext().messageRegistry().registerPending(sel, reply, new FailedJob(getContext(), router), timeout);
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnelId, router.getIdentity().getHash());
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnelId, to);
}
/** we're searching for a router, so we can just send direct */
@@ -476,23 +474,6 @@ class SearchJob extends JobImpl {
}
**********/
/**
* what tunnel will we send the search out through?
*
* @return tunnel id (or null if none are found)
*/
private TunnelInfo getOutboundTunnelId() {
return getContext().tunnelManager().selectOutboundTunnel();
}
/**
* what tunnel will we get replies through?
*
* @return tunnel id (or null if none are found)
*/
private TunnelInfo getInboundTunnelId() {
return getContext().tunnelManager().selectInboundTunnel();
}
/**
* Build the database search message
@@ -664,19 +645,20 @@ class SearchJob extends JobImpl {
* provide us with the data when we asked them.
*/
private boolean resend(RouterInfo toPeer, LeaseSet ls) {
Hash to = toPeer.getIdentity().getHash();
DatabaseStoreMessage msg = new DatabaseStoreMessage(getContext());
msg.setEntry(ls);
msg.setMessageExpiration(getContext().clock().now() + RESEND_TIMEOUT);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(to);
if (outTunnel != null) {
TunnelId targetTunnelId = null; // not needed
Job onSend = null; // not wanted
if (_log.shouldLog(Log.DEBUG))
_log.debug("resending leaseSet out to " + toPeer.getIdentity().getHash() + " through " + outTunnel + ": " + msg);
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, toPeer.getIdentity().getHash());
_log.debug("resending leaseSet out to " + to + " through " + outTunnel + ": " + msg);
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, to);
return true;
} else {
if (_log.shouldLog(Log.WARN))

View File

@@ -36,8 +36,8 @@ class SingleSearchJob extends FloodOnlySearchJob {
public void runJob() {
_onm = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, _timeoutMs);
DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true);
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundExploratoryTunnel(_to);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(_to);
if ( (replyTunnel == null) || (outTunnel == null) ) {
failed();
return;

View File

@@ -32,15 +32,18 @@ import net.i2p.stat.RateStat;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
/**
* Unused directly - see FloodfillStoreJob
*/
class StoreJob extends JobImpl {
protected Log _log;
protected final Log _log;
private KademliaNetworkDatabaseFacade _facade;
protected StoreState _state;
private Job _onSuccess;
private Job _onFailure;
protected final StoreState _state;
private final Job _onSuccess;
private final Job _onFailure;
private long _timeoutMs;
private long _expiration;
private PeerSelector _peerSelector;
private final long _expiration;
private final PeerSelector _peerSelector;
private final static int PARALLELIZATION = 4; // how many sent at a time
private final static int REDUNDANCY = 4; // we want the data sent to 6 peers
@@ -308,7 +311,7 @@ class StoreJob extends JobImpl {
*
*/
private void sendDirect(DatabaseStoreMessage msg, RouterInfo peer, long expiration) {
long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
long token = 1 + getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
msg.setReplyToken(token);
msg.setReplyGateway(getContext().routerHash());
@@ -343,9 +346,10 @@ class StoreJob extends JobImpl {
*
*/
private void sendStoreThroughGarlic(DatabaseStoreMessage msg, RouterInfo peer, long expiration) {
long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
long token = 1 + getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
TunnelInfo replyTunnel = selectInboundTunnel();
Hash to = peer.getIdentity().getHash();
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundExploratoryTunnel(to);
if (replyTunnel == null) {
_log.warn("No reply inbound tunnels available!");
return;
@@ -358,9 +362,9 @@ class StoreJob extends JobImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send(dbStore) w/ token expected " + token);
_state.addPending(peer.getIdentity().getHash());
_state.addPending(to);
TunnelInfo outTunnel = selectOutboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(to);
if (outTunnel != null) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(getJobId() + ": Sending tunnel message out " + outTunnelId + " to "
@@ -375,7 +379,7 @@ class StoreJob extends JobImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending store to " + peer.getIdentity().getHash() + " through " + outTunnel + ": " + msg);
getContext().messageRegistry().registerPending(selector, onReply, onFail, (int)(expiration - getContext().clock().now()));
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, peer.getIdentity().getHash());
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, to);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No outbound tunnels to send a dbStore out!");
@@ -383,14 +387,6 @@ class StoreJob extends JobImpl {
}
}
private TunnelInfo selectOutboundTunnel() {
return getContext().tunnelManager().selectOutboundTunnel();
}
private TunnelInfo selectInboundTunnel() {
return getContext().tunnelManager().selectInboundTunnel();
}
/**
* Send a leaseset store message out the client tunnel,
* with the reply to come back through a client tunnel.
@@ -408,10 +404,11 @@ class StoreJob extends JobImpl {
* @since 0.7.10
*/
private void sendStoreThroughClient(DatabaseStoreMessage msg, RouterInfo peer, long expiration) {
long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
long token = 1 + getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
Hash client = msg.getKey();
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel(client);
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!");
@@ -426,8 +423,7 @@ class StoreJob extends JobImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send(dbStore) w/ token expected " + token);
Hash to = peer.getIdentity().getHash();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel(client);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel(client, to);
if (outTunnel != null) {
I2NPMessage sent;
boolean shouldEncrypt = supportsEncryption(peer);

View File

@@ -55,6 +55,7 @@ abstract class BuildRequestor {
* to send an inbound build request.
* This is more secure than using the router's exploratory tunnels, as it
* makes correlation of multiple clients more difficult.
* @return true always
*/
private static boolean usePairedTunnels(RouterContext ctx) {
return true;
@@ -100,25 +101,26 @@ abstract class BuildRequestor {
cfg.setTunnelPool(pool);
TunnelInfo pairedTunnel = null;
Hash farEnd = cfg.getFarEnd();
if (pool.getSettings().isExploratory() || !usePairedTunnels(ctx)) {
if (pool.getSettings().isInbound())
pairedTunnel = ctx.tunnelManager().selectOutboundTunnel();
pairedTunnel = ctx.tunnelManager().selectOutboundExploratoryTunnel(farEnd);
else
pairedTunnel = ctx.tunnelManager().selectInboundTunnel();
pairedTunnel = ctx.tunnelManager().selectInboundExploratoryTunnel(farEnd);
} else {
if (pool.getSettings().isInbound())
pairedTunnel = ctx.tunnelManager().selectOutboundTunnel(pool.getSettings().getDestination());
pairedTunnel = ctx.tunnelManager().selectOutboundTunnel(pool.getSettings().getDestination(), farEnd);
else
pairedTunnel = ctx.tunnelManager().selectInboundTunnel(pool.getSettings().getDestination());
pairedTunnel = ctx.tunnelManager().selectInboundTunnel(pool.getSettings().getDestination(), farEnd);
}
if (pairedTunnel == null) {
if (log.shouldLog(Log.WARN))
log.warn("Couldn't find a paired tunnel for " + cfg + ", fall back on exploratory tunnels for pairing");
if (!pool.getSettings().isExploratory() && usePairedTunnels(ctx))
if (pool.getSettings().isInbound())
pairedTunnel = ctx.tunnelManager().selectOutboundTunnel();
pairedTunnel = ctx.tunnelManager().selectOutboundExploratoryTunnel(farEnd);
else
pairedTunnel = ctx.tunnelManager().selectInboundTunnel();
pairedTunnel = ctx.tunnelManager().selectInboundExploratoryTunnel(farEnd);
}
if (pairedTunnel == null) {
if (log.shouldLog(Log.ERROR))

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Properties;
import java.util.TreeSet;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
@@ -58,6 +59,8 @@ public class TunnelPool {
(_settings.isExploratory() ? "exploratory" : _settings.getDestinationNickname()) +
(_settings.isInbound() ? ".in" : ".out");
refreshSettings();
ctx.statManager().createRateStat("tunnel.matchLease", "How often does our OBEP match their IBGW?", "Tunnels",
new long[] {60*60*1000});
}
/**
@@ -143,6 +146,7 @@ public class TunnelPool {
* the pool is configured to allow 0hop tunnels, this builds a fake one
* and returns it.
*
* @return null on failure, but it should always build and return a fallback
*/
TunnelInfo selectTunnel() { return selectTunnel(true); }
@@ -221,6 +225,41 @@ public class TunnelPool {
return null;
}
/**
* 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() + getSettings().getLengthVariance()) > 0);
TunnelInfo rv = null;
synchronized (_tunnels) {
if (!_tunnels.isEmpty()) {
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) ? 1 : 0);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": No tunnels to select from");
}
return rv;
}
public TunnelInfo getTunnel(TunnelId gatewayId) {
synchronized (_tunnels) {
for (int i = 0; i < _tunnels.size(); i++) {
@@ -495,6 +534,44 @@ public class TunnelPool {
}
}
/**
* Find the tunnel with the far-end that is XOR-closest to a given hash
*
* @since 0.8.10
*/
private static class TunnelInfoComparator implements Comparator<TunnelInfo> {
private final byte[] _base;
private final boolean _avoidZero;
/**
* @param target key to compare distances with
* @param avoidZeroHop if true, zero-hop tunnels will be put last
*/
public TunnelInfoComparator(Hash target, boolean avoidZeroHop) {
_base = target.getData();
_avoidZero = avoidZeroHop;
}
public int compare(TunnelInfo lhs, TunnelInfo rhs) {
if (_avoidZero) {
// put the zero-hops last
int llen = lhs.getLength();
int rlen = rhs.getLength();
if (llen > 1 && rlen <= 1)
return -1;
if (rlen > 1 && llen <= 1)
return 1;
}
byte lhsDelta[] = DataHelper.xor(lhs.getFarEnd().getData(), _base);
byte rhsDelta[] = DataHelper.xor(rhs.getFarEnd().getData(), _base);
int rv = DataHelper.compareTo(lhsDelta, rhsDelta);
if (rv != 0)
return rv;
// latest-expiring first as a tie-breaker
return (int) (rhs.getExpiration() - lhs.getExpiration());
}
}
/**
* Build a leaseSet with the required tunnels that aren't about to expire.
* Caller must synchronize on _tunnels.

View File

@@ -84,7 +84,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
RATES);
}
/** pick an inbound tunnel not bound to a particular destination */
/**
* Pick a random inbound exploratory tunnel
*
* @return null if none
*/
public TunnelInfo selectInboundTunnel() {
TunnelPool pool = _inboundExploratory;
if (pool == null) return null;
@@ -97,7 +101,12 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return info;
}
/** pick an inbound tunnel bound to the given destination */
/**
* Pick a random inbound tunnel from the given destination's pool
*
* @param destination if null, returns inbound exploratory tunnel
* @return null if none
*/
public TunnelInfo selectInboundTunnel(Hash destination) {
if (destination == null) return selectInboundTunnel();
TunnelPool pool = _clientInboundPools.get(destination);
@@ -105,12 +114,16 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return pool.selectTunnel();
}
if (_log.shouldLog(Log.ERROR))
_log.error("Want the inbound tunnel for " + destination.calculateHash().toBase64() +
_log.error("Want the inbound tunnel for " + destination.calculateHash() +
" but there isn't a pool?");
return null;
}
/** pick an outbound tunnel not bound to a particular destination */
/**
* Pick a random outbound exploratory tunnel
*
* @return null if none
*/
public TunnelInfo selectOutboundTunnel() {
TunnelPool pool = _outboundExploratory;
if (pool == null) return null;
@@ -123,7 +136,12 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return info;
}
/** pick an outbound tunnel bound to the given destination */
/**
* Pick a random outbound tunnel from the given destination's pool
*
* @param destination if null, returns outbound exploratory tunnel
* @return null if none
*/
public TunnelInfo selectOutboundTunnel(Hash destination) {
if (destination == null) return selectOutboundTunnel();
TunnelPool pool = _clientOutboundPools.get(destination);
@@ -133,6 +151,95 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return null;
}
/**
* Pick the inbound exploratory tunnel with the gateway closest to the given hash.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectInboundExploratoryTunnel(Hash closestTo) {
TunnelPool pool = _inboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
if (info == null) {
_inboundExploratory.buildFallback();
// still can be null, but probably not
info = _inboundExploratory.selectTunnel(closestTo);
}
return info;
}
/**
* Pick the inbound tunnel with the gateway closest to the given hash
* from the given destination's pool.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param destination if null, returns inbound exploratory tunnel
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectInboundTunnel(Hash destination, Hash closestTo) {
if (destination == null) return selectInboundExploratoryTunnel(closestTo);
TunnelPool pool = _clientInboundPools.get(destination);
if (pool != null) {
return pool.selectTunnel(closestTo);
}
if (_log.shouldLog(Log.ERROR))
_log.error("Want the inbound tunnel for " + destination.calculateHash() +
" but there isn't a pool?");
return null;
}
/**
* Pick the outbound exploratory tunnel with the endpoint closest to the given hash.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectOutboundExploratoryTunnel(Hash closestTo) {
TunnelPool pool = _outboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
if (info == null) {
pool.buildFallback();
// still can be null, but probably not
info = pool.selectTunnel(closestTo);
}
return info;
}
/**
* Pick the outbound tunnel with the endpoint closest to the given hash
* from the given destination's pool.
* By using this instead of the random selectTunnel(),
* we force some locality in OBEP-IBGW connections to minimize
* those connections network-wide.
*
* @param destination if null, returns outbound exploratory tunnel
* @param closestTo non-null
* @return null if none
* @since 0.8.10
*/
public TunnelInfo selectOutboundTunnel(Hash destination, Hash closestTo) {
if (destination == null) return selectOutboundExploratoryTunnel(closestTo);
TunnelPool pool = _clientOutboundPools.get(destination);
if (pool != null) {
return pool.selectTunnel(closestTo);
}
return null;
}
public TunnelInfo getTunnelInfo(TunnelId id) {
TunnelInfo info = null;
for (TunnelPool pool : _clientInboundPools.values()) {
@@ -151,12 +258,15 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return null;
}
/** @return number of inbound exploratory tunnels */
public int getFreeTunnelCount() {
if (_inboundExploratory == null)
return 0;
else
return _inboundExploratory.size();
}
/** @return number of outbound exploratory tunnels */
public int getOutboundTunnelCount() {
if (_outboundExploratory == null)
return 0;
@@ -355,7 +465,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
void buildComplete(PooledTunnelCreatorConfig cfg) {
if (cfg.getLength() > 1 &&
(!_context.router().gracefulShutdownInProgress()) &&
!Boolean.valueOf(_context.getProperty("router.disableTunnelTesting")).booleanValue()) {
!_context.getBooleanPropertyDefaultTrue("router.disableTunnelTesting")) {
TunnelPool pool = cfg.getTunnelPool();
if (pool == null) {
// never seen this before, do we reallly need to bother