diff --git a/history.txt b/history.txt index 2e8ab7e4d..29bd7ca09 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,15 @@ +2008-03-07 zzz + * Naming: Optimize lookups for a destkey + * 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 + * SusiMail: Add 'Create Account' link + * TunnelDispatcher: Change a common wtf error to a warn + 2008-03-05 zzz * Naming: Make HostsTxt the sole default NamingService (was Meta = PetName + HostsTxt) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index e42b9bbcc..b54357532 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -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); diff --git a/router/java/src/net/i2p/router/TunnelPoolSettings.java b/router/java/src/net/i2p/router/TunnelPoolSettings.java index aaeb5cec3..a92470d2e 100644 --- a/router/java/src/net/i2p/router/TunnelPoolSettings.java +++ b/router/java/src/net/i2p/router/TunnelPoolSettings.java @@ -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; diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index f96a71b85..78f9086c3 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -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)) diff --git a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java index d4d03e056..159dc28fe 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ClientPeerSelector.java @@ -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());