Skip to content

Commit

Permalink
* Use partitions of fast tier for various hops of client tunnels; mi…
Browse files Browse the repository at this point in the history
…nor cleanups
  • Loading branch information
zzz committed May 11, 2011
1 parent 5ce06d0 commit f965466
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 28 deletions.
3 changes: 3 additions & 0 deletions history.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
2011-05-11 zzz
* Use partitions of fast tier for various hops of client tunnels

2011-05-06 zzz
* Tunnels and profiles:
- Increase max fast and high-cap tier sizes
Expand Down
2 changes: 1 addition & 1 deletion router/java/src/net/i2p/router/RouterVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
Expand Down
140 changes: 123 additions & 17 deletions router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
Expand All @@ -22,6 +21,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.i2p.crypto.SHA256Generator;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
Expand Down Expand Up @@ -78,7 +78,7 @@ public class ProfileOrganizer {
public static final String PROP_MINIMUM_FAST_PEERS = "profileOrganizer.minFastPeers";
public static final int DEFAULT_MINIMUM_FAST_PEERS = 8;
/** 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;

/**
Expand All @@ -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) */
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) {
_context = context;
_log = context.logManager().getLog(ProfileOrganizer.class);
Expand All @@ -108,9 +105,6 @@ public ProfileOrganizer(RouterContext context) {
_notFailingPeersList = new ArrayList(256);
_failingPeers = new HashMap(16);
_strictCapacityOrder = new TreeSet(_comp);
_thresholdSpeedValue = 0.0d;
_thresholdCapacityValue = 0.0d;
_thresholdIntegrationValue = 0.0d;
_persistenceHelper = new ProfilePersistenceHelper(_context);

_context.statManager().createRateStat("peer.profileSortTime", "How long the reorg takes sorting peers", "Peers", new long[] { 10*60*1000 });
Expand Down Expand Up @@ -279,14 +273,25 @@ public boolean exportProfile(Hash profile, OutputStream out) throws IOException
* @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) {
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) {
getReadLock();
try {
Expand All @@ -303,6 +308,51 @@ public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, i
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
*
Expand Down Expand Up @@ -343,13 +393,17 @@ public void selectHighCapacityPeers(int howMany, Set<Hash> exclude, Set<Hash> ma
/**
* 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) {
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) {
getReadLock();
try {
Expand Down Expand Up @@ -1063,10 +1117,10 @@ private final static double avg(double total, double quantity) {

/** called after locking the reorganizeLock */
private PeerProfile locked_getProfile(Hash peer) {
PeerProfile cur = (PeerProfile)_notFailingPeers.get(peer);
PeerProfile cur = _notFailingPeers.get(peer);
if (cur != null)
return cur;
cur = (PeerProfile)_failingPeers.get(peer);
cur = _failingPeers.get(peer);
return cur;
}

Expand Down Expand Up @@ -1158,22 +1212,74 @@ private Set<Integer> maskedIPSet(Hash peer, int mask) {
}

/** 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;
for (int i = 0; i < mask; i++)
rv = (rv << 8) | (ip[i] & 0xff);
return Integer.valueOf(rv);
}

/** 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) {
if (a.contains(o))
return true;
}
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) {
NetworkDatabaseFacade netDb = _context.netDb();
// the CLI shouldn't depend upon the netDb
Expand Down Expand Up @@ -1288,7 +1394,7 @@ private void locked_placeProfile(PeerProfile profile) {
*/
protected int getMinimumFastPeers() {
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);
}

Expand Down
53 changes: 45 additions & 8 deletions router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,57 @@ public List<Hash> selectPeers(RouterContext ctx, TunnelPoolSettings settings) {
return null;
if ( (length == 0) && (settings.getLength()+settings.getLengthVariance() > 0) )
return null;
HashSet matches = new HashSet(length);

List<Hash> rv;

if (length > 0) {
if (shouldSelectExplicit(settings))
return selectExplicit(ctx, settings, length);
}

Set exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory());
ctx.profileOrganizer().selectFastPeers(length, exclude, matches, settings.getIPRestriction());
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);
}

matches.remove(ctx.routerHash());
ArrayList<Hash> rv = new ArrayList(matches);
if (rv.size() > 1)
orderPeers(rv, settings.getRandomKey());
if (settings.isInbound())
rv.add(0, ctx.routerHash());
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public abstract class TunnelPeerSelector {
*/
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) {
int length = settings.getLength();
int override = settings.getLengthOverride();
Expand All @@ -61,8 +64,8 @@ else if (settings.getLengthVariance() != 0) {
}
if (length < 0)
length = 0;
else if (length > 8) // as documented in tunnel.html
length = 8;
else if (length > 7) // as documented in tunnel.html
length = 7;
/*
if ( (ctx.tunnelManager().getOutboundTunnelCount() <= 0) ||
(ctx.tunnelManager().getFreeTunnelCount() <= 0) ) {
Expand Down

0 comments on commit f965466

Please sign in to comment.