* ProfileOrganizer, TunnelPoolSettings, ClientPeerSelector:

- Prevent peers with matching IPs from joining same tunnel.
        Match 0-4 bytes of IP (0=off, 1=most restrictive, 4=least).
        Default is 2 (disallow routers in same /16).
        Set with router.defaultPool.IPRestriction=x
      - Comment out unused RebuildPeriod pool setting
      - Add random key to pool in preparation for XOR peer ordering
This commit is contained in:
zzz
2008-03-07 14:36:40 +00:00
parent 5ef325408c
commit 0c75725f5e
5 changed files with 170 additions and 24 deletions

View File

@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
public class RouterVersion {
public final static String ID = "$Revision: 1.548 $ $Date: 2008-02-10 15:00:00 $";
public final static String VERSION = "0.6.1.31";
public final static long BUILD = 3202;
public final static long BUILD = 3203;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
System.out.println("Router ID: " + RouterVersion.ID);

View File

@ -4,6 +4,7 @@ import java.util.Iterator;
import java.util.Properties;
import net.i2p.data.Hash;
import net.i2p.util.RandomSource;
/**
* Wrap up the settings for a pool of tunnels (duh)
@ -14,7 +15,7 @@ public class TunnelPoolSettings {
private String _destinationNickname;
private int _quantity;
private int _backupQuantity;
private int _rebuildPeriod;
// private int _rebuildPeriod;
private int _duration;
private int _length;
private int _lengthVariance;
@ -22,7 +23,9 @@ public class TunnelPoolSettings {
private boolean _isInbound;
private boolean _isExploratory;
private boolean _allowZeroHop;
private int _IPRestriction;
private Properties _unknownOptions;
private Hash _randomKey;
/** prefix used to override the router's defaults for clients */
public static final String PREFIX_DEFAULT = "router.defaultPool.";
@ -34,24 +37,26 @@ public class TunnelPoolSettings {
public static final String PROP_NICKNAME = "nickname";
public static final String PROP_QUANTITY = "quantity";
public static final String PROP_BACKUP_QUANTITY = "backupQuantity";
public static final String PROP_REBUILD_PERIOD = "rebuildPeriod";
// public static final String PROP_REBUILD_PERIOD = "rebuildPeriod";
public static final String PROP_DURATION = "duration";
public static final String PROP_LENGTH = "length";
public static final String PROP_LENGTH_VARIANCE = "lengthVariance";
public static final String PROP_ALLOW_ZERO_HOP = "allowZeroHop";
public static final String PROP_IP_RESTRICTION = "IPRestriction";
public static final int DEFAULT_QUANTITY = 2;
public static final int DEFAULT_BACKUP_QUANTITY = 0;
public static final int DEFAULT_REBUILD_PERIOD = 60*1000;
// public static final int DEFAULT_REBUILD_PERIOD = 60*1000;
public static final int DEFAULT_DURATION = 10*60*1000;
public static final int DEFAULT_LENGTH = 2;
public static final int DEFAULT_LENGTH_VARIANCE = 1;
public static final boolean DEFAULT_ALLOW_ZERO_HOP = true;
public static final int DEFAULT_IP_RESTRICTION = 2; // class B (/16)
public TunnelPoolSettings() {
_quantity = DEFAULT_QUANTITY;
_backupQuantity = DEFAULT_BACKUP_QUANTITY;
_rebuildPeriod = DEFAULT_REBUILD_PERIOD;
// _rebuildPeriod = DEFAULT_REBUILD_PERIOD;
_duration = DEFAULT_DURATION;
_length = DEFAULT_LENGTH;
_lengthVariance = DEFAULT_LENGTH_VARIANCE;
@ -61,7 +66,9 @@ public class TunnelPoolSettings {
_isExploratory = false;
_destination = null;
_destinationNickname = null;
_IPRestriction = DEFAULT_IP_RESTRICTION;
_unknownOptions = new Properties();
_randomKey = generateRandomKey();
}
/** how many tunnels should be available at all times */
@ -73,8 +80,8 @@ public class TunnelPoolSettings {
public void setBackupQuantity(int quantity) { _backupQuantity = quantity; }
/** how long before tunnel expiration should new tunnels be built */
public int getRebuildPeriod() { return _rebuildPeriod; }
public void setRebuildPeriod(int periodMs) { _rebuildPeriod = periodMs; }
// public int getRebuildPeriod() { return _rebuildPeriod; }
// public void setRebuildPeriod(int periodMs) { _rebuildPeriod = periodMs; }
/** how many remote hops should be in the tunnel */
public int getLength() { return _length; }
@ -112,10 +119,22 @@ public class TunnelPoolSettings {
public Hash getDestination() { return _destination; }
public void setDestination(Hash dest) { _destination = dest; }
/** random key used for peer ordering */
public Hash getRandomKey() { return _randomKey; }
/** what user supplied name was given to the client connected (can be null) */
public String getDestinationNickname() { return _destinationNickname; }
public void setDestinationNickname(String name) { _destinationNickname = name; }
/**
* How many bytes to match to determine if a router's IP is too close to another's
* to be in the same tunnel
* (1-4, 0 to disable)
*
*/
public int getIPRestriction() { int r = _IPRestriction; if (r>4) r=4; else if (r<0) r=0; return r;}
public void setIPRestriction(int b) { _IPRestriction = b; }
public Properties getUnknownOptions() { return _unknownOptions; }
public void readFromProperties(String prefix, Properties props) {
@ -135,10 +154,12 @@ public class TunnelPoolSettings {
_lengthVariance = getInt(value, DEFAULT_LENGTH_VARIANCE);
else if (name.equalsIgnoreCase(prefix + PROP_QUANTITY))
_quantity = getInt(value, DEFAULT_QUANTITY);
else if (name.equalsIgnoreCase(prefix + PROP_REBUILD_PERIOD))
_rebuildPeriod = getInt(value, DEFAULT_REBUILD_PERIOD);
// else if (name.equalsIgnoreCase(prefix + PROP_REBUILD_PERIOD))
// _rebuildPeriod = getInt(value, DEFAULT_REBUILD_PERIOD);
else if (name.equalsIgnoreCase(prefix + PROP_NICKNAME))
_destinationNickname = value;
else if (name.equalsIgnoreCase(prefix + PROP_IP_RESTRICTION))
_IPRestriction = getInt(value, DEFAULT_IP_RESTRICTION);
else
_unknownOptions.setProperty(name.substring((prefix != null ? prefix.length() : 0)), value);
}
@ -155,7 +176,8 @@ public class TunnelPoolSettings {
if (_destinationNickname != null)
props.setProperty(prefix + PROP_NICKNAME, ""+_destinationNickname);
props.setProperty(prefix + PROP_QUANTITY, ""+_quantity);
props.setProperty(prefix + PROP_REBUILD_PERIOD, ""+_rebuildPeriod);
// props.setProperty(prefix + PROP_REBUILD_PERIOD, ""+_rebuildPeriod);
props.setProperty(prefix + PROP_IP_RESTRICTION, ""+_IPRestriction);
for (Iterator iter = _unknownOptions.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String val = _unknownOptions.getProperty(name);
@ -180,8 +202,12 @@ public class TunnelPoolSettings {
return buf.toString();
}
////
////
// used for strict peer ordering
private Hash generateRandomKey() {
byte hash[] = new byte[Hash.HASH_LENGTH];
RandomSource.getInstance().nextBytes(hash);
return new Hash(hash);
}
private static final boolean getBoolean(String str, boolean defaultValue) {
if (str == null) return defaultValue;

View File

@ -3,6 +3,8 @@ package net.i2p.router.peermanager;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
@ -13,11 +15,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.NetworkDatabaseFacade;
@ -229,13 +233,16 @@ public class ProfileOrganizer {
*
*/
public void selectFastPeers(int howMany, Set exclude, Set matches) {
selectFastPeers(howMany, exclude, matches, 0);
}
public void selectFastPeers(int howMany, Set exclude, Set matches, int mask) {
synchronized (_reorganizeLock) {
locked_selectPeers(_fastPeers, howMany, exclude, matches);
locked_selectPeers(_fastPeers, howMany, exclude, matches, mask);
}
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);
selectHighCapacityPeers(howMany, exclude, matches, mask);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("selectFastPeers("+howMany+"), found enough fast (" + matches.size() + ")");
@ -248,6 +255,9 @@ public class ProfileOrganizer {
*
*/
public void selectHighCapacityPeers(int howMany, Set exclude, Set matches) {
selectHighCapacityPeers(howMany, exclude, matches, 0);
}
public void selectHighCapacityPeers(int howMany, Set exclude, Set matches, int mask) {
synchronized (_reorganizeLock) {
// we only use selectHighCapacityPeers when we are selecting for PURPOSE_TEST
// or we are falling back due to _fastPeers being too small, so we can always
@ -258,12 +268,12 @@ public class ProfileOrganizer {
else
exclude.addAll(_fastPeers.keySet());
*/
locked_selectPeers(_highCapacityPeers, howMany, exclude, matches);
locked_selectPeers(_highCapacityPeers, howMany, exclude, matches, mask);
}
if (matches.size() < howMany) {
if (_log.shouldLog(Log.INFO))
_log.info("selectHighCap("+howMany+"), not enough fast (" + matches.size() + ") going on to notFailing");
selectNotFailingPeers(howMany, exclude, matches);
selectNotFailingPeers(howMany, exclude, matches, mask);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("selectHighCap("+howMany+"), found enough highCap (" + matches.size() + ")");
@ -275,13 +285,16 @@ public class ProfileOrganizer {
*
*/
public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches) {
selectWellIntegratedPeers(howMany, exclude, matches, 0);
}
public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches, int mask) {
synchronized (_reorganizeLock) {
locked_selectPeers(_wellIntegratedPeers, howMany, exclude, matches);
locked_selectPeers(_wellIntegratedPeers, howMany, exclude, matches, mask);
}
if (matches.size() < howMany) {
if (_log.shouldLog(Log.INFO))
_log.info("selectWellIntegrated("+howMany+"), not enough integrated (" + matches.size() + ") going on to notFailing");
selectNotFailingPeers(howMany, exclude, matches);
selectNotFailingPeers(howMany, exclude, matches, mask);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("selectWellIntegrated("+howMany+"), found enough well integrated (" + matches.size() + ")");
@ -295,7 +308,13 @@ public class ProfileOrganizer {
*
*/
public void selectNotFailingPeers(int howMany, Set exclude, Set matches) {
selectNotFailingPeers(howMany, exclude, matches, false);
selectNotFailingPeers(howMany, exclude, matches, false, 0);
}
public void selectNotFailingPeers(int howMany, Set exclude, Set matches, int mask) {
selectNotFailingPeers(howMany, exclude, matches, false, mask);
}
public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) {
selectNotFailingPeers(howMany, exclude, matches, onlyNotFailing, 0);
}
/**
* Return a set of Hashes for peers that are not failing, preferring ones that
@ -306,9 +325,9 @@ public class ProfileOrganizer {
* @param matches set to store the matches in
* @param onlyNotFailing if true, don't include any high capacity peers
*/
public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) {
public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) {
if (matches.size() < howMany)
selectAllNotFailingPeers(howMany, exclude, matches, onlyNotFailing);
selectAllNotFailingPeers(howMany, exclude, matches, onlyNotFailing, mask);
return;
}
/**
@ -347,7 +366,10 @@ public class ProfileOrganizer {
* Return a set of Hashes for peers that are not failing.
*
*/
private void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) {
public void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) {
selectAllNotFailingPeers(howMany, exclude, matches, onlyNotFailing, 0);
}
private void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) {
if (matches.size() < howMany) {
int orig = matches.size();
int needed = howMany - orig;
@ -676,6 +698,7 @@ public class ProfileOrganizer {
_log.info("Our average capacity is doing well [" + meanCapacity
+ "], and includes " + numExceedingMean);
_thresholdCapacityValue = meanCapacity;
// else if mean > median && reordered.size > minHighCapacitiyPeers then threshold = reordered.get(minHighCapacity).getCapacityValue()
} else if (reordered.size()/2 >= minHighCapacityPeers) {
// ok mean is skewed low, but we still have enough to use the median
if (_log.shouldLog(Log.INFO))
@ -779,6 +802,9 @@ public class ProfileOrganizer {
*
*/
private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches) {
locked_selectPeers(peers, howMany, toExclude, matches, 0);
}
private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches, int mask) {
List all = new ArrayList(peers.keySet());
if (toExclude != null)
all.removeAll(toExclude);
@ -789,6 +815,11 @@ public class ProfileOrganizer {
for (int i = 0; (matches.size() < howMany) && (i < all.size()); i++) {
Hash peer = (Hash)all.get(i);
boolean ok = isSelectable(peer);
if (ok) {
ok = mask <= 0 || notRestricted(peer, matches, mask);
if ((!ok) && _log.shouldLog(Log.WARN))
_log.warn("IP restriction prevents " + peer + " from joining " + matches);
}
if (ok)
matches.add(peer);
else
@ -796,6 +827,82 @@ public class ProfileOrganizer {
}
}
/**
* Does the peer's IP address NOT match the IP address of any peer already in the set,
* on any transport, within a given mask?
* mask is 1-4 (number of bytes to match) or 0 to disable
* Perhaps rewrite this to just make a set of all the IP addresses rather than loop.
*/
private boolean notRestricted(Hash peer, Set matches, int mask) {
if (mask <= 0) return true;
if (matches.size() <= 0) return true;
RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer);
if (pinfo == null) return false;
Set paddr = pinfo.getAddresses();
if (paddr == null || paddr.size() == 0)
return false;
List pladdr = new ArrayList(paddr);
List lmatches = new ArrayList(matches);
// for each match
for (int i = 0; i < matches.size(); i++) {
RouterInfo minfo = _context.netDb().lookupRouterInfoLocally((Hash) lmatches.get(i));
if (minfo == null) continue;
Set maddr = minfo.getAddresses();
if (maddr == null || maddr.size() == 0)
continue;
List mladdr = new ArrayList(maddr);
String oldphost = null;
// for each peer address
for (int j = 0; j < paddr.size(); j++) {
RouterAddress pa = (RouterAddress) pladdr.get(j);
if (pa == null) continue;
Properties pprops = pa.getOptions();
if (pprops == null) continue;
String phost = pprops.getProperty("host");
if (phost == null) continue;
if (oldphost != null && oldphost.equals(phost)) continue;
oldphost = phost;
InetAddress pi;
try {
pi = InetAddress.getByName(phost);
} catch (UnknownHostException uhe) {
continue;
}
if (pi == null) continue;
byte[] pib = pi.getAddress();
String oldmhost = null;
// for each match address
for (int k = 0; k < maddr.size(); k++) {
RouterAddress ma = (RouterAddress) mladdr.get(k);
if (ma == null) continue;
Properties mprops = ma.getOptions();
if (mprops == null) continue;
String mhost = mprops.getProperty("host");
if (mhost == null) continue;
if (oldmhost != null && oldmhost.equals(mhost)) continue;
oldmhost = mhost;
InetAddress mi;
try {
mi = InetAddress.getByName(mhost);
} catch (UnknownHostException uhe) {
continue;
}
if (mi == null) continue;
byte[] mib = mi.getAddress();
// assume ipv4, compare 1 to 4 bytes
// log.info("Comparing " + pi + " with " + mi);
for (int m = 0; m < mask; m++) {
if (pib[m] != mib[m])
break;
if (m == mask-1)
return false; // IP match
}
}
}
}
return true;
}
public boolean isSelectable(Hash peer) {
NetworkDatabaseFacade netDb = _context.netDb();
// the CLI shouldn't depend upon the netDb
@ -807,7 +914,7 @@ public class ProfileOrganizer {
return false; // never select a shitlisted peer
}
RouterInfo info = netDb.lookupRouterInfoLocally(peer);
RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
if (null != info) {
if (info.getIdentity().isHidden()) {
if (_log.shouldLog(Log.WARN))

View File

@ -24,10 +24,11 @@ class ClientPeerSelector extends TunnelPeerSelector {
}
Set exclude = getExclude(ctx, settings.isInbound(), settings.isExploratory());
ctx.profileOrganizer().selectFastPeers(length, exclude, matches);
ctx.profileOrganizer().selectFastPeers(length, exclude, matches, settings.getIPRestriction());
matches.remove(ctx.routerHash());
ArrayList rv = new ArrayList(matches);
// Todo - Rather than shuffle, sort using xor distance from settings.getRandomKey()
Collections.shuffle(rv, ctx.random());
if (settings.isInbound())
rv.add(0, ctx.routerHash());