* Use partitions of fast tier for various hops of client tunnels; minor cleanups

This commit is contained in:
zzz
2011-05-11 14:40:55 +00:00
parent 5ce06d02b4
commit f9654661bb
5 changed files with 177 additions and 28 deletions

View File

@ -1,3 +1,6 @@
2011-05-11 zzz
* Use partitions of fast tier for various hops of client tunnels
2011-05-06 zzz 2011-05-06 zzz
* Tunnels and profiles: * Tunnels and profiles:
- Increase max fast and high-cap tier sizes - Increase max fast and high-cap tier sizes

View File

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

View File

@ -2,7 +2,6 @@ package net.i2p.router.peermanager;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Writer;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@ -22,6 +21,7 @@ import java.util.TreeSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.RouterAddress; import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo; import net.i2p.data.RouterInfo;
@ -78,7 +78,7 @@ public class ProfileOrganizer {
public static final String PROP_MINIMUM_FAST_PEERS = "profileOrganizer.minFastPeers"; public static final String PROP_MINIMUM_FAST_PEERS = "profileOrganizer.minFastPeers";
public static final int DEFAULT_MINIMUM_FAST_PEERS = 8; public static final int DEFAULT_MINIMUM_FAST_PEERS = 8;
/** this is misnamed, it is really the max minimum number. */ /** this is misnamed, it is really the max minimum number. */
private static final int DEFAULT_MAXIMUM_FAST_PEERS = 16; private static final int DEFAULT_MAXIMUM_FAST_PEERS = 18;
private static final int ABSOLUTE_MAX_FAST_PEERS = 60; private static final int ABSOLUTE_MAX_FAST_PEERS = 60;
/** /**
@ -94,9 +94,6 @@ public class ProfileOrganizer {
/** synchronized against this lock when updating the tier that peers are located in (and when fetching them from a peer) */ /** synchronized against this lock when updating the tier that peers are located in (and when fetching them from a peer) */
private final ReentrantReadWriteLock _reorganizeLock = new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock _reorganizeLock = new ReentrantReadWriteLock(true);
/** incredibly weak PRNG, just used for shuffling peers. no need to waste the real PRNG on this */
private Random _random = new Random();
public ProfileOrganizer(RouterContext context) { public ProfileOrganizer(RouterContext context) {
_context = context; _context = context;
_log = context.logManager().getLog(ProfileOrganizer.class); _log = context.logManager().getLog(ProfileOrganizer.class);
@ -108,9 +105,6 @@ public class ProfileOrganizer {
_notFailingPeersList = new ArrayList(256); _notFailingPeersList = new ArrayList(256);
_failingPeers = new HashMap(16); _failingPeers = new HashMap(16);
_strictCapacityOrder = new TreeSet(_comp); _strictCapacityOrder = new TreeSet(_comp);
_thresholdSpeedValue = 0.0d;
_thresholdCapacityValue = 0.0d;
_thresholdIntegrationValue = 0.0d;
_persistenceHelper = new ProfilePersistenceHelper(_context); _persistenceHelper = new ProfilePersistenceHelper(_context);
_context.statManager().createRateStat("peer.profileSortTime", "How long the reorg takes sorting peers", "Peers", new long[] { 10*60*1000 }); _context.statManager().createRateStat("peer.profileSortTime", "How long the reorg takes sorting peers", "Peers", new long[] { 10*60*1000 });
@ -279,14 +273,25 @@ public class ProfileOrganizer {
* @param howMany how many peers are desired * @param howMany how many peers are desired
* @param exclude set of Hashes for routers that we don't want selected * @param exclude set of Hashes for routers that we don't want selected
* @param matches set to store the return value in * @param matches set to store the return value in
* @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should
* not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match
* *
*/ */
public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches) { public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches) {
selectFastPeers(howMany, exclude, matches, 0); selectFastPeers(howMany, exclude, matches, 0);
} }
/**
* Return a set of Hashes for peers that are both fast and reliable. If an insufficient
* number of peers are both fast and reliable, fall back onto high capacity peers, and if that
* doesn't contain sufficient peers, fall back onto not failing peers, and even THAT doesn't
* have sufficient peers, fall back onto failing peers.
*
* @param howMany how many peers are desired
* @param exclude set of Hashes for routers that we don't want selected
* @param matches set to store the return value in
* @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should
* not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match
*
*/
public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask) { public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask) {
getReadLock(); getReadLock();
try { try {
@ -303,6 +308,51 @@ public class ProfileOrganizer {
return; return;
} }
/**
* Return a set of Hashes for peers that are both fast and reliable. If an insufficient
* number of peers are both fast and reliable, fall back onto high capacity peers, and if that
* doesn't contain sufficient peers, fall back onto not failing peers, and even THAT doesn't
* have sufficient peers, fall back onto failing peers.
*
* @param howMany how many peers are desired
* @param exclude set of Hashes for routers that we don't want selected
* @param matches set to store the return value in
* @param randomKey used for deterministic random partitioning into subtiers
* @param subTierMode 0 or 2-7:
*<pre>
* 0: no partitioning, use entire tier
* 2: return only from group 0 or 1
* 3: return only from group 2 or 3
* 4: return only from group 0
* 5: return only from group 1
* 6: return only from group 2
* 7: return only from group 3
*</pre>
*/
public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, Hash randomKey, int subTierMode) {
getReadLock();
try {
if (subTierMode > 0) {
int sz = _fastPeers.size();
if (sz < 6 || (subTierMode >= 4 && sz < 12))
subTierMode = 0;
}
if (subTierMode > 0)
locked_selectPeers(_fastPeers, howMany, exclude, matches, randomKey, subTierMode);
else
locked_selectPeers(_fastPeers, howMany, exclude, matches, 2);
} finally { releaseReadLock(); }
if (matches.size() < howMany) {
if (_log.shouldLog(Log.INFO))
_log.info("selectFastPeers("+howMany+"), not enough fast (" + matches.size() + ") going on to highCap");
selectHighCapacityPeers(howMany, exclude, matches, 2);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("selectFastPeers("+howMany+"), found enough fast (" + matches.size() + ")");
}
return;
}
/** /**
* Return a set of Hashes for peers that have a high capacity * Return a set of Hashes for peers that have a high capacity
* *
@ -343,13 +393,17 @@ public class ProfileOrganizer {
/** /**
* Return a set of Hashes for peers that are well integrated into the network. * Return a set of Hashes for peers that are well integrated into the network.
* *
* @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should
* not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match
*/ */
public void selectWellIntegratedPeers(int howMany, Set<Hash> exclude, Set<Hash> matches) { public void selectWellIntegratedPeers(int howMany, Set<Hash> exclude, Set<Hash> matches) {
selectWellIntegratedPeers(howMany, exclude, matches, 0); selectWellIntegratedPeers(howMany, exclude, matches, 0);
} }
/**
* Return a set of Hashes for peers that are well integrated into the network.
*
* @param mask 0-4 Number of bytes to match to determine if peers in the same IP range should
* not be in the same tunnel. 0 = disable check; 1 = /8; 2 = /16; 3 = /24; 4 = exact IP match
*/
public void selectWellIntegratedPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask) { public void selectWellIntegratedPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask) {
getReadLock(); getReadLock();
try { try {
@ -1063,10 +1117,10 @@ public class ProfileOrganizer {
/** called after locking the reorganizeLock */ /** called after locking the reorganizeLock */
private PeerProfile locked_getProfile(Hash peer) { private PeerProfile locked_getProfile(Hash peer) {
PeerProfile cur = (PeerProfile)_notFailingPeers.get(peer); PeerProfile cur = _notFailingPeers.get(peer);
if (cur != null) if (cur != null)
return cur; return cur;
cur = (PeerProfile)_failingPeers.get(peer); cur = _failingPeers.get(peer);
return cur; return cur;
} }
@ -1158,7 +1212,7 @@ public class ProfileOrganizer {
} }
/** generate an arbitrary unique value for this ip/mask (mask = 1-4) */ /** generate an arbitrary unique value for this ip/mask (mask = 1-4) */
private Integer maskedIP(byte[] ip, int mask) { private static Integer maskedIP(byte[] ip, int mask) {
int rv = 0; int rv = 0;
for (int i = 0; i < mask; i++) for (int i = 0; i < mask; i++)
rv = (rv << 8) | (ip[i] & 0xff); rv = (rv << 8) | (ip[i] & 0xff);
@ -1166,7 +1220,7 @@ public class ProfileOrganizer {
} }
/** does a contain any of the elements in b? */ /** does a contain any of the elements in b? */
private boolean containsAny(Set a, Set b) { private static boolean containsAny(Set a, Set b) {
for (Object o : b) { for (Object o : b) {
if (a.contains(o)) if (a.contains(o))
return true; return true;
@ -1174,6 +1228,58 @@ public class ProfileOrganizer {
return false; return false;
} }
/**
* @param randomKey used for deterministic random partitioning into subtiers
* @param subTierMode 2-7:
*<pre>
* 2: return only from group 0 or 1
* 3: return only from group 2 or 3
* 4: return only from group 0
* 5: return only from group 1
* 6: return only from group 2
* 7: return only from group 3
*</pre>
*/
private void locked_selectPeers(Map<Hash, PeerProfile> peers, int howMany, Set<Hash> toExclude, Set<Hash> matches, Hash randomKey, int subTierMode) {
List<Hash> all = new ArrayList(peers.keySet());
// use RandomIterator to avoid shuffling the whole thing
for (Iterator<Hash> iter = new RandomIterator(all); (matches.size() < howMany) && iter.hasNext(); ) {
Hash peer = iter.next();
if (toExclude != null && toExclude.contains(peer))
continue;
if (matches.contains(peer))
continue;
if (_us.equals(peer))
continue;
int subTier = getSubTier(peer, randomKey);
if (subTierMode >= 4) {
if (subTier != (subTierMode & 0x03))
continue;
} else {
if ((subTier >> 1) != (subTierMode & 0x01))
continue;
}
boolean ok = isSelectable(peer);
if (ok)
matches.add(peer);
else
matches.remove(peer);
}
}
/**
* Implement a random, deterministic split into 4 groups that cannot be predicted by
* others.
* @return 0-3
*/
private static int getSubTier(Hash peer, Hash randomKey) {
byte[] data = new byte[Hash.HASH_LENGTH + 4];
System.arraycopy(peer.getData(), 0, data, 0, Hash.HASH_LENGTH);
System.arraycopy(randomKey.getData(), 0, data, Hash.HASH_LENGTH, 4);
Hash rh = SHA256Generator.getInstance().calculateHash(data);
return rh.getData()[0] & 0x03;
}
public boolean isSelectable(Hash peer) { public boolean isSelectable(Hash peer) {
NetworkDatabaseFacade netDb = _context.netDb(); NetworkDatabaseFacade netDb = _context.netDb();
// the CLI shouldn't depend upon the netDb // the CLI shouldn't depend upon the netDb
@ -1288,7 +1394,7 @@ public class ProfileOrganizer {
*/ */
protected int getMinimumFastPeers() { protected int getMinimumFastPeers() {
int def = Math.min(DEFAULT_MAXIMUM_FAST_PEERS, int def = Math.min(DEFAULT_MAXIMUM_FAST_PEERS,
(2 *_context.clientManager().listClients().size()) + DEFAULT_MINIMUM_FAST_PEERS - 2); (6 *_context.clientManager().listClients().size()) + DEFAULT_MINIMUM_FAST_PEERS - 2);
return _context.getProperty(PROP_MINIMUM_FAST_PEERS, def); return _context.getProperty(PROP_MINIMUM_FAST_PEERS, def);
} }

View File

@ -21,20 +21,57 @@ class ClientPeerSelector extends TunnelPeerSelector {
return null; return null;
if ( (length == 0) && (settings.getLength()+settings.getLengthVariance() > 0) ) if ( (length == 0) && (settings.getLength()+settings.getLengthVariance() > 0) )
return null; return null;
HashSet matches = new HashSet(length);
List<Hash> rv;
if (length > 0) { if (length > 0) {
if (shouldSelectExplicit(settings)) if (shouldSelectExplicit(settings))
return selectExplicit(ctx, settings, length); return selectExplicit(ctx, settings, length);
Set<Hash> exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory());
Set<Hash> matches = new HashSet(length);
if (length == 1) {
ctx.profileOrganizer().selectFastPeers(length, exclude, matches, 0);
matches.remove(ctx.routerHash());
rv = new ArrayList(matches);
} else {
// build a tunnel using 4 subtiers.
// For a 2-hop tunnel, the first hop comes from subtiers 0-1 and the last from subtiers 2-3.
// For a longer tunnels, the first hop comes from subtier 0, the middle from subtiers 2-3, and the last from subtier 1.
rv = new ArrayList(length + 1);
// OBEP or IB last hop
// group 0 or 1 if two hops, otherwise group 0
ctx.profileOrganizer().selectFastPeers(1, exclude, matches, settings.getRandomKey(), length == 2 ? 2 : 4);
matches.remove(ctx.routerHash());
exclude.addAll(matches);
rv.addAll(matches);
matches.clear();
if (length > 2) {
// middle hop(s)
// group 2 or 3
ctx.profileOrganizer().selectFastPeers(length - 2, exclude, matches, settings.getRandomKey(), 3);
matches.remove(ctx.routerHash());
if (matches.size() > 1) {
// order the middle peers for tunnels >= 4 hops
List<Hash> ordered = new ArrayList(matches);
orderPeers(ordered, settings.getRandomKey());
rv.addAll(ordered);
} else {
rv.addAll(matches);
}
exclude.addAll(matches);
matches.clear();
}
// IBGW or OB first hop
// group 2 or 3 if two hops, otherwise group 1
ctx.profileOrganizer().selectFastPeers(1, exclude, matches, settings.getRandomKey(), length == 2 ? 3 : 5);
matches.remove(ctx.routerHash());
rv.addAll(matches);
}
} else {
rv = new ArrayList(1);
} }
Set exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory());
ctx.profileOrganizer().selectFastPeers(length, exclude, matches, settings.getIPRestriction());
matches.remove(ctx.routerHash());
ArrayList<Hash> rv = new ArrayList(matches);
if (rv.size() > 1)
orderPeers(rv, settings.getRandomKey());
if (settings.isInbound()) if (settings.isInbound())
rv.add(0, ctx.routerHash()); rv.add(0, ctx.routerHash());
else else

View File

@ -41,6 +41,9 @@ public abstract class TunnelPeerSelector {
*/ */
public abstract List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings); public abstract List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings);
/**
* @return randomized number of hops 0-7, not including ourselves
*/
protected int getLength(RouterContext ctx, TunnelPoolSettings settings) { protected int getLength(RouterContext ctx, TunnelPoolSettings settings) {
int length = settings.getLength(); int length = settings.getLength();
int override = settings.getLengthOverride(); int override = settings.getLengthOverride();
@ -61,8 +64,8 @@ public abstract class TunnelPeerSelector {
} }
if (length < 0) if (length < 0)
length = 0; length = 0;
else if (length > 8) // as documented in tunnel.html else if (length > 7) // as documented in tunnel.html
length = 8; length = 7;
/* /*
if ( (ctx.tunnelManager().getOutboundTunnelCount() <= 0) || if ( (ctx.tunnelManager().getOutboundTunnelCount() <= 0) ||
(ctx.tunnelManager().getFreeTunnelCount() <= 0) ) { (ctx.tunnelManager().getFreeTunnelCount() <= 0) ) {