package net.i2p.router; import java.util.Set; import java.util.Locale; import java.util.Map; import java.util.Properties; import net.i2p.data.Base64; import net.i2p.data.Hash; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.NativeBigInteger; import net.i2p.util.RandomSource; import net.i2p.util.SystemVersion; /** * Wrap up the settings for a pool of tunnels. * */ public class TunnelPoolSettings { private final Hash _destination; private String _destinationNickname; private int _quantity; private int _backupQuantity; // private int _rebuildPeriod; //private int _duration; private int _length; private int _lengthVariance; private int _lengthOverride; private final boolean _isInbound; private final boolean _isExploratory; private boolean _allowZeroHop; private int _IPRestriction; private final Properties _unknownOptions; private Hash _randomKey; private int _priority; private final Set<Hash> _aliases; private Hash _aliasOf; /** prefix used to override the router's defaults for clients */ // unimplemented //public static final String PREFIX_DEFAULT = "router.defaultPool."; /** prefix used to configure the inbound exploratory pool */ public static final String PREFIX_INBOUND_EXPLORATORY = "router.inboundPool."; /** prefix used to configure the outbound exploratory pool */ public static final String PREFIX_OUTBOUND_EXPLORATORY = "router.outboundPool."; 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_DURATION = "duration"; public static final String PROP_LENGTH = "length"; public static final String PROP_LENGTH_VARIANCE = "lengthVariance"; /** don't trust this, always true */ public static final String PROP_ALLOW_ZERO_HOP = "allowZeroHop"; public static final String PROP_IP_RESTRICTION = "IPRestriction"; public static final String PROP_PRIORITY = "priority"; /** @since 0.9.17 */ public static final String PROP_RANDOM_KEY = "randomKey"; 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_DURATION = 10*60*1000; //public static final int DEFAULT_LENGTH = SystemVersion.isAndroid() ? 2 : 3; private static final boolean isSlow = SystemVersion.isSlow(); /** client only */ private static final int DEFAULT_IB_LENGTH = 3; private static final int DEFAULT_OB_LENGTH = 3; private static final int DEFAULT_LENGTH_VARIANCE = 0; /** expl only */ private static final int DEFAULT_IB_EXPL_LENGTH = 2; private static final int DEFAULT_OB_EXPL_LENGTH = isSlow ? 2 : 3; //private static final int DEFAULT_OB_EXPL_LENGTH = 2; private static final int DEFAULT_IB_EXPL_LENGTH_VARIANCE = isSlow ? 0 : 1; private static final int DEFAULT_OB_EXPL_LENGTH_VARIANCE = 0; //private static final int DEFAULT_OB_EXPL_LENGTH_VARIANCE = isSlow ? 0 : 1; public static final boolean DEFAULT_ALLOW_ZERO_HOP = true; public static final int DEFAULT_IP_RESTRICTION = 2; // class B (/16) private static final int MIN_PRIORITY = -25; private static final int MAX_PRIORITY = 25; private static final int EXPLORATORY_PRIORITY = 30; /** * Exploratory tunnel */ public TunnelPoolSettings(boolean isInbound) { this(null, isInbound); } /** * Client tunnel unless dest == null */ public TunnelPoolSettings(Hash dest, boolean isInbound) { _destination = dest; _isExploratory = dest == null; _isInbound = isInbound; _quantity = DEFAULT_QUANTITY; _backupQuantity = DEFAULT_BACKUP_QUANTITY; // _rebuildPeriod = DEFAULT_REBUILD_PERIOD; //_duration = DEFAULT_DURATION; if (isInbound) { _length = _isExploratory ? DEFAULT_IB_EXPL_LENGTH : DEFAULT_IB_LENGTH; _lengthVariance = _isExploratory ? DEFAULT_IB_EXPL_LENGTH_VARIANCE : DEFAULT_LENGTH_VARIANCE; } else { _length = _isExploratory ? DEFAULT_OB_EXPL_LENGTH : DEFAULT_OB_LENGTH; _lengthVariance = _isExploratory ? DEFAULT_OB_EXPL_LENGTH_VARIANCE : DEFAULT_LENGTH_VARIANCE; } _lengthOverride = -1; if (_isExploratory) _allowZeroHop = true; else _allowZeroHop = DEFAULT_ALLOW_ZERO_HOP; _IPRestriction = DEFAULT_IP_RESTRICTION; _unknownOptions = new Properties(); _randomKey = generateRandomKey(); if (_isExploratory && !_isInbound) _priority = EXPLORATORY_PRIORITY; if (!_isExploratory) _aliases = new ConcurrentHashSet<Hash>(4); else _aliases = null; } /** how many tunnels should be available at all times */ public int getQuantity() { return _quantity; } public void setQuantity(int quantity) { _quantity = quantity; } /** how many backup tunnels should be kept waiting in the wings */ public int getBackupQuantity() { return _backupQuantity; } public void setBackupQuantity(int quantity) { _backupQuantity = quantity; } /** * Convenience * @return getQuantity() + getBackupQuantity() * @since 0.8.11 */ public int getTotalQuantity() { return _quantity + _backupQuantity; } /** how long before tunnel expiration should new tunnels be built */ // public int getRebuildPeriod() { return _rebuildPeriod; } // public void setRebuildPeriod(int periodMs) { _rebuildPeriod = periodMs; } /** * How many remote hops should be in the tunnel NOT including us * @return 0 to 7 */ public int getLength() { return _length; } /** * How many remote hops should be in the tunnel NOT including us * @param length 0 to 7 (not enforced here) */ public void setLength(int length) { _length = length; } /** * If there are no tunnels to build with, will this pool allow 0 hop tunnels? * Always true for exploratory. * Generally true for client, but should probably be ignored... * use getLength() + getLengthVariance() > 0 instead. */ public boolean getAllowZeroHop() { return _allowZeroHop; } /** * If there are no tunnels to build with, will this pool allow 0 hop tunnels? * No effect on exploratory (always true) */ public void setAllowZeroHop(boolean ok) { if (!_isExploratory) _allowZeroHop = ok; } /** * how should the length be varied. if negative, this randomly skews from * (length - variance) to (length + variance), or if positive, from length * to (length + variance), inclusive. * */ public int getLengthVariance() { return _lengthVariance; } public void setLengthVariance(int variance) { _lengthVariance = variance; } /** * A temporary length to be used due to network conditions. * If less than zero, the standard length should be used. * Unused until 0.8.11 */ public int getLengthOverride() { return _lengthOverride; } /** * A temporary length to be used due to network conditions. * If less than zero, the standard length will be used. * Unused until 0.8.11 */ public void setLengthOverride(int length) { _lengthOverride = length; } /** is this an inbound tunnel? */ public boolean isInbound() { return _isInbound; } /** is this an exploratory tunnel (or a client tunnel) */ public boolean isExploratory() { return _isExploratory; } // Duration is hardcoded //public int getDuration() { return _duration; } //public void setDuration(int ms) { _duration = ms; } /** what destination is this a client tunnel for (or null if exploratory) */ public Hash getDestination() { return _destination; } /** * Other destinations that use the same tunnel (or null if exploratory) * Modifiable, concurrent, not a copy * @since 0.9.21 */ public Set<Hash> getAliases() { return _aliases; } /** * Other destination that this is an alias of (or null). * If non-null, don't build tunnels. * @since 0.9.21 */ public Hash getAliasOf() { return _aliasOf; } /** * Set other destination that this is an alias of (or null). * If non-null, don't build tunnels. * @since 0.9.21 */ public void setAliasOf(Hash h) { _aliasOf = h; } /** * random key used for peer ordering * * @return non-null */ 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; } /** * Outbound message priority - for outbound tunnels only * @return -25 to +30, default 30 for outbound exploratory and 0 for others * @since 0.9.4 */ public int getPriority() { return _priority; } public Properties getUnknownOptions() { return _unknownOptions; } /** * Defaults in props are NOT honored. * In-JVM client side must promote defaults to the primary map. * * @param prefix non-null */ public void readFromProperties(String prefix, Properties props) { for (Map.Entry<Object, Object> e : props.entrySet()) { String name = (String) e.getKey(); String value = (String) e.getValue(); if (name.startsWith(prefix)) { if (name.equalsIgnoreCase(prefix + PROP_ALLOW_ZERO_HOP)) { if (!_isExploratory) _allowZeroHop = getBoolean(value, DEFAULT_ALLOW_ZERO_HOP); } else if (name.equalsIgnoreCase(prefix + PROP_BACKUP_QUANTITY)) _backupQuantity = getInt(value, DEFAULT_BACKUP_QUANTITY); //else if (name.equalsIgnoreCase(prefix + PROP_DURATION)) // _duration = getInt(value, DEFAULT_DURATION); else if (name.equalsIgnoreCase(prefix + PROP_LENGTH)) _length = getInt(value, _isInbound ? (_isExploratory ? DEFAULT_IB_EXPL_LENGTH : DEFAULT_IB_LENGTH) : (_isExploratory ? DEFAULT_OB_EXPL_LENGTH : DEFAULT_OB_LENGTH)); else if (name.equalsIgnoreCase(prefix + PROP_LENGTH_VARIANCE)) _lengthVariance = getInt(value, _isExploratory ? (_isInbound ? DEFAULT_IB_EXPL_LENGTH_VARIANCE : DEFAULT_OB_EXPL_LENGTH_VARIANCE) : 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_NICKNAME)) _destinationNickname = value; else if (name.equalsIgnoreCase(prefix + PROP_IP_RESTRICTION)) _IPRestriction = getInt(value, DEFAULT_IP_RESTRICTION); else if ((!_isInbound) && name.equalsIgnoreCase(prefix + PROP_PRIORITY)) { int def = _isExploratory ? EXPLORATORY_PRIORITY : 0; int max = _isExploratory ? EXPLORATORY_PRIORITY : MAX_PRIORITY; _priority = Math.min(max, Math.max(MIN_PRIORITY, getInt(value, def))); } else if (name.equalsIgnoreCase(prefix + PROP_RANDOM_KEY)) { byte[] rk = Base64.decode(value); if (rk != null && rk.length == Hash.HASH_LENGTH) _randomKey = new Hash(rk); } else _unknownOptions.setProperty(name.substring(prefix.length()), value); } } } /** * @param prefix non-null */ public void writeToProperties(String prefix, Properties props) { if (props == null) return; props.setProperty(prefix + PROP_ALLOW_ZERO_HOP, ""+_allowZeroHop); props.setProperty(prefix + PROP_BACKUP_QUANTITY, ""+_backupQuantity); //props.setProperty(prefix + PROP_DURATION, ""+_duration); props.setProperty(prefix + PROP_LENGTH, ""+_length); props.setProperty(prefix + PROP_LENGTH_VARIANCE, ""+_lengthVariance); 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_IP_RESTRICTION, ""+_IPRestriction); if (!_isInbound) props.setProperty(prefix + PROP_PRIORITY, Integer.toString(_priority)); for (Map.Entry<Object, Object> e : _unknownOptions.entrySet()) { String name = (String) e.getKey(); String val = (String) e.getValue(); props.setProperty(prefix + name, val); } } @Override public String toString() { StringBuilder buf = new StringBuilder(); Properties p = new Properties(); writeToProperties("", p); buf.append("Tunnel pool settings:\n"); buf.append("====================================\n"); for (Map.Entry<Object, Object> e : p.entrySet()) { String name = (String) e.getKey(); String val = (String) e.getValue(); buf.append(name).append(" = [").append(val).append("]\n"); } buf.append("is inbound? ").append(_isInbound).append("\n"); buf.append("is exploratory? ").append(_isExploratory).append("\n"); buf.append("====================================\n"); return buf.toString(); } // used for strict peer ordering private static 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; boolean v = Boolean.parseBoolean(str) || "YES".equals(str.toUpperCase(Locale.US)); return v; } private static final int getInt(String str, int defaultValue) { return (int)getLong(str, defaultValue); } private static final long getLong(String str, long defaultValue) { if (str == null) return defaultValue; try { long val = Long.parseLong(str); return val; } catch (NumberFormatException nfe) { return defaultValue; } } }