propagate from branch 'i2p.i2p' (head cbca70618d2083a5fcdead2390e9d30060080e74)

to branch 'i2p.i2p.zzz.test' (head 1affab2e83613f326d269370de6e5aed40ecae52)
This commit is contained in:
zzz
2012-10-09 13:36:32 +00:00
151 changed files with 8127 additions and 4601 deletions

View File

@@ -91,7 +91,7 @@ public class Blocklist {
static final String BLOCKLIST_FILE_DEFAULT = "blocklist.txt";
public void startup() {
if (! Boolean.valueOf(_context.getProperty(PROP_BLOCKLIST_ENABLED, "true")).booleanValue())
if (! _context.getBooleanPropertyDefaultTrue(PROP_BLOCKLIST_ENABLED))
return;
String file = _context.getProperty(PROP_BLOCKLIST_FILE, BLOCKLIST_FILE_DEFAULT);
// Maybe someday we'll read in multiple files and merge them
@@ -849,6 +849,7 @@ public class Blocklist {
return Translate.getString(key, _context, BUNDLE_NAME);
}
/****
public static void main(String args[]) {
Blocklist b = new Blocklist();
if ( (args != null) && (args.length == 1) )
@@ -861,4 +862,5 @@ public class Blocklist {
System.out.println("Testing " + tests[i] + " returns " + b.isBlocklisted(tests[i]));
}
}
****/
}

View File

@@ -20,12 +20,8 @@ public class ClientTunnelSettings {
private TunnelPoolSettings _outboundSettings;
public ClientTunnelSettings() {
_inboundSettings = new TunnelPoolSettings();
_inboundSettings.setIsInbound(true);
_inboundSettings.setIsExploratory(false);
_outboundSettings = new TunnelPoolSettings();
_outboundSettings.setIsInbound(false);
_outboundSettings.setIsExploratory(false);
_inboundSettings = new TunnelPoolSettings(false, true);
_outboundSettings = new TunnelPoolSettings(false, false);
}
public TunnelPoolSettings getInboundSettings() { return _inboundSettings; }

View File

@@ -348,7 +348,7 @@ public class InNetMessagePool implements Service {
_dispatchThreaded = DEFAULT_DISPATCH_THREADED;
String threadedStr = _context.getProperty(PROP_DISPATCH_THREADED);
if (threadedStr != null) {
_dispatchThreaded = Boolean.valueOf(threadedStr).booleanValue();
_dispatchThreaded = Boolean.parseBoolean(threadedStr);
}
if (_dispatchThreaded) {
_context.statManager().createRateStat("pool.dispatchDataTime", "How long a tunnel dispatch takes", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });

View File

@@ -80,7 +80,7 @@ public class MessageHistory {
String getFilename() { return _historyFile; }
private void updateSettings() {
_doLog = Boolean.valueOf(_context.getProperty(PROP_KEEP_MESSAGE_HISTORY)).booleanValue();
_doLog = _context.getBooleanProperty(PROP_KEEP_MESSAGE_HISTORY);
_historyFile = _context.getProperty(PROP_MESSAGE_HISTORY_FILENAME, DEFAULT_MESSAGE_HISTORY_FILENAME);
}

View File

@@ -49,6 +49,7 @@ import net.i2p.util.FortunaRandomSource;
import net.i2p.util.I2PAppThread;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SimpleByteCache;
import net.i2p.util.SimpleScheduler;
@@ -176,7 +177,7 @@ public class Router implements RouterClock.ClockShiftListener {
// Do we copy all the data files to the new directory? default false
String migrate = System.getProperty("i2p.dir.migrate");
boolean migrateFiles = Boolean.valueOf(migrate).booleanValue();
boolean migrateFiles = Boolean.parseBoolean(migrate);
String userDir = WorkingDir.getWorkingDir(envProps, migrateFiles);
// Use the router.config file specified in the router.configLocation property
@@ -196,7 +197,7 @@ public class Router implements RouterClock.ClockShiftListener {
envProps.putAll(_config);
// This doesn't work, guess it has to be in the static block above?
// if (Boolean.valueOf(envProps.getProperty("router.disableIPv6")).booleanValue())
// if (Boolean.parseBoolean(envProps.getProperty("router.disableIPv6")))
// System.setProperty("java.net.preferIPv4Stack", "true");
if (envProps.getProperty("i2p.dir.config") == null)
@@ -630,7 +631,7 @@ public class Router implements RouterClock.ClockShiftListener {
return true;
String h = _context.getProperty(PROP_HIDDEN_HIDDEN);
if (h != null)
return Boolean.valueOf(h).booleanValue();
return Boolean.parseBoolean(h);
return _context.commSystem().isInBadCountry();
}
@@ -976,44 +977,24 @@ public class Router implements RouterClock.ClockShiftListener {
* Save the current config options (returning true if save was
* successful, false otherwise)
*
* Note that unlike DataHelper.storeProps(),
* this does escape the \r or \n that are unescaped in DataHelper.loadProps().
* Note that the escaping of \r or \n was probably a mistake and should be taken out.
*
* Synchronized with file read in getConfig()
*/
public boolean saveConfig() {
synchronized(_configFileLock) {
FileOutputStream fos = null;
try {
fos = new SecureFileOutputStream(_configFilename);
StringBuilder buf = new StringBuilder(8*1024);
buf.append("# NOTE: This I2P config file must use UTF-8 encoding\n");
TreeSet ordered = new TreeSet(_config.keySet());
for (Iterator iter = ordered.iterator() ; iter.hasNext(); ) {
String key = (String)iter.next();
String val = _config.get(key);
// Escape line breaks before saving.
// Remember: "\" needs escaping both for regex and string.
// NOOO - see comments in DataHelper
//val = val.replaceAll("\\r","\\\\r");
//val = val.replaceAll("\\n","\\\\n");
buf.append(key).append('=').append(val).append('\n');
}
fos.write(buf.toString().getBytes("UTF-8"));
} catch (IOException ioe) {
try {
Properties ordered = new OrderedProperties();
synchronized(_configFileLock) {
ordered.putAll(_config);
DataHelper.storeProps(ordered, new File(_configFilename));
}
} catch (Exception ioe) {
// warning, _log will be null when called from constructor
if (_log != null)
_log.error("Error saving the config to " + _configFilename, ioe);
else
System.err.println("Error saving the config to " + _configFilename + ": " + ioe);
return false;
} finally {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
}
return true;
}
return true;
}
/**

View File

@@ -18,10 +18,10 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 23;
public final static long BUILD = 13;
/** for example "-test" */
public final static String EXTRA = "-rc-test";
public final static String EXTRA = "";
public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;
public static void main(String args[]) {
System.out.println("I2P Router version: " + FULL_VERSION);

View File

@@ -21,8 +21,8 @@ public class TunnelPoolSettings {
private int _length;
private int _lengthVariance;
private int _lengthOverride;
private boolean _isInbound;
private boolean _isExploratory;
private final boolean _isInbound;
private final boolean _isExploratory;
private boolean _allowZeroHop;
private int _IPRestriction;
private final Properties _unknownOptions;
@@ -54,7 +54,9 @@ public class TunnelPoolSettings {
public static final boolean DEFAULT_ALLOW_ZERO_HOP = true;
public static final int DEFAULT_IP_RESTRICTION = 2; // class B (/16)
public TunnelPoolSettings() {
public TunnelPoolSettings(boolean isExploratory, boolean isInbound) {
_isExploratory = isExploratory;
_isInbound = isInbound;
_quantity = DEFAULT_QUANTITY;
_backupQuantity = DEFAULT_BACKUP_QUANTITY;
// _rebuildPeriod = DEFAULT_REBUILD_PERIOD;
@@ -130,11 +132,9 @@ public class TunnelPoolSettings {
/** is this an inbound tunnel? */
public boolean isInbound() { return _isInbound; }
public void setIsInbound(boolean isInbound) { _isInbound = isInbound; }
/** is this an exploratory tunnel (or a client tunnel) */
public boolean isExploratory() { return _isExploratory; }
public void setIsExploratory(boolean isExploratory) { _isExploratory = isExploratory; }
// Duration is hardcoded
//public int getDuration() { return _duration; }
@@ -237,7 +237,7 @@ public class TunnelPoolSettings {
private static final boolean getBoolean(String str, boolean defaultValue) {
if (str == null) return defaultValue;
boolean v = Boolean.valueOf(str).booleanValue() ||
boolean v = Boolean.parseBoolean(str) ||
(str != null && "YES".equals(str.toUpperCase(Locale.US)));
return v;
}

View File

@@ -308,6 +308,11 @@ class ClientConnectionRunner {
if (_log.shouldLog(Log.WARN))
_log.warn("Error writing out the disconnect message: " + ime);
}
// give it a little time to get sent out...
// even better would be to have stopRunning() flush it?
try {
Thread.sleep(50);
} catch (InterruptedException ie) {}
stopRunning();
}

View File

@@ -326,7 +326,7 @@ class ClientManager {
if (destHash == null) return true;
ClientConnectionRunner runner = getRunner(destHash);
if (runner == null) return true;
return !Boolean.valueOf(runner.getConfig().getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY)).booleanValue();
return !Boolean.parseBoolean(runner.getConfig().getOptions().getProperty(ClientManagerFacade.PROP_CLIENT_ONLY));
}
/**

View File

@@ -169,7 +169,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
}
// Auth, since 0.8.2
if (_enforceAuth && Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue()) {
if (_enforceAuth && _context.getBooleanProperty("i2cp.auth")) {
String configUser = _context.getProperty("i2cp.username");
String configPW = _context.getProperty("i2cp.password");
if (configUser != null && configPW != null) {

View File

@@ -452,7 +452,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
// Per-message flag == false overrides session option which is default true
String allow = _clientMessage.getSenderConfig().getOptions().getProperty(BUNDLE_REPLY_LEASESET);
boolean allowLeaseBundle = SendMessageOptions.getSendLeaseSet(sendFlags) &&
(allow == null || Boolean.valueOf(allow).booleanValue());
(allow == null || Boolean.parseBoolean(allow));
if (allowLeaseBundle) {
// If we want an ack, bundle a leaseSet...
//replyLeaseSet = getReplyLeaseSet(wantACK);

View File

@@ -25,7 +25,7 @@ class FloodfillMonitorJob extends JobImpl {
private static final int REQUEUE_DELAY = 60*60*1000;
private static final long MIN_UPTIME = 2*60*60*1000;
private static final long MIN_CHANGE_DELAY = 6*60*60*1000;
private static final int MIN_FF = 300;
private static final int MIN_FF = 360;
private static final int MAX_FF = 999999;
private static final String PROP_FLOODFILL_PARTICIPANT = "router.floodfillParticipant";

View File

@@ -45,7 +45,7 @@ class HarvesterJob extends JobImpl {
public static final String PROP_ENABLED = "netDb.shouldHarvest";
private boolean harvestDirectly() {
return Boolean.valueOf(getContext().getProperty("netDb.harvestDirectly", "false")).booleanValue();
return getContext().getBooleanProperty("netDb.harvestDirectly");
}
public HarvesterJob(RouterContext context, KademliaNetworkDatabaseFacade facade) {

View File

@@ -220,7 +220,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
String enforce = _context.getProperty(PROP_ENFORCE_NETID);
if (enforce != null)
_enforceNetId = Boolean.valueOf(enforce).booleanValue();
_enforceNetId = Boolean.parseBoolean(enforce);
else
_enforceNetId = DEFAULT_ENFORCE_NETID;
_ds.restart();
@@ -246,7 +246,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
String dbDir = _context.getProperty(PROP_DB_DIR, DEFAULT_DB_DIR);
String enforce = _context.getProperty(PROP_ENFORCE_NETID);
if (enforce != null)
_enforceNetId = Boolean.valueOf(enforce).booleanValue();
_enforceNetId = Boolean.parseBoolean(enforce);
else
_enforceNetId = DEFAULT_ENFORCE_NETID;

View File

@@ -136,7 +136,7 @@ class SearchJob extends JobImpl {
// Returning false essentially enables kademlia as a backup to floodfill for search responses.
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(ctx))
return false;
return Boolean.valueOf(ctx.getProperty("netDb.floodfillOnly", DEFAULT_FLOODFILL_ONLY + "")).booleanValue();
return ctx.getProperty("netDb.floodfillOnly", DEFAULT_FLOODFILL_ONLY);
}
/***

View File

@@ -527,7 +527,7 @@ public class Reseeder {
/******
public static void main(String args[]) {
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
if ( (args != null) && (args.length == 1) && (!Boolean.parseBoolean(args[0])) ) {
System.out.println("Not reseeding, as requested");
return; // not reseeding on request
}

View File

@@ -54,7 +54,7 @@ public class BootCommSystemJob extends JobImpl {
private void startupDb() {
Job bootDb = new BootNetworkDbJob(getContext());
boolean useTrusted = Boolean.valueOf(getContext().getProperty(PROP_USE_TRUSTED_LINKS)).booleanValue();
boolean useTrusted = getContext().getBooleanProperty(PROP_USE_TRUSTED_LINKS);
if (useTrusted) {
_log.debug("Using trusted links...");
getContext().jobQueue().addJob(new BuildTrustedLinksJob(getContext(), bootDb));

View File

@@ -1,7 +1,10 @@
package net.i2p.router.tasks;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.util.EventLog;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@@ -12,30 +15,47 @@ import net.i2p.util.Log;
*/
public class OOMListener implements I2PThread.OOMEventListener {
private final RouterContext _context;
private final AtomicBoolean _wasCalled = new AtomicBoolean();
public OOMListener(RouterContext ctx) {
_context = ctx;
}
public void outOfMemory(OutOfMemoryError oom) {
// boost priority to help us shut down
Thread.currentThread().setPriority(Thread.MAX_PRIORITY - 1);
Router.clearCaches();
Log log = _context.logManager().getLog(Router.class);
log.log(Log.CRIT, "Thread ran out of memory, shutting down I2P", oom);
// prevent multiple parallel shutdowns (when you OOM, you OOM a lot...)
if (_context.router().isFinalShutdownInProgress())
return;
for (int i = 0; i < 5; i++) { // try this 5 times, in case it OOMs
try {
log.log(Log.CRIT, "free mem: " + Runtime.getRuntime().freeMemory() +
" total mem: " + Runtime.getRuntime().totalMemory());
break; // w00t
} catch (OutOfMemoryError oome) {
// gobble
}
}
log.log(Log.CRIT, "To prevent future shutdowns, increase wrapper.java.maxmemory in $I2P/wrapper.config");
try {
// prevent multiple parallel shutdowns (when you OOM, you OOM a lot...)
if (_context.router().isFinalShutdownInProgress())
return;
} catch (OutOfMemoryError oome) {}
try {
// Only do this once
if (_wasCalled.getAndSet(true))
return;
} catch (OutOfMemoryError oome) {}
try {
// boost priority to help us shut down
// this may or may not do anything...
Thread.currentThread().setPriority(Thread.MAX_PRIORITY - 1);
} catch (OutOfMemoryError oome) {}
try {
Router.clearCaches();
} catch (OutOfMemoryError oome) {}
Log log = null;
try {
log = _context.logManager().getLog(Router.class);
log.log(Log.CRIT, "Thread ran out of memory, shutting down I2P", oom);
log.log(Log.CRIT, "free mem: " + Runtime.getRuntime().freeMemory() +
" total mem: " + Runtime.getRuntime().totalMemory());
if (_context.hasWrapper())
log.log(Log.CRIT, "To prevent future shutdowns, increase wrapper.java.maxmemory in $I2P/wrapper.config");
} catch (OutOfMemoryError oome) {}
try {
ThreadDump.dump(_context, 1);
} catch (OutOfMemoryError oome) {}
try {
_context.router().eventLog().addEvent(EventLog.OOM);
} catch (OutOfMemoryError oome) {}
_context.router().shutdown(Router.EXIT_OOM);
}
}

View File

@@ -3,6 +3,7 @@ package net.i2p.router.tasks;
import net.i2p.router.Router;
import net.i2p.router.RouterClock;
import net.i2p.router.RouterContext;
import net.i2p.router.util.EventLog;
import net.i2p.util.Log;
/**
@@ -16,6 +17,7 @@ public class Restarter implements Runnable {
}
public void run() {
_context.router().eventLog().addEvent(EventLog.SOFT_RESTART);
Log log = _context.logManager().getLog(Router.class);
log.error("Stopping the router for a restart...");
log.logAlways(Log.WARN, "Stopping the client manager");

View File

@@ -64,7 +64,7 @@ public class RouterWatchdog implements Runnable {
private boolean shutdownOnHang() {
// prop default false
if (!Boolean.valueOf(_context.getProperty("watchdog.haltOnHang")).booleanValue())
if (!_context.getBooleanProperty("watchdog.haltOnHang"))
return false;
// Client manager starts complaining after 10 minutes, and we run every minute,
@@ -113,16 +113,7 @@ public class RouterWatchdog implements Runnable {
// This works on linux...
// It won't on windows, and we can't call i2prouter.bat either, it does something
// completely different...
if (_context.hasWrapper() && !SystemVersion.isWindows()) {
ShellCommand sc = new ShellCommand();
File i2pr = new File(_context.getBaseDir(), "i2prouter");
String[] args = new String[2];
args[0] = i2pr.getAbsolutePath();
args[1] = "dump";
boolean success = sc.executeSilentAndWaitTimed(args, 10);
if (success)
_log.log(Log.CRIT, "Threads dumped to wrapper log");
}
ThreadDump.dump(_context, 10);
}
}
}

View File

@@ -0,0 +1,43 @@
package net.i2p.router.tasks;
import java.io.File;
import net.i2p.I2PAppContext;
import net.i2p.util.ShellCommand;
import net.i2p.util.Log;
import net.i2p.util.SystemVersion;
/**
* Only works with wrapper on non-windows platforms
*
* @since 0.9.3 moved from RouterWatchdog
*/
abstract class ThreadDump {
/**
* Signal the wrapper to asynchronously dump threads to wrapper.log.
* It waits for the signal to complete (which should be fast)
* but does not wait for the dump itself.
*
* @param secondsToWait if <= 0, don't wait
* @return success, false if windows or no wrapper, true if secondsToWait <= 0,
false if timed out, dump result otherwise
*/
public static boolean dump(I2PAppContext context, int secondsToWait) {
if (SystemVersion.isWindows() || !context.hasWrapper())
return false;
ShellCommand sc = new ShellCommand();
File i2pr = new File(context.getBaseDir(), "i2prouter");
String[] args = new String[2];
args[0] = i2pr.getAbsolutePath();
args[1] = "dump";
boolean success = sc.executeSilentAndWaitTimed(args, secondsToWait);
if (secondsToWait <= 0)
success = true;
if (success) {
Log log = context.logManager().getLog(ThreadDump.class);
log.log(Log.CRIT, "Threads dumped to wrapper log");
}
return success;
}
}

View File

@@ -72,7 +72,7 @@ public class RouterTimestamper extends Timestamper {
// so the property must be set at startup.
// We still need to be instantiated since the router calls clock().getTimestamper().waitForInitialization()
String disabled = ctx.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
if (Boolean.valueOf(disabled).booleanValue()) {
if (Boolean.parseBoolean(disabled)) {
_initialized = true;
return;
}
@@ -321,7 +321,7 @@ public class RouterTimestamper extends Timestamper {
_context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY));
String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
_disabled = Boolean.valueOf(disabled).booleanValue();
_disabled = Boolean.parseBoolean(disabled);
_concurringServers = Math.min(4, Math.max(1,
_context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS)));

View File

@@ -331,7 +331,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " auto: " + enabled + " status: " + status);
if (enabled.equals("always") ||
(Boolean.valueOf(enabled).booleanValue() && status == STATUS_OK)) {
(Boolean.parseBoolean(enabled) && status == STATUS_OK)) {
String nhost = UDPAddr.getOption(UDPAddress.PROP_HOST);
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: " + nhost);
@@ -354,7 +354,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
changed = true;
} else if (ohost == null || ohost.length() <= 0) {
return;
} else if (Boolean.valueOf(enabled).booleanValue() && status != STATUS_OK) {
} else if (Boolean.parseBoolean(enabled) && status != STATUS_OK) {
// UDP transitioned to not-OK, turn off NTCP address
// This will commonly happen at startup if we were initially OK
// because UPnP was successful, but a subsequent SSU Peer Test determines

View File

@@ -91,7 +91,7 @@ class GeoIP {
*/
/******
public void lookup() {
if (! Boolean.valueOf(_context.getProperty(PROP_GEOIP_ENABLED, "true")).booleanValue()) {
if (! _context.getBooleanPropertyDefaultTrue(PROP_GEOIP_ENABLED)) {
_pendingSearch.clear();
return;
}
@@ -105,7 +105,7 @@ class GeoIP {
* Results will be added to the table and available via get() after completion.
*/
public void blockingLookup() {
if (! Boolean.valueOf(_context.getProperty(PROP_GEOIP_ENABLED, "true")).booleanValue()) {
if (! _context.getBooleanPropertyDefaultTrue(PROP_GEOIP_ENABLED)) {
_pendingSearch.clear();
return;
}

View File

@@ -362,7 +362,7 @@ public class TransportManager implements TransportEventListener {
}
// Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here
if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 &&
Boolean.valueOf(_context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)).booleanValue()) {
_context.getBooleanProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)) {
Transport udp = getTransport(UDPTransport.STYLE);
if (udp != null)
port = t.getRequestedPort();
@@ -545,7 +545,7 @@ public class TransportManager implements TransportEventListener {
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ").append(_("The number of pending sends which exceed congestion window")).append("<br>\n" +
"<b id=\"def.ssthresh\">SST</b>: ").append(_("The slow start threshold")).append("<br>\n" +
"<b id=\"def.rtt\">RTT</b>: ").append(_("The round trip time in milliseconds")).append("<br>\n" +
"<b id=\"def.dev\">").append(_("Dev")).append("</b>: ").append(_("The standard deviation of the round trip time in milliseconds")).append("<br>\n" +
//"<b id=\"def.dev\">").append(_("Dev")).append("</b>: ").append(_("The standard deviation of the round trip time in milliseconds")).append("<br>\n" +
"<b id=\"def.rto\">RTO</b>: ").append(_("The retransmit timeout in milliseconds")).append("<br>\n" +
"<b id=\"def.mtu\">MTU</b>: ").append(_("Current maximum send packet size / estimated maximum receive packet size (bytes)")).append("<br>\n" +
"<b id=\"def.send\">").append(_("TX")).append("</b>: ").append(_("The total number of packets sent to the peer")).append("<br>\n" +

View File

@@ -78,7 +78,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
private volatile boolean thinksWeAreDoubleNatted = false;
/** List of ports we want to forward */
private Set<ForwardPort> portsToForward;
private final Set<ForwardPort> portsToForward;
/** List of ports we have actually forwarded */
private final Set<ForwardPort> portsForwarded;
/** Callback to call when a forward fails or succeeds */
@@ -88,13 +88,14 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
super();
_context = context;
_log = _context.logManager().getLog(UPnP.class);
portsToForward = new HashSet<ForwardPort>();
portsForwarded = new HashSet<ForwardPort>();
addDeviceChangeListener(this);
}
public boolean runPlugin() {
public synchronized boolean runPlugin() {
synchronized(lock) {
portsToForward = null;
portsToForward.clear();
}
return super.start();
}
@@ -102,9 +103,9 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
/**
* WARNING - Blocking up to 2 seconds
*/
public void terminate() {
public synchronized void terminate() {
synchronized(lock) {
portsToForward = null;
portsToForward.clear();
}
// this gets spun off in a thread...
unregisterPortMappings();
@@ -221,9 +222,10 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
private void registerPortMappings() {
Set ports;
synchronized(lock) {
ports = portsToForward;
ports = new HashSet(portsForwarded);
}
if(ports == null) return;
if (ports.isEmpty())
return;
registerPorts(ports);
}
@@ -281,6 +283,8 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
synchronized(lock) {
ports = new HashSet(portsForwarded);
}
if (ports.isEmpty())
return;
this.unregisterPorts(ports);
}
@@ -528,17 +532,15 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
if(upstreamMaxBitRate > 0)
sb.append("<br>").append(_("UPnP reports the maximum upstream bit rate is {0}bits/sec", DataHelper.formatSize2(upstreamMaxBitRate)));
synchronized(lock) {
if(portsToForward != null) {
for(ForwardPort port : portsToForward) {
sb.append("<br>");
if(portsForwarded.contains(port))
// {0} is TCP or UDP
// {1,number,#####} prevents 12345 from being output as 12,345 in the English locale.
// If you want the digit separator in your locale, translate as {1}.
sb.append(_("{0} port {1,number,#####} was successfully forwarded by UPnP.", protoToString(port.protocol), port.portNumber));
else
sb.append(_("{0} port {1,number,#####} was not forwarded by UPnP.", protoToString(port.protocol), port.portNumber));
}
for(ForwardPort port : portsToForward) {
sb.append("<br>");
if(portsForwarded.contains(port))
// {0} is TCP or UDP
// {1,number,#####} prevents 12345 from being output as 12,345 in the English locale.
// If you want the digit separator in your locale, translate as {1}.
sb.append(_("{0} port {1,number,#####} was successfully forwarded by UPnP.", protoToString(port.protocol), port.portNumber));
else
sb.append(_("{0} port {1,number,#####} was not forwarded by UPnP.", protoToString(port.protocol), port.portNumber));
}
}
@@ -726,13 +728,13 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
_log.error("ForwardPortCallback changed from "+forwardCallback+" to "+cb+" - using new value, but this is very strange!");
}
forwardCallback = cb;
if(portsToForward == null || portsToForward.isEmpty()) {
portsToForward = ports;
if (portsToForward.isEmpty()) {
portsToForward.addAll(ports);
portsToForwardNow = ports;
portsToDumpNow = null;
} else if(ports.isEmpty()) {
portsToDumpNow = portsToForward;
portsToForward = ports;
portsToForward.clear();
portsToForwardNow = null;
} else {
// Some ports to keep, some ports to dump
@@ -760,7 +762,8 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
portsToDumpNow.add(port);
}
}
portsToForward = ports;
portsToForward.clear();
portsToForward.addAll(ports);
}
if(_router == null) {
if (_log.shouldLog(Log.WARN))
@@ -768,9 +771,9 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
return; // When one is found, we will do the forwards
}
}
if(portsToDumpNow != null)
if(portsToDumpNow != null && !portsToDumpNow.isEmpty())
unregisterPorts(portsToDumpNow);
if(portsToForwardNow != null)
if(portsToForwardNow != null && !portsToForwardNow.isEmpty())
registerPorts(portsToForwardNow);
}

View File

@@ -6,10 +6,12 @@ package net.i2p.router.transport;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.i2p.router.RouterContext;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.Translate;
@@ -59,15 +61,42 @@ class UPnPManager {
_upnpCallback = new UPnPCallback();
}
/**
* Blocking, may take a while
*/
public synchronized void start() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("UPnP Start");
if (!_isRunning)
_isRunning = _upnp.runPlugin();
if (!_isRunning)
_log.error("UPnP start failed - port conflict?");
if (!_isRunning) {
long b = _context.clock().now();
try {
_isRunning = _upnp.runPlugin();
if (_log.shouldLog(Log.INFO))
_log.info("UPnP runPlugin took " + (_context.clock().now() - b));
} catch (Exception e) {
// NPE in UPnP (ticket #728), can't let it bring us down
_log.error("UPnP error, please report", e);
}
}
if (!_isRunning) {
// Do we have a non-loopback, non-broadcast address?
// If not, that's why it failed (HTTPServer won't start)
Set<String> addrs = Addresses.getAddresses(true, false);
addrs.remove("0.0.0.0");
for (Iterator<String> iter = addrs.iterator(); iter.hasNext(); ) {
if (iter.next().startsWith("127."))
iter.remove();
}
if (addrs.isEmpty())
_log.logAlways(Log.WARN, "UPnP start failed - no network connection?");
else
_log.error("UPnP start failed - port conflict?");
}
}
/**
* Blocking, may take a while
*/
public synchronized void stop() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("UPnP Stop");
@@ -86,8 +115,17 @@ class UPnPManager {
public void update(Map<String, Integer> ports) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("UPnP Update with " + ports.size() + " ports");
if (!_isRunning)
return;
//synchronized(this) {
// TODO
// called too often and may block for too long
// may not have started if net was disconnected previously
//if (!_isRunning && !ports.isEmpty())
// start();
if (!_isRunning)
return;
//}
Set<ForwardPort> forwards = new HashSet(ports.size());
for (Map.Entry<String, Integer> entry : ports.entrySet()) {
String style = entry.getKey();

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.data.RouterIdentity;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.util.ConcurrentHashSet;
@@ -499,7 +500,8 @@ class EventPumper implements Runnable {
return;
}
// BUGFIX for firewalls. --Sponge
chan.socket().setKeepAlive(true);
if (_context.commSystem().getReachabilityStatus() != CommSystemFacade.STATUS_OK)
chan.socket().setKeepAlive(true);
SelectionKey ckey = chan.register(_selector, SelectionKey.OP_READ);
new NTCPConnection(_context, _transport, chan, ckey);
@@ -519,7 +521,8 @@ class EventPumper implements Runnable {
_log.debug("processing connect for " + con + ": connected? " + connected);
if (connected) {
// BUGFIX for firewalls. --Sponge
chan.socket().setKeepAlive(true);
if (_context.commSystem().getReachabilityStatus() != CommSystemFacade.STATUS_OK)
chan.socket().setKeepAlive(true);
con.setKey(key);
con.outboundConnected();
_context.statManager().addRateData("ntcp.connectSuccessful", 1);

View File

@@ -990,9 +990,10 @@ class NTCPConnection {
if (clearMessage) {
// see synchronization comments in prepareNextWriteFast()
synchronized (_outbound) {
if (_currentOutbound != null)
if (_currentOutbound != null) {
msg = _currentOutbound;
_currentOutbound = null;
_currentOutbound = null;
}
}
if (msg != null) {
_lastSendTime = System.currentTimeMillis();
@@ -1065,11 +1066,11 @@ class NTCPConnection {
//_sendBps15s = (0.955f)*_sendBps15s + (0.045f)*((float)sent*1000f)/(float)time;
//_recvBps15s = (0.955f)*_recvBps15s + (0.045f)*((float)recv*1000)/(float)time;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Rates updated to "
+ _sendBps + '/' + _recvBps + "Bps in/out "
//+ _sendBps15s + "/" + _recvBps15s + "Bps in/out 15s after "
+ sent + '/' + recv + " in " + DataHelper.formatDuration(time));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Rates updated to "
// + _sendBps + '/' + _recvBps + "Bps in/out "
// //+ _sendBps15s + "/" + _recvBps15s + "Bps in/out 15s after "
// + sent + '/' + recv + " in " + DataHelper.formatDuration(time));
}
}

View File

@@ -17,6 +17,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
@@ -68,6 +69,13 @@ public class NTCPTransport extends TransportImpl {
private long _lastBadSkew;
private static final long[] RATES = { 10*60*1000 };
/**
* To prevent trouble. To be raised to 1024 in 0.9.4.
*
* @since 0.9.3
*/
private static final int MIN_PEER_PORT = 500;
// Opera doesn't have the char, TODO check UA
//private static final String THINSP = "&thinsp;/&thinsp;";
private static final String THINSP = " / ";
@@ -140,7 +148,7 @@ public class NTCPTransport extends TransportImpl {
_context.statManager().createRateStat("ntcp.writeError", "", "ntcp", RATES);
_establishing = new ConcurrentHashSet(16);
_conLock = new Object();
_conByIdent = new HashMap(64);
_conByIdent = new ConcurrentHashMap(64);
_finisher = new NTCPSendFinisher(ctx, this);
@@ -160,7 +168,7 @@ public class NTCPTransport extends TransportImpl {
_context.statManager().addRateData("ntcp.inboundEstablished", 1);
markReachable(con.getRemotePeer().calculateHash(), true);
//_context.shitlist().unshitlistRouter(con.getRemotePeer().calculateHash());
NTCPConnection old = null;
NTCPConnection old;
synchronized (_conLock) {
old = _conByIdent.put(con.getRemotePeer().calculateHash(), con);
}
@@ -263,6 +271,7 @@ public class NTCPTransport extends TransportImpl {
public void afterSend(OutNetMessage msg, boolean sendSuccessful, boolean allowRequeue, long msToSend) {
super.afterSend(msg, sendSuccessful, allowRequeue, msToSend);
}
public TransportBid bid(RouterInfo toAddress, long dataSize) {
if (!isAlive())
return null;
@@ -299,7 +308,7 @@ public class NTCPTransport extends TransportImpl {
return null;
}
byte[] ip = addr.getIP();
if ( (addr.getPort() <= 0) || (ip == null) ) {
if ( (addr.getPort() < MIN_PEER_PORT) || (ip == null) ) {
_context.statManager().addRateData("ntcp.connectFailedInvalidPort", 1);
markUnreachable(peer);
//_context.shitlist().shitlistRouter(toAddress.getIdentity().calculateHash(), "Invalid NTCP address", STYLE);
@@ -354,26 +363,23 @@ public class NTCPTransport extends TransportImpl {
@Override
public boolean isEstablished(Hash dest) {
synchronized (_conLock) {
NTCPConnection con = _conByIdent.get(dest);
return (con != null) && con.isEstablished() && !con.isClosed();
}
}
@Override
public boolean isBacklogged(Hash dest) {
synchronized (_conLock) {
NTCPConnection con = _conByIdent.get(dest);
return (con != null) && con.isEstablished() && con.tooBacklogged();
}
}
void removeCon(NTCPConnection con) {
NTCPConnection removed = null;
synchronized (_conLock) {
RouterIdentity ident = con.getRemotePeer();
if (ident != null)
RouterIdentity ident = con.getRemotePeer();
if (ident != null) {
synchronized (_conLock) {
removed = _conByIdent.remove(ident.calculateHash());
}
}
if ( (removed != null) && (removed != con) ) {// multiple cons, close 'em both
if (_log.shouldLog(Log.WARN))
@@ -388,19 +394,17 @@ public class NTCPTransport extends TransportImpl {
*
*/
@Override
public int countActivePeers() { synchronized (_conLock) { return _conByIdent.size(); } }
public int countActivePeers() { return _conByIdent.size(); }
/**
* How many peers are we actively sending messages to (this minute)
*/
@Override
public int countActiveSendPeers() {
int active = 0;
synchronized (_conLock) {
for (Iterator iter = _conByIdent.values().iterator(); iter.hasNext(); ) {
NTCPConnection con = (NTCPConnection)iter.next();
for (NTCPConnection con : _conByIdent.values()) {
if ( (con.getTimeSinceSend() <= 60*1000) || (con.getTimeSinceReceive() <= 60*1000) )
active++;
}
}
return active;
}
@@ -416,16 +420,9 @@ public class NTCPTransport extends TransportImpl {
*/
@Override
public Vector<Long> getClockSkews() {
Vector<NTCPConnection> peers = new Vector();
Vector<Long> skews = new Vector();
synchronized (_conLock) {
peers.addAll(_conByIdent.values());
}
for (Iterator<NTCPConnection> iter = peers.iterator(); iter.hasNext(); ) {
NTCPConnection con = iter.next();
for (NTCPConnection con : _conByIdent.values()) {
if (con.isEstablished())
skews.addElement(Long.valueOf(con.getClockSkew()));
}
@@ -551,11 +548,14 @@ public class NTCPTransport extends TransportImpl {
ServerSocketChannel chan = ServerSocketChannel.open();
chan.configureBlocking(false);
int port = _myAddress.getPort();
if (port > 0 && port < 1024)
_log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended");
InetSocketAddress addr = null;
if(bindToAddr==null) {
addr = new InetSocketAddress(_myAddress.getPort());
addr = new InetSocketAddress(port);
} else {
addr = new InetSocketAddress(bindToAddr, _myAddress.getPort());
addr = new InetSocketAddress(bindToAddr, port);
if (_log.shouldLog(Log.WARN))
_log.warn("Binding only to " + bindToAddr);
}
@@ -603,6 +603,7 @@ public class NTCPTransport extends TransportImpl {
void establishing(NTCPConnection con) {
_establishing.add(con);
}
/**
* called in the EventPumper no more than once a second or so, closing
* any unconnected/unestablished connections
@@ -694,12 +695,10 @@ public class NTCPTransport extends TransportImpl {
@Override
public short getReachabilityStatus() {
if (isAlive() && _myAddress != null) {
synchronized (_conLock) {
for (NTCPConnection con : _conByIdent.values()) {
if (con.isInbound())
return CommSystemFacade.STATUS_OK;
}
}
}
return CommSystemFacade.STATUS_UNKNOWN;
}
@@ -727,17 +726,17 @@ public class NTCPTransport extends TransportImpl {
// will this work?
replaceAddress(null);
}
public static final String STYLE = "NTCP";
public void renderStatusHTML(java.io.Writer out, int sortFlags) throws IOException {}
@Override
public void renderStatusHTML(java.io.Writer out, String urlBase, int sortFlags) throws IOException {
TreeSet peers = new TreeSet(getComparator(sortFlags));
synchronized (_conLock) {
peers.addAll(_conByIdent.values());
}
long offsetTotal = 0;
peers.addAll(_conByIdent.values());
long offsetTotal = 0;
float bpsSend = 0;
float bpsRecv = 0;
long totalUptime = 0;
@@ -838,6 +837,7 @@ public class NTCPTransport extends TransportImpl {
}
private static final NumberFormat _rateFmt = new DecimalFormat("#,##0.00");
private static String formatRate(float rate) {
synchronized (_rateFmt) { return _rateFmt.format(rate); }
}
@@ -858,14 +858,12 @@ public class NTCPTransport extends TransportImpl {
public static final AlphaComparator instance() { return _instance; }
}
private static class PeerComparator implements Comparator {
public int compare(Object lhs, Object rhs) {
if ( (lhs == null) || (rhs == null) || !(lhs instanceof NTCPConnection) || !(rhs instanceof NTCPConnection))
throw new IllegalArgumentException("rhs = " + rhs + " lhs = " + lhs);
return compare((NTCPConnection)lhs, (NTCPConnection)rhs);
}
protected int compare(NTCPConnection l, NTCPConnection r) {
private static class PeerComparator implements Comparator<NTCPConnection> {
public int compare(NTCPConnection l, NTCPConnection r) {
if (l == null || r == null)
throw new IllegalArgumentException();
// base64 retains binary ordering
// UM, no it doesn't, but close enough
return l.getRemotePeer().calculateHash().toBase64().compareTo(r.getRemotePeer().calculateHash().toBase64());
}
}

View File

@@ -80,9 +80,12 @@ class Writer {
}
private class Runner implements Runnable {
private boolean _stop;
public Runner() { _stop = false; }
private volatile boolean _stop;
public Runner() {}
public void stop() { _stop = true; }
public void run() {
if (_log.shouldLog(Log.INFO)) _log.info("Starting writer");
NTCPConnection con = null;

View File

@@ -28,7 +28,7 @@ class ACKSender implements Runnable {
private static final long POISON_PS = -9999999999l;
/** how frequently do we want to send ACKs to a peer? */
static final int ACK_FREQUENCY = 500;
static final int ACK_FREQUENCY = 350;
public ACKSender(RouterContext ctx, UDPTransport transport) {
_context = ctx;
@@ -73,12 +73,12 @@ class ACKSender implements Runnable {
_peersToACK.clear();
}
private long ackFrequency(long timeSinceACK, long rtt) {
private static long ackFrequency(long timeSinceACK, long rtt) {
// if we are actively pumping lots of data to them, we can depend upon
// the unsentACKThreshold to figure out when to send an ACK instead of
// using the timer, so we can set the timeout/frequency higher
if (timeSinceACK < 2*1000)
return Math.max(rtt/2, 500);
return Math.max(rtt/2, ACK_FREQUENCY);
else
return ACK_FREQUENCY;
}
@@ -162,15 +162,15 @@ class ACKSender implements Runnable {
}
if (!ackBitfields.isEmpty()) {
_context.statManager().addRateData("udp.sendACKCount", ackBitfields.size(), 0);
_context.statManager().addRateData("udp.sendACKCount", ackBitfields.size());
if (remaining > 0)
_context.statManager().addRateData("udp.sendACKRemaining", remaining, 0);
_context.statManager().addRateData("udp.sendACKRemaining", remaining);
// set above before the break
//now = _context.clock().now();
if (lastSend < 0)
lastSend = now - 1;
_context.statManager().addRateData("udp.ackFrequency", now-lastSend, now-wanted);
//_context.statManager().getStatLog().addData(peer.getRemoteHostId().toString(), "udp.peer.sendACKCount", ackBitfields.size(), 0);
//_context.statManager().getStatLog().addData(peer.getRemoteHostId().toString(), "udp.peer.sendACKCount", ackBitfields.size());
UDPPacket ack = _builder.buildACK(peer, ackBitfields);
ack.markType(1);
ack.setFragmentCount(-1);
@@ -193,7 +193,7 @@ class ACKSender implements Runnable {
ackPeer(peer);
}
} else {
_context.statManager().addRateData("udp.abortACK", 1, 0);
_context.statManager().addRateData("udp.abortACK", 1);
}
}
}

View File

@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
@@ -21,7 +22,6 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.OutNetMessage;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import static net.i2p.router.transport.udp.InboundEstablishState.InboundState.*;
import static net.i2p.router.transport.udp.OutboundEstablishState.OutboundState.*;
@@ -86,7 +86,9 @@ class EstablishmentManager {
private int _activity;
/** max outbound in progress - max inbound is half of this */
private static final int DEFAULT_MAX_CONCURRENT_ESTABLISH = 30;
private final int DEFAULT_MAX_CONCURRENT_ESTABLISH;
private static final int DEFAULT_LOW_MAX_CONCURRENT_ESTABLISH = 20;
private static final int DEFAULT_HIGH_MAX_CONCURRENT_ESTABLISH = 150;
private static final String PROP_MAX_CONCURRENT_ESTABLISH = "i2np.udp.maxConcurrentEstablish";
/** max pending outbound connections (waiting because we are at MAX_CONCURRENT_ESTABLISH) */
@@ -132,6 +134,9 @@ class EstablishmentManager {
_outboundByClaimedAddress = new ConcurrentHashMap();
_outboundByHash = new ConcurrentHashMap();
_activityLock = new Object();
DEFAULT_MAX_CONCURRENT_ESTABLISH = Math.max(DEFAULT_LOW_MAX_CONCURRENT_ESTABLISH,
Math.min(DEFAULT_HIGH_MAX_CONCURRENT_ESTABLISH,
ctx.bandwidthLimiter().getOutboundKBytesPerSecond() / 2));
_context.statManager().createRateStat("udp.inboundEstablishTime", "How long it takes for a new inbound session to be established", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.outboundEstablishTime", "How long it takes for a new outbound session to be established", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundEstablishFailedState", "What state a failed inbound establishment request fails in", "udp", UDPTransport.RATES);
@@ -142,6 +147,7 @@ class EstablishmentManager {
_context.statManager().createRateStat("udp.establishDropped", "Dropped an inbound establish message", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.establishRejected", "How many pending outbound connections are there when we refuse to add any more?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.establishOverflow", "How many messages were queued up on a pending connection when it was too much?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.establishBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
// following are for PeerState
_context.statManager().createRateStat("udp.congestionOccurred", "How large the cwin was when congestion occurred (duration == sendBps)", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.congestedRTO", "retransmission timeout after congestion (duration == rtt dev)", "udp", UDPTransport.RATES);
@@ -247,6 +253,7 @@ class EstablishmentManager {
_transport.failed(msg, "Remote peer's IP isn't valid");
_transport.markUnreachable(toHash);
//_context.shitlist().shitlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE);
_context.statManager().addRateData("udp.establishBadIP", 1);
return;
}
@@ -413,9 +420,9 @@ class EstablishmentManager {
*
*/
void receiveSessionRequest(RemoteHostId from, UDPPacketReader reader) {
if (!_transport.isValid(from.getIP())) {
if (from.getPort() < UDPTransport.MIN_PEER_PORT || !_transport.isValid(from.getIP())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive session request from invalid IP: " + from);
_log.warn("Receive session request from invalid: " + from);
return;
}
@@ -435,6 +442,7 @@ class EstablishmentManager {
if (_context.blocklist().isBlocklisted(from.getIP())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive session request from blocklisted IP: " + from);
_context.statManager().addRateData("udp.establishBadIP", 1);
return; // drop the packet
}
if (!_transport.allowConnection())
@@ -450,14 +458,9 @@ class EstablishmentManager {
}
if (isNew) {
// we don't expect inbound connections when hidden, but it could happen
// Don't offer if we are approaching max connections. While Relay Intros do not
// count as connections, we have to keep the connection to this peer up longer if
// we are offering introductions.
// Don't offer to relay to privileged ports.
if ((!_context.router().isHidden()) && (!_transport.introducersRequired()) && _transport.haveCapacity() &&
state.getSentPort() >= 1024 &&
!((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) {
// TODO if already we have their RI, only offer if they need it (no 'C' cap)
if (_transport.canIntroduce() && state.getSentPort() >= 1024) {
// ensure > 0
long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
state.setSentRelayTag(tag);
@@ -573,7 +576,14 @@ class EstablishmentManager {
}
}
if (_outboundStates.size() < getMaxConcurrentEstablish() && !_queuedOutbound.isEmpty()) {
// in theory shouldn't need locking, but
// getting IllegalStateExceptions on old Java 5,
// which hoses this state.
synchronized(_queuedOutbound) {
locked_admitQueued();
}
}
//remaining = _queuedOutbound.size();
//if (admitted > 0)
@@ -600,6 +610,7 @@ class EstablishmentManager {
// ok, active shrunk, lets let some queued in.
Map.Entry<RemoteHostId, List<OutNetMessage>> entry = iter.next();
// java 5 IllegalStateException here
iter.remove();
RemoteHostId to = entry.getKey();
List<OutNetMessage> allQueued = entry.getValue();
@@ -709,7 +720,7 @@ class EstablishmentManager {
private void sendInboundComplete(PeerState peer) {
// SimpleTimer.getInstance().addEvent(new PublishToNewInbound(peer), 10*1000);
if (_log.shouldLog(Log.INFO))
_log.info("Completing to the peer after confirm: " + peer);
_log.info("Completing to the peer after IB confirm: " + peer);
DeliveryStatusMessage dsm = new DeliveryStatusMessage(_context);
dsm.setArrival(Router.NETWORK_ID); // overloaded, sure, but future versions can check this
// This causes huge values in the inNetPool.droppedDeliveryStatusDelay stat
@@ -803,29 +814,10 @@ class EstablishmentManager {
/** the relay tag is a 4-byte field in the protocol */
public static final long MAX_TAG_VALUE = 0xFFFFFFFFl;
/**
* This may be called more than once
*/
private void sendCreated(InboundEstablishState state) {
long now = _context.clock().now();
// This is usually handled in receiveSessionRequest() above, except, I guess,
// if the session isn't new and we are going through again.
// Don't offer if we are approaching max connections (see comments above)
// Also don't offer if we are floodfill, as this extends the max idle time
// and we will have lots of incoming conns
if ((!_context.router().isHidden()) && (!_transport.introducersRequired()) && _transport.haveCapacity() &&
!((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) {
// offer to relay
// (perhaps we should check our bw usage and/or how many peers we are
// already offering introducing?)
if (state.getSentRelayTag() == 0) {
// ensure > 0
state.setSentRelayTag(1 + _context.random().nextLong(MAX_TAG_VALUE));
} else {
// don't change it, since we've already prepared our sig
}
} else {
// don't offer to relay
state.setSentRelayTag(0);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send created to: " + state);
@@ -901,15 +893,14 @@ class EstablishmentManager {
byte ip[] = new byte[sz];
reader.getRelayResponseReader().readCharlieIP(ip, 0);
int port = reader.getRelayResponseReader().readCharliePort();
if ((!isValid(ip, port)) || (!isValid(bob.getIP(), bob.getPort()))) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad relay resp from " + bob + " for " + Addresses.toString(ip, port));
_context.statManager().addRateData("udp.relayBadIP", 1);
return;
}
InetAddress addr = null;
try {
if (!_transport.isValid(ip))
throw new UnknownHostException("non-public IP");
// let's not relay to a privileged port, sounds like trouble
if (port < 1024 || port > 65535)
throw new UnknownHostException("bad port " + port);
if (Arrays.equals(ip, _transport.getExternalIP()))
throw new UnknownHostException("relay myself");
addr = InetAddress.getByAddress(ip);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
@@ -943,7 +934,20 @@ class EstablishmentManager {
}
notifyActivity();
}
/**
* Are IP and port valid?
* Refuse anybody in the same /16
* @since 0.9.3
*/
private boolean isValid(byte[] ip, int port) {
return port >= 1024 &&
port <= 65535 &&
_transport.isValid(ip) &&
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
(!_context.blocklist().isBlocklisted(ip));
}
/**
* Note that while a SessionConfirmed could in theory be fragmented,
* in practice a RouterIdentity is 387 bytes and a single fragment is 512 bytes max,

View File

@@ -0,0 +1,44 @@
package net.i2p.router.transport.udp;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Count IPs
*
* @since 0.9.3
*/
class IPThrottler {
private ObjectCounter<Integer> _counter;
private final int _max;
public IPThrottler(int max, long time) {
_max = max;
_counter = new ObjectCounter();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), time);
}
/**
* Increments before checking
* @return true if ip.length != 4
*/
public boolean shouldThrottle(byte[] ip) {
if (ip.length != 4)
return true;
return _counter.increment(toInt(ip)) > _max;
}
private static Integer toInt(byte ip[]) {
int rv = 0;
for (int i = 0; i < 4; i++)
rv |= (ip[i] & 0xff) << ((3-i)*8);
return Integer.valueOf(rv);
}
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
_counter.clear();
}
}
}

View File

@@ -3,7 +3,6 @@ package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -12,6 +11,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey;
@@ -43,10 +43,10 @@ class IntroductionManager {
private static final int MAX_INBOUND = 20;
/**
* TODO this should be enforced in EstablishmentManager, it isn't now.
* This is enforced in EstablishmentManager
* @since 0.8.11
*/
private static final int MAX_OUTBOUND = 100;
public static final int MAX_OUTBOUND = 100;
/** Max one per target in this time */
private static final long PUNCH_CLEAN_TIME = 5*1000;
@@ -64,6 +64,7 @@ class IntroductionManager {
ctx.statManager().createRateStat("udp.receiveRelayIntro", "How often we get a relayed request for us to talk to someone?", "udp", UDPTransport.RATES);
ctx.statManager().createRateStat("udp.receiveRelayRequest", "How often we receive a good request to relay to someone else?", "udp", UDPTransport.RATES);
ctx.statManager().createRateStat("udp.receiveRelayRequestBadTag", "Received relay requests with bad/expired tag", "udp", UDPTransport.RATES);
ctx.statManager().createRateStat("udp.relayBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
}
public void reset() {
@@ -160,7 +161,7 @@ class IntroductionManager {
}
byte[] ip = cur.getRemoteIP();
int port = cur.getRemotePort();
if (ip == null || !TransportImpl.isPubliclyRoutable(ip) || port < 1024 || port > 65535)
if (!isValid(ip, port))
continue;
if (_log.shouldLog(Log.INFO))
_log.info("Picking introducer: " + cur);
@@ -185,14 +186,15 @@ class IntroductionManager {
*/
public void pingIntroducers() {
// Try to keep the connection up for two hours after we made anybody an introducer
long pingCutoff = _context.clock().now() - (105 * 60 * 1000);
long inactivityCutoff = _context.clock().now() - UDPTransport.MIN_EXPIRE_TIMEOUT;
long now = _context.clock().now();
long pingCutoff = now - (105 * 60 * 1000);
long inactivityCutoff = now - UDPTransport.MIN_EXPIRE_TIMEOUT;
for (PeerState cur : _inbound) {
if (cur.getIntroducerTime() > pingCutoff &&
cur.getLastSendTime() < inactivityCutoff) {
if (_log.shouldLog(Log.INFO))
_log.info("Pinging introducer: " + cur);
cur.setLastSendTime(_context.clock().now());
cur.setLastSendTime(now);
_transport.send(_builder.buildPing(cur));
}
}
@@ -207,6 +209,14 @@ class IntroductionManager {
int introducerCount() {
return _inbound.size();
}
/**
* @return number of peers we have volunteered to introduce
* @since 0.9.3
*/
int introducedCount() {
return _outbound.size();
}
/**
* We are Charlie and we got this from Bob.
@@ -218,7 +228,7 @@ class IntroductionManager {
void receiveRelayIntro(RemoteHostId bob, UDPPacketReader reader) {
if (_context.router().isHidden())
return;
_context.statManager().addRateData("udp.receiveRelayIntro", 1, 0);
_context.statManager().addRateData("udp.receiveRelayIntro", 1);
if (!_transport.allowConnection()) {
if (_log.shouldLog(Log.WARN))
@@ -230,23 +240,25 @@ class IntroductionManager {
byte ip[] = new byte[ipSize];
reader.getRelayIntroReader().readIP(ip, 0);
int port = reader.getRelayIntroReader().readPort();
if ((!isValid(ip, port)) || (!isValid(bob.getIP(), bob.getPort()))) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad relay intro from " + bob + " for " + Addresses.toString(ip, port));
_context.statManager().addRateData("udp.relayBadIP", 1);
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Receive relay intro from " + bob + " for " + Addresses.toString(ip, port));
InetAddress to = null;
try {
if (!_transport.isValid(ip))
throw new UnknownHostException("non-public IP");
// let's not punch to a privileged port, sounds like trouble
if (port < 1024 || port > 65535)
throw new UnknownHostException("bad port " + port);
if (Arrays.equals(ip, _transport.getExternalIP()))
throw new UnknownHostException("punch myself");
to = InetAddress.getByAddress(ip);
} catch (UnknownHostException uhe) {
// shitlist Bob?
if (_log.shouldLog(Log.WARN))
_log.warn("IP for alice to hole punch to is invalid", uhe);
_context.statManager().addRateData("udp.relayBadIP", 1);
return;
}
@@ -312,7 +324,23 @@ class IntroductionManager {
void receiveRelayRequest(RemoteHostId alice, UDPPacketReader reader) {
if (_context.router().isHidden())
return;
long tag = reader.getRelayRequestReader().readTag();
UDPPacketReader.RelayRequestReader rrReader = reader.getRelayRequestReader();
long tag = rrReader.readTag();
int ipSize = rrReader.readIPSize();
int port = rrReader.readPort();
// ip/port inside message should be 0:0, as it's unimplemented on send -
// see PacketBuilder.buildRelayRequest()
if (!isValid(alice.getIP(), alice.getPort()) || ipSize != 0 || port != 0) {
if (_log.shouldLog(Log.WARN)) {
byte ip[] = new byte[ipSize];
rrReader.readIP(ip, 0);
_log.warn("Bad relay req from " + alice + " for " + Addresses.toString(ip, port));
}
_context.statManager().addRateData("udp.relayBadIP", 1);
return;
}
PeerState charlie = get(tag);
if (charlie == null) {
if (_log.shouldLog(Log.INFO))
@@ -337,4 +365,17 @@ class IntroductionManager {
// send alice back charlie's info
_transport.send(_builder.buildRelayResponse(alice, charlie, reader.getRelayRequestReader().readNonce(), aliceIntroKey));
}
/**
* Are IP and port valid?
* Refuse anybody in the same /16
* @since 0.9.3
*/
private boolean isValid(byte[] ip, int port) {
return port >= 1024 &&
port <= 65535 &&
_transport.isValid(ip) &&
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
(!_context.blocklist().isBlocklisted(ip));
}
}

View File

@@ -61,10 +61,10 @@ class MessageReceiver {
//_cache = ByteCache.getInstance(64, I2NPMessage.MAX_SIZE);
_context.statManager().createRateStat("udp.inboundExpired", "How many messages were expired before reception?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundRemaining", "How many messages were remaining when a message is pulled off the complete queue?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.inboundReady", "How many messages were ready when a message is added to the complete queue?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReady", "How many messages were ready when a message is added to the complete queue?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReadTime", "How long it takes to parse in the completed fragments into a message?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReceiveProcessTime", "How long it takes to add the message to the transport?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.inboundLag", "How long the oldest ready message has been sitting on the queue (period is the queue size)?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundLag", "How long the oldest ready message has been sitting on the queue (period is the queue size)?", "udp", UDPTransport.RATES);
_alive = true;
}

View File

@@ -369,7 +369,7 @@ class OutboundEstablishState {
off += 4;
DataHelper.toLong(signed, off, 4, _receivedSignedOnTime);
boolean valid = _context.dsa().verifySignature(_receivedSignature, signed, _remotePeer.getSigningPublicKey());
if (!valid || _log.shouldLog(Log.DEBUG)) {
if (_log.shouldLog(Log.DEBUG) || (_log.shouldLog(Log.WARN) && !valid)) {
StringBuilder buf = new StringBuilder(128);
buf.append("Signed sessionCreated:");
buf.append(" Alice: ").append(Addresses.toString(_aliceIP, _alicePort));

View File

@@ -42,10 +42,20 @@ class OutboundMessageState implements CDPQEntry {
private long _seqNum;
public static final int MAX_MSG_SIZE = 32 * 1024;
/** is this enough for a high-bandwidth router? */
private static final int MAX_ENTRIES = 64;
/** would two caches, one for small and one for large messages, be better? */
private static final ByteCache _cache = ByteCache.getInstance(MAX_ENTRIES, MAX_MSG_SIZE);
private static final int CACHE4_BYTES = MAX_MSG_SIZE;
private static final int CACHE3_BYTES = CACHE4_BYTES / 4;
private static final int CACHE2_BYTES = CACHE3_BYTES / 4;
private static final int CACHE1_BYTES = CACHE2_BYTES / 4;
private static final int CACHE1_MAX = 256;
private static final int CACHE2_MAX = CACHE1_MAX / 4;
private static final int CACHE3_MAX = CACHE2_MAX / 4;
private static final int CACHE4_MAX = CACHE3_MAX / 4;
private static final ByteCache _cache1 = ByteCache.getInstance(CACHE1_MAX, CACHE1_BYTES);
private static final ByteCache _cache2 = ByteCache.getInstance(CACHE2_MAX, CACHE2_BYTES);
private static final ByteCache _cache3 = ByteCache.getInstance(CACHE3_MAX, CACHE3_BYTES);
private static final ByteCache _cache4 = ByteCache.getInstance(CACHE4_MAX, CACHE4_BYTES);
private static final long EXPIRATION = 10*1000;
@@ -72,6 +82,7 @@ class OutboundMessageState implements CDPQEntry {
* Called from UDPTransport
* TODO make two constructors, remove this, and make more things final
* @return success
* @throws IAE if too big
*/
public boolean initialize(I2NPMessage msg, PeerState peer) {
if (msg == null)
@@ -91,6 +102,7 @@ class OutboundMessageState implements CDPQEntry {
* Called from OutboundMessageFragments
* TODO make two constructors, remove this, and make more things final
* @return success
* @throws IAE if too big
*/
public boolean initialize(OutNetMessage m, I2NPMessage msg) {
if ( (m == null) || (msg == null) )
@@ -110,19 +122,13 @@ class OutboundMessageState implements CDPQEntry {
* Called from OutboundMessageFragments
* @param m null if msg is "injected"
* @return success
* @throws IAE if too big
*/
private boolean initialize(OutNetMessage m, I2NPMessage msg, PeerState peer) {
_message = m;
_peer = peer;
if (_messageBuf != null) {
_cache.release(_messageBuf);
_messageBuf = null;
}
_messageBuf = _cache.acquire();
int size = msg.getRawMessageSize();
if (size > _messageBuf.getData().length)
throw new IllegalArgumentException("Size too large! " + size + " in " + msg);
acquireBuf(size);
try {
int len = msg.toRawByteArray(_messageBuf.getData());
_messageBuf.setValid(len);
@@ -137,13 +143,49 @@ class OutboundMessageState implements CDPQEntry {
// _log.debug("Raw byte array for " + _messageId + ": " + Base64.encode(_messageBuf.getData(), 0, len));
return true;
} catch (IllegalStateException ise) {
_cache.release(_messageBuf);
_messageBuf = null;
_released = true;
releaseBuf();
return false;
}
}
/**
* @throws IAE if too big
* @since 0.9.3
*/
private void acquireBuf(int size) {
if (_messageBuf != null)
releaseBuf();
if (size <= CACHE1_BYTES)
_messageBuf = _cache1.acquire();
else if (size <= CACHE2_BYTES)
_messageBuf = _cache2.acquire();
else if (size <= CACHE3_BYTES)
_messageBuf = _cache3.acquire();
else if (size <= CACHE4_BYTES)
_messageBuf = _cache4.acquire();
else
throw new IllegalArgumentException("Size too large! " + size);
}
/**
* @since 0.9.3
*/
private void releaseBuf() {
if (_messageBuf == null)
return;
int size = _messageBuf.getData().length;
if (size == CACHE1_BYTES)
_cache1.release(_messageBuf);
else if (size == CACHE2_BYTES)
_cache2.release(_messageBuf);
else if (size == CACHE3_BYTES)
_cache3.release(_messageBuf);
else if (size == CACHE4_BYTES)
_cache4.release(_messageBuf);
_messageBuf = null;
_released = true;
}
/**
* This is synchronized with writeFragment(),
* so we do not release (probably due to an ack) while we are retransmitting.
@@ -151,8 +193,7 @@ class OutboundMessageState implements CDPQEntry {
*/
public synchronized void releaseResources() {
if (_messageBuf != null && !_released) {
_cache.release(_messageBuf);
_released = true;
releaseBuf();
if (_log.shouldLog(Log.WARN))
_releasedBy = new Exception ("Released on " + new Date() + " by:");
}

View File

@@ -1,11 +1,14 @@
package net.i2p.router.transport.udp;
import java.util.Date;
import java.util.List;
import java.util.Map;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.LHMCache;
import net.i2p.util.Log;
/**
@@ -30,6 +33,8 @@ class PacketHandler {
private final IntroductionManager _introManager;
private volatile boolean _keepReading;
private final Handler[] _handlers;
private final Map<RemoteHostId, Object> _failCache;
private static final Object DUMMY = new Object();
private static final int MIN_NUM_HANDLERS = 1; // unless < 32MB
private static final int MAX_NUM_HANDLERS = 1;
@@ -46,6 +51,7 @@ class PacketHandler {
_inbound = inbound;
_testManager = testManager;
_introManager = introManager;
_failCache = new LHMCache(24);
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
@@ -143,8 +149,8 @@ class PacketHandler {
if (packet == null) break; // keepReading is probably false, or bind failed...
packet.received();
if (_log.shouldLog(Log.INFO))
_log.info("Received: " + packet);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received: " + packet);
_state = 4;
long queueTime = packet.getLifetime();
long handleStart = _context.clock().now();
@@ -294,7 +300,7 @@ class PacketHandler {
receivePacket(reader, packet, est, false);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Validation with existing con failed, and validation as reestablish failed too. DROP");
_log.warn("Validation with existing con failed, and validation as reestablish failed too. DROP " + packet);
_context.statManager().addRateData("udp.droppedInvalidReestablish", packet.getLifetime(), packet.getExpiration());
}
return;
@@ -327,8 +333,70 @@ class PacketHandler {
if (!isValid) {
// Note that the vast majority of these are NOT corrupted packets, but
// packets for which we don't have the PeerState (i.e. SessionKey)
// Case 1: 48 byte destroy packet, we already closed
// Case 2: 369 byte session created packet, re-tx of one that failed validation
// (peer probably doesn't know his correct external port, esp. on <= 0.9.1
// Case 3:
// For peers that change ports, look for an existing session with the same IP
// If we find it, and the packet validates with its mac key, tell the transport
// to change the port, and handle the packet.
// All this since 0.9.3.
RemoteHostId remoteHost = packet.getRemoteHost();
boolean alreadyFailed;
synchronized(_failCache) {
alreadyFailed = _failCache.get(remoteHost) != null;
}
if (!alreadyFailed) {
// this is slow, that's why we cache it above.
List<PeerState> peers = _transport.getPeerStatesByIP(remoteHost);
if (!peers.isEmpty()) {
StringBuilder buf = new StringBuilder(256);
buf.append("Established peers with this IP: ");
boolean foundSamePort = false;
PeerState state = null;
int newPort = remoteHost.getPort();
for (PeerState ps : peers) {
boolean valid = false;
long now = _context.clock().now();
if (_log.shouldLog(Log.WARN))
buf.append(ps.getRemoteHostId().toString())
.append(" last sent: ").append(now - ps.getLastSendTime())
.append(" last rcvd: ").append(now - ps.getLastReceiveTime());
if (ps.getRemotePort() == newPort) {
foundSamePort = true;
} else if (packet.validate(ps.getCurrentMACKey())) {
packet.decrypt(ps.getCurrentCipherKey());
reader.initialize(packet);
if (_log.shouldLog(Log.WARN))
buf.append(" VALID type ").append(reader.readPayloadType()).append("; ");
valid = true;
if (state == null)
state = ps;
} else {
if (_log.shouldLog(Log.WARN))
buf.append(" INVALID; ");
}
}
if (state != null && !foundSamePort) {
_transport.changePeerPort(state, newPort);
if (_log.shouldLog(Log.WARN)) {
buf.append(" CHANGED PORT TO ").append(newPort).append(" AND HANDLED");
_log.warn(buf.toString());
}
handlePacket(reader, packet, state, null, null, true);
return;
}
if (_log.shouldLog(Log.WARN))
_log.warn(buf.toString());
}
synchronized(_failCache) {
_failCache.put(remoteHost, DUMMY);
}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Cannot validate rcvd pkt (path): " + packet);
_log.warn("Cannot validate rcvd pkt (path) wasCached? " + alreadyFailed + ": " + packet);
_context.statManager().addRateData("udp.droppedInvalidEstablish", packet.getLifetime(), packet.getExpiration());
switch (peerType) {
case INBOUND_FALLBACK:

View File

@@ -72,6 +72,7 @@ class PeerState {
* A positive number means our clock is ahead of theirs.
*/
private long _clockSkew;
private final Object _clockSkewLock = new Object();
/** what is the current receive second, for congestion control? */
private long _currentReceiveSecond;
@@ -79,6 +80,8 @@ class PeerState {
private long _lastSendTime;
/** when did we last send them a message that was ACKed */
private long _lastSendFullyTime;
/** when did we last send them a ping? */
private long _lastPingTime;
/** when did we last receive a packet from them? */
private long _lastReceiveTime;
/** how many consecutive messages have we sent and not received an ACK to */
@@ -137,11 +140,11 @@ class PeerState {
/** what IP is the peer sending and receiving packets on? */
private final byte[] _remoteIP;
/** cached IP address */
private transient InetAddress _remoteIPAddress;
private volatile InetAddress _remoteIPAddress;
/** what port is the peer sending and receiving packets on? */
private final int _remotePort;
private volatile int _remotePort;
/** cached RemoteHostId, used to find the peerState by remote info */
private final RemoteHostId _remoteHostId;
private volatile RemoteHostId _remoteHostId;
/** if we need to contact them, do we need to talk to an introducer? */
//private boolean _remoteRequiresIntroduction;
@@ -284,9 +287,12 @@ class PeerState {
*/
public static final int LARGE_MTU = 1484;
/** 600 */
private static final int MIN_RTO = 100 + ACKSender.ACK_FREQUENCY;
private static final int INIT_RTO = 4*1000;
private static final int INIT_RTO = 3*1000;
public static final int INIT_RTT = INIT_RTO / 2;
private static final int MAX_RTO = 15*1000;
private static final int CLOCK_SKEW_FUDGE = (ACKSender.ACK_FREQUENCY * 2) / 3;
public PeerState(RouterContext ctx, UDPTransport transport,
byte[] remoteIP, int remotePort, Hash remotePeer, boolean isInbound) {
@@ -313,7 +319,7 @@ class PeerState {
//_mtuLastChecked = -1;
_lastACKSend = -1;
_rto = INIT_RTO;
_rtt = INIT_RTO / 2;
_rtt = INIT_RTT;
_rttDeviation = _rtt;
_inboundMessages = new HashMap(8);
_outboundMessages = new ArrayList(32);
@@ -325,6 +331,17 @@ class PeerState {
_remoteHostId = new RemoteHostId(remoteIP, remotePort);
}
/**
* Caller should sync; UDPTransport must remove and add to peersByRemoteHost map
* @since 0.9.3
*/
public void changePort(int newPort) {
if (newPort != _remotePort) {
_remoteHostId = new RemoteHostId(_remoteIP, newPort);
_remotePort = newPort;
}
}
/**
* The peer are we talking to. This should be set as soon as this
* state is created if we are initiating a connection, but if we are
@@ -342,9 +359,12 @@ class PeerState {
* connection is established.
*/
public SessionKey getCurrentCipherKey() { return _currentCipherKey; }
/**
* The pending AES key for verifying packets if we are rekeying the
* connection, or null if we are not in the process of rekeying.
*
* @return null always, rekeying unimplemented
*/
public SessionKey getNextMACKey() { return _nextMACKey; }
@@ -352,6 +372,8 @@ class PeerState {
* The pending AES key for encrypting/decrypting packets if we are
* rekeying the connection, or null if we are not in the process
* of rekeying.
*
* @return null always, rekeying unimplemented
*/
public SessionKey getNextCipherKey() { return _nextCipherKey; }
@@ -384,13 +406,20 @@ class PeerState {
public long getLastReceiveTime() { return _lastReceiveTime; }
/** how many seconds have we sent packets without any ACKs received? */
public int getConsecutiveFailedSends() { return _consecutiveFailedSends; }
/** have we received a packet with the ECN bit set in the current second? */
/**
* have we received a packet with the ECN bit set in the current second?
* @return false always
* @deprecated unused, ECNs are never sent, always returns false
*/
public boolean getCurrentSecondECNReceived() { return _currentSecondECNReceived; }
/**
* have all of the packets received in the current second requested that
* the previous second's ACKs be sent?
*/
//public boolean getRemoteWantsPreviousACKs() { return _remoteWantsPreviousACKs; }
/** how many bytes should we send to the peer in a second */
public int getSendWindowBytes() { return _sendWindowBytes; }
/** how many bytes can we send to the peer in the current second */
@@ -504,7 +533,12 @@ class PeerState {
* A positive number means our clock is ahead of theirs.
*/
public void adjustClockSkew(long skew) {
_clockSkew = (long) (0.9*_clockSkew + 0.1*(skew - (_rtt / 2)));
// the real one-way delay is much less than RTT / 2, due to ack delays,
// so add a fudge factor
double adj = 0.1 * (skew + CLOCK_SKEW_FUDGE - (_rtt / 2));
synchronized(_clockSkewLock) {
_clockSkew = (long) (0.9*_clockSkew + adj);
}
}
/** what is the current receive second, for congestion control? */
@@ -513,6 +547,21 @@ class PeerState {
public void setLastSendTime(long when) { _lastSendTime = when; }
/** when did we last receive a packet from them? */
public void setLastReceiveTime(long when) { _lastReceiveTime = when; }
/**
* Note ping sent. Does not update last send time.
* @since 0.9.3
*/
public void setLastPingTime(long when) { _lastPingTime = when; }
/**
* Latest of last sent, last ACK, last ping
* @since 0.9.3
*/
public long getLastSendOrPingTime() {
return Math.max(Math.max(_lastSendTime, _lastACKSend), _lastPingTime);
}
/** return the smoothed send transfer rate */
public int getSendBps() { return _sendBps; }
public int getReceiveBps() { return _receiveBps; }
@@ -714,7 +763,8 @@ class PeerState {
/**
* either they told us to back off, or we had to resend to get
* the data through.
*
* Caller should synch on this
* @return true if window shrunk, but nobody uses the return value
*/
private boolean congestionOccurred() {
long now = _context.clock().now();
@@ -722,8 +772,6 @@ class PeerState {
return false; // only shrink once every few seconds
_lastCongestionOccurred = now;
_context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes, _sendBps);
int congestionAt = _sendWindowBytes;
//if (true)
// _sendWindowBytes -= 10000;
@@ -779,7 +827,10 @@ class PeerState {
return randomResends;
}
/** the ack was sent */
/**
* The ack was sent.
* Side effect - sets _lastACKSend
*/
public void removeACKMessage(Long messageId) {
boolean removed = _currentACKs.remove(messageId);
if (removed) {
@@ -800,15 +851,15 @@ class PeerState {
/**
* The max number of acks we save to send as duplicates
*/
private static final int MAX_RESEND_ACKS = 16;
private static final int MAX_RESEND_ACKS = 64;
/**
* The max number of duplicate acks sent in each ack-only messge.
* Doesn't really matter, we have plenty of room...
* @since 0.7.13
*/
private static final int MAX_RESEND_ACKS_LARGE = MAX_RESEND_ACKS;
private static final int MAX_RESEND_ACKS_LARGE = MAX_RESEND_ACKS / 3;
/** for small MTU */
private static final int MAX_RESEND_ACKS_SMALL = MAX_RESEND_ACKS;
private static final int MAX_RESEND_ACKS_SMALL = MAX_RESEND_ACKS / 5;
/**
* grab a list of ACKBitfield instances, some of which may fully
@@ -827,11 +878,11 @@ class PeerState {
* See above. Only called by ACKSender with alwaysIncludeRetransmissions = false.
* So this is only for ACK-only packets, so all the size limiting is useless.
* FIXME.
* Side effect - sets _lastACKSend if rv is non-empty
*
* @return non-null, possibly empty
*/
public List<ACKBitfield> retrieveACKBitfields(boolean alwaysIncludeRetransmissions) {
List<ACKBitfield> rv = new ArrayList(MAX_RESEND_ACKS);
int bytesRemaining = countMaxACKData();
// Limit the overhead of all the resent acks when using small MTU
@@ -843,8 +894,10 @@ class PeerState {
maxResendAcks = MAX_RESEND_ACKS_SMALL;
else
maxResendAcks = MAX_RESEND_ACKS_LARGE;
List<Long> randomResends = new ArrayList(_currentACKsResend);
List<ACKBitfield> rv = new ArrayList(maxResendAcks);
// save to add to currentACKsResend later so we don't include twice
List<Long> currentACKsRemoved = new ArrayList(_currentACKs.size());
// As explained above, we include the acks in any order
// since we are unlikely to get backed up -
// just take them using the Set iterator.
@@ -854,12 +907,13 @@ class PeerState {
iter.remove();
long id = val.longValue();
rv.add(new FullACKBitfield(id));
_currentACKsResend.offer(val);
currentACKsRemoved.add(val);
bytesRemaining -= 4;
}
if (_currentACKs.isEmpty())
_wantACKSendSince = -1;
if (alwaysIncludeRetransmissions || !rv.isEmpty()) {
List<Long> randomResends = new ArrayList(_currentACKsResend);
// now repeat by putting in some old ACKs
// randomly selected from the Resend queue.
// Maybe we should only resend each one a certain number of times...
@@ -880,6 +934,9 @@ class PeerState {
bytesRemaining -= 4;
//}
}
for (Long val : currentACKsRemoved) {
_currentACKsResend.offer(val);
}
}
// trim down the resends
while (_currentACKsResend.size() > MAX_RESEND_ACKS)
@@ -907,7 +964,8 @@ class PeerState {
}
}
_lastACKSend = _context.clock().now();
if (!rv.isEmpty())
_lastACKSend = _context.clock().now();
//if (rv == null)
// rv = Collections.EMPTY_LIST;
if (partialIncluded > 0)
@@ -975,8 +1033,11 @@ class PeerState {
public String toString() { return "Full ACK of " + _msgId; }
}
/** we sent a message which was ACKed containing the given # of bytes */
public void messageACKed(int bytesACKed, long lifetime, int numSends) {
/**
* We sent a message which was ACKed containing the given # of bytes.
* Caller should synch on this
*/
private void locked_messageACKed(int bytesACKed, long lifetime, int numSends) {
_concurrentMessagesActive--;
if (_concurrentMessagesActive < 0)
_concurrentMessagesActive = 0;
@@ -990,15 +1051,15 @@ class PeerState {
if (_sendWindowBytes <= _slowStartThreshold) {
_sendWindowBytes += bytesACKed;
} else {
if (false) {
_sendWindowBytes += 16; // why 16?
} else {
//if (false) {
// _sendWindowBytes += 16; // why 16?
//} else {
float prob = ((float)bytesACKed) / ((float)(_sendWindowBytes<<1));
float v = _context.random().nextFloat();
if (v < 0) v = 0-v;
if (v <= prob)
_sendWindowBytes += bytesACKed; //512; // bytesACKed;
}
//}
}
} else {
int allow = _concurrentMessagesAllowed - 1;
@@ -1011,21 +1072,31 @@ class PeerState {
_lastReceiveTime = _context.clock().now();
_lastSendFullyTime = _lastReceiveTime;
if (true) {
//if (true) {
if (_sendWindowBytesRemaining + bytesACKed <= _sendWindowBytes)
_sendWindowBytesRemaining += bytesACKed;
else
_sendWindowBytesRemaining = _sendWindowBytes;
}
//}
_messagesSent++;
if (numSends < 2) {
synchronized (this) {
// caller synchs
//synchronized (this) {
recalculateTimeouts(lifetime);
adjustMTU();
}
//}
}
else if (_log.shouldLog(Log.INFO))
}
/**
* We sent a message which was ACKed containing the given # of bytes.
*/
private void messageACKed(int bytesACKed, long lifetime, int numSends) {
synchronized(this) {
locked_messageACKed(bytesACKed, lifetime, numSends);
}
if (numSends >= 2 && _log.shouldLog(Log.INFO))
_log.info("acked after numSends=" + numSends + " w/ lifetime=" + lifetime + " and size=" + bytesACKed);
_context.statManager().addRateData("udp.sendBps", _sendBps, lifetime);
@@ -1104,9 +1175,10 @@ class PeerState {
_packetsRetransmitted = packets;
}
*****/
congestionOccurred();
_context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes, _sendBps);
_context.statManager().addRateData("udp.congestedRTO", _rto, _rttDeviation);
synchronized (this) {
congestionOccurred();
adjustMTU();
}
//_rto *= 2;
@@ -1168,10 +1240,14 @@ class PeerState {
}
/**
* we received a backoff request, so cut our send window
* We received a backoff request, so cut our send window.
* NOTE: ECN sending is unimplemented, this is never called.
*/
public void ECNReceived() {
congestionOccurred();
synchronized(this) {
congestionOccurred();
}
_context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes, _sendBps);
_currentSecondECNReceived = true;
_lastReceiveTime = _context.clock().now();
}
@@ -1182,17 +1258,33 @@ class PeerState {
/** when did we last send an ACK to the peer? */
public long getLastACKSend() { return _lastACKSend; }
/** @deprecated unused */
public void setLastACKSend(long when) { _lastACKSend = when; }
public long getWantedACKSendSince() { return _wantACKSendSince; }
/**
* Are we out of room to send all the current unsent acks in a single packet?
* This is a huge threshold (134 for small MTU and 255 for large MTU)
* that is rarely if ever exceeded in practice.
* So just use a fixed threshold of half the resend acks, so that if the
* packet is lost the acks have a decent chance of getting retransmitted.
* Used only by ACKSender.
*/
public boolean unsentACKThresholdReached() {
int threshold = countMaxACKData() / 4;
return _currentACKs.size() >= threshold;
//int threshold = countMaxACKData() / 4;
//return _currentACKs.size() >= threshold;
return _currentACKs.size() >= MAX_RESEND_ACKS / 2;
}
/** @return MTU - 83 */
/**
* @return how many bytes available for acks in an ack-only packet, == MTU - 83
* Max of 1020
*/
private int countMaxACKData() {
return _mtu
return Math.min(PacketBuilder.ABSOLUTE_MAX_ACKS * 4,
_mtu
- PacketBuilder.IP_HEADER_SIZE
- PacketBuilder.UDP_HEADER_SIZE
- UDPPacket.IV_SIZE
@@ -1201,7 +1293,7 @@ class PeerState {
- 4 // timestamp
- 1 // data flag
- 1 // # ACKs
- 16; // padding safety
- 16); // padding safety
}
private int minRTO() {
@@ -1213,6 +1305,7 @@ class PeerState {
// return MAX_RTO;
}
/** @return non-null */
RemoteHostId getRemoteHostId() { return _remoteHostId; }
/**
@@ -1770,6 +1863,7 @@ class PeerState {
/**
* Transfer the basic activity/state from the old peer to the current peer
*
* @param oldPeer non-null
*/
public void loadFrom(PeerState oldPeer) {
_rto = oldPeer._rto;
@@ -1875,6 +1969,9 @@ class PeerState {
buf.append(" consecFail: ").append(_consecutiveFailedSends);
buf.append(" recv OK/Dup: ").append(_packetsReceived).append('/').append(_packetsReceivedDuplicate);
buf.append(" send OK/Dup: ").append(_packetsTransmitted).append('/').append(_packetsRetransmitted);
buf.append(" IBM: ").append(_inboundMessages.size());
buf.append(" OBQ: ").append(_outboundQueue.size());
buf.append(" OBL: ").append(_outboundMessages.size());
return buf.toString();
}
}

View File

@@ -14,12 +14,16 @@ import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
import static net.i2p.router.transport.udp.PeerTestState.Role.*;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Entry points are runTest() to start a new test as Alice,
* and receiveTest() for all received test packets.
*
* From udp.html on the website:
<p>The automation of collaborative reachability testing for peers is
@@ -30,12 +34,27 @@ quite simple:</p>
<pre>
Alice Bob Charlie
runTest()
sendTestToBob() receiveFromAliceAsBob()
PeerTest -------------------&gt;
sendTestToCharlie() receiveFromBobAsCharlie()
PeerTest--------------------&gt;
receiveFromCharlieAsBob()
&lt;-------------------PeerTest
receiveTestReply()
&lt;-------------------PeerTest
receiveTestReply()
&lt;------------------------------------------PeerTest
receiveFromAliceAsCharlie()
PeerTest------------------------------------------&gt;
receiveTestReply()
&lt;------------------------------------------PeerTest
</pre>
@@ -104,9 +123,27 @@ class PeerTestManager {
private boolean _currentTestComplete;
/** as Alice */
private final Queue<Long> _recentTests;
private final IPThrottler _throttle;
private static final int MAX_RELAYED_PER_TEST_ALICE = 9;
private static final int MAX_RELAYED_PER_TEST_BOB = 6;
private static final int MAX_RELAYED_PER_TEST_CHARLIE = 6;
/** longest we will keep track of a Charlie nonce for */
private static final int MAX_CHARLIE_LIFETIME = 10*1000;
private static final int MAX_CHARLIE_LIFETIME = 15*1000;
/** as Bob/Charlie */
private static final int MAX_ACTIVE_TESTS = 20;
private static final int MAX_RECENT_TESTS = 40;
/** for the throttler */
private static final int MAX_PER_IP = 12;
private static final long THROTTLE_CLEAN_TIME = 10*60*1000;
/** initial - ContinueTest adds backoff */
private static final int RESEND_TIMEOUT = 4*1000;
private static final int MAX_TEST_TIME = 30*1000;
private static final long MAX_NONCE = (1l << 32) - 1l;
/**
* Have seen peer tests (as Alice) get stuck (_currentTest != null)
@@ -120,15 +157,12 @@ class PeerTestManager {
_activeTests = new ConcurrentHashMap();
_recentTests = new LinkedBlockingQueue();
_packetBuilder = new PacketBuilder(context, transport);
_throttle = new IPThrottler(MAX_PER_IP, THROTTLE_CLEAN_TIME);
_context.statManager().createRateStat("udp.statusKnownCharlie", "How often the bob we pick passes us to a charlie we already have a session with?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.receiveTestReply", "How often we get a reply to our peer test?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.receiveTest", "How often we get a packet requesting us to participate in a peer test?", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.testBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
}
private static final int RESEND_TIMEOUT = 5*1000;
private static final int MAX_TEST_TIME = 30*1000;
private static final long MAX_NONCE = (1l << 32) - 1l;
//public void runTest(InetAddress bobIP, int bobPort, SessionKey bobIntroKey) {
/**
* The next few methods are for when we are Alice
@@ -136,42 +170,60 @@ class PeerTestManager {
public synchronized void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
if (_currentTest != null) {
if (_log.shouldLog(Log.WARN))
_log.warn("We are already running a test with bob = " + _currentTest.getBobIP() + ", aborting test with bob = " + bobIP);
_log.warn("We are already running a test: " + _currentTest + ", aborting test with bob = " + bobIP);
return;
}
PeerTestState test = new PeerTestState();
test.setNonce(_context.random().nextLong(MAX_NONCE));
if (DataHelper.eq(bobIP.getAddress(), 0, _transport.getExternalIP(), 0, 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not running test with Bob too close to us " + bobIP);
return;
}
PeerTestState test = new PeerTestState(ALICE,
_context.random().nextLong(MAX_NONCE),
_context.clock().now());
test.setBobIP(bobIP);
test.setBobPort(bobPort);
test.setBobCipherKey(bobCipherKey);
test.setBobMACKey(bobMACKey);
test.setBeginTime(_context.clock().now());
test.setLastSendTime(test.getBeginTime());
test.setOurRole(PeerTestState.ALICE);
_currentTest = test;
_currentTestComplete = false;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Running test with bob = " + bobIP + ":" + bobPort + " " + test.getNonce());
while (_recentTests.size() > 16)
_log.debug("Start new test: " + test);
while (_recentTests.size() > MAX_RECENT_TESTS)
_recentTests.poll();
_recentTests.offer(Long.valueOf(test.getNonce()));
test.incrementPacketsRelayed();
sendTestToBob();
_context.simpleScheduler().addEvent(new ContinueTest(), RESEND_TIMEOUT);
_context.simpleScheduler().addEvent(new ContinueTest(test.getNonce()), RESEND_TIMEOUT);
}
private class ContinueTest implements SimpleTimer.TimedEvent {
private final long _nonce;
public ContinueTest(long nonce) {
_nonce = nonce;
}
public void timeReached() {
synchronized (PeerTestManager.this) {
PeerTestState state = _currentTest;
if (state == null) {
// already completed
if (state == null || state.getNonce() != _nonce) {
// already completed, possibly on to the next test
return;
} else if (expired()) {
testComplete(true);
} else if (_context.clock().now() - state.getLastSendTime() >= RESEND_TIMEOUT) {
int sent = state.incrementPacketsRelayed();
if (sent > MAX_RELAYED_PER_TEST_ALICE) {
testComplete(false);
if (_log.shouldLog(Log.WARN))
_log.warn("Sent too many packets: " + state);
return;
}
if (state.getReceiveBobTime() <= 0) {
// no message from Bob yet, send it again
sendTestToBob();
@@ -184,7 +236,8 @@ class PeerTestManager {
// second message from Charlie yet
sendTestToCharlie();
}
_context.simpleScheduler().addEvent(ContinueTest.this, RESEND_TIMEOUT);
// retx at 4, 10, 17, 25 elapsed time
_context.simpleScheduler().addEvent(ContinueTest.this, RESEND_TIMEOUT + (sent*1000));
}
}
}
@@ -204,19 +257,20 @@ class PeerTestManager {
PeerTestState test = _currentTest;
if (!expired()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending test to bob: " + test.getBobIP() + ":" + test.getBobPort());
_log.debug("Sending test to Bob: " + test);
_transport.send(_packetBuilder.buildPeerTestFromAlice(test.getBobIP(), test.getBobPort(), test.getBobCipherKey(), test.getBobMACKey(), //_bobIntroKey,
test.getNonce(), _transport.getIntroKey()));
} else {
_currentTest = null;
}
}
/** call from a synchronized method */
private void sendTestToCharlie() {
PeerTestState test = _currentTest;
if (!expired()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending test to charlie: " + test.getCharlieIP() + ":" + test.getCharliePort());
_log.debug("Sending test to Charlie: " + test);
_transport.send(_packetBuilder.buildPeerTestFromAlice(test.getCharlieIP(), test.getCharliePort(), test.getCharlieIntroKey(),
test.getNonce(), _transport.getIntroKey()));
} else {
@@ -240,7 +294,7 @@ class PeerTestManager {
* test. We are Alice.
*/
private synchronized void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) {
_context.statManager().addRateData("udp.receiveTestReply", 1, 0);
_context.statManager().addRateData("udp.receiveTestReply", 1);
PeerTestState test = _currentTest;
if (expired())
return;
@@ -270,16 +324,20 @@ class PeerTestManager {
InetAddress addr = InetAddress.getByAddress(ip);
test.setAliceIP(addr);
test.setReceiveBobTime(_context.clock().now());
test.setAlicePort(testInfo.readPort());
int testPort = testInfo.readPort();
if (testPort == 0)
throw new UnknownHostException("port 0");
test.setAlicePort(testPort);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test reply from bob @ " + from + " via our " + test.getAlicePort() + "/" + test.getAlicePortFromCharlie());
_log.debug("Receive test reply from Bob: " + test);
if (test.getAlicePortFromCharlie() > 0)
testComplete(false);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to get our IP (length " + ipSize +
") from bob's reply: " + from + ", " + testInfo, uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
} else {
// The reply is from Charlie
@@ -294,7 +352,7 @@ class PeerTestManager {
+ _currentTest + ", charlie: " + from + ")");
// why are we doing this instead of calling testComplete() ?
_currentTestComplete = true;
_context.statManager().addRateData("udp.statusKnownCharlie", 1, 0);
_context.statManager().addRateData("udp.statusKnownCharlie", 1);
honorStatus(CommSystemFacade.STATUS_UNKNOWN);
_currentTest = null;
return;
@@ -302,26 +360,29 @@ class PeerTestManager {
if (test.getReceiveCharlieTime() > 0) {
// this is our second charlie, yay!
test.setAlicePortFromCharlie(testInfo.readPort());
byte ip[] = new byte[testInfo.readIPSize()];
testInfo.readIP(ip, 0);
try {
int testPort = testInfo.readPort();
if (testPort == 0)
throw new UnknownHostException("port 0");
test.setAlicePortFromCharlie(testPort);
byte ip[] = new byte[testInfo.readIPSize()];
testInfo.readIP(ip, 0);
InetAddress addr = InetAddress.getByAddress(ip);
test.setAliceIPFromCharlie(addr);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test reply from charlie @ " + test.getCharlieIP() + " via our "
+ test.getAlicePort() + "/" + test.getAlicePortFromCharlie());
_log.debug("Receive test reply from Charlie: " + test);
if (test.getReceiveBobTime() > 0)
testComplete(true);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Charlie @ " + from + " said we were an invalid IP address: " + uhe.getMessage(), uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
} else {
if (test.getPacketsRelayed() > MAX_RELAYED_PER_TEST) {
if (test.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_ALICE) {
testComplete(false);
if (_log.shouldLog(Log.WARN))
_log.warn("Received too many packets on the test: " + test);
_log.warn("Sent too many packets on the test: " + test);
return;
}
@@ -338,11 +399,12 @@ class PeerTestManager {
test.setCharlieIP(InetAddress.getByAddress(from.getIP()));
test.setCharliePort(from.getPort());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test from charlie @ " + from);
_log.debug("Receive test from Charlie: " + test);
sendTestToCharlie();
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Charlie's IP is b0rked: " + from + ": " + testInfo);
_context.statManager().addRateData("udp.testBadIP", 1);
}
}
}
@@ -409,44 +471,119 @@ class PeerTestManager {
}
/**
* Entry point for all incoming packets. Most of the source and dest validation is here.
*
* Receive a test message of some sort from the given peer, queueing up any packet
* that should be sent in response, or if its a reply to our own current testing,
* adjusting our test state.
*
* We could be Alice, Bob, or Charlie.
*/
public void receiveTest(RemoteHostId from, UDPPacketReader reader) {
_context.statManager().addRateData("udp.receiveTest", 1, 0);
_context.statManager().addRateData("udp.receiveTest", 1);
byte[] fromIP = from.getIP();
int fromPort = from.getPort();
if (fromPort < 1024 || fromPort > 65535 ||
(!_transport.isValid(fromIP)) ||
DataHelper.eq(fromIP, 0, _transport.getExternalIP(), 0, 2) ||
_context.blocklist().isBlocklisted(fromIP)) {
// spoof check, and don't respond to privileged ports
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid PeerTest address: " + Addresses.toString(fromIP, fromPort));
_context.statManager().addRateData("udp.testBadIP", 1);
return;
}
UDPPacketReader.PeerTestReader testInfo = reader.getPeerTestReader();
byte testIP[] = null;
int testPort = testInfo.readPort();
if (testInfo.readIPSize() > 0) {
testIP = new byte[testInfo.readIPSize()];
testInfo.readIP(testIP, 0);
}
if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) ||
(testIP != null &&
((!_transport.isValid(testIP)) ||
_context.blocklist().isBlocklisted(testIP)))) {
// spoof check, and don't respond to privileged ports
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid address in PeerTest: " + Addresses.toString(testIP, testPort));
_context.statManager().addRateData("udp.testBadIP", 1);
return;
}
// The from IP/port and message's IP/port are now validated.
// EXCEPT that either the message's IP could be empty or the message's port could be 0.
// Both of those cases should be checked in receiveXfromY() as appropriate.
// Also, IP could be us, check is below.
long nonce = testInfo.readNonce();
PeerTestState test = _currentTest;
if ( (test != null) && (test.getNonce() == nonce) ) {
// we are Alice
// we are Alice, we initiated the test
receiveTestReply(from, testInfo);
return;
}
// we are Bob or Charlie
// we are Bob or Charlie, we are helping Alice
if ( (testInfo.readIPSize() > 0) && (testPort > 0) ) {
testIP = new byte[testInfo.readIPSize()];
testInfo.readIP(testIP, 0);
if (_throttle.shouldThrottle(fromIP)) {
if (_log.shouldLog(Log.WARN))
_log.warn("PeerTest throttle from " + Addresses.toString(fromIP, fromPort));
return;
}
// use the same counter for both from and to IPs
if (testIP != null && _throttle.shouldThrottle(testIP)) {
if (_log.shouldLog(Log.WARN))
_log.warn("PeerTest throttle to " + Addresses.toString(testIP, testPort));
return;
}
Long lNonce = Long.valueOf(nonce);
PeerTestState state = _activeTests.get(lNonce);
if (testIP != null && DataHelper.eq(testIP, 0, _transport.getExternalIP(), 0, 2)) {
// spoof check - have to do this after receiveTestReply(), since
// the field should be us there.
// Let's also eliminate anybody in the same /16
if (_recentTests.contains(lNonce)) {
if (_log.shouldLog(Log.INFO))
_log.info("Got delayed reply on nonce " + nonce +
" from: " + Addresses.toString(fromIP, fromPort));
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Nearby address in PeerTest: " + Addresses.toString(testIP, testPort) +
" from: " + Addresses.toString(fromIP, fromPort) +
" state? " + state);
_context.statManager().addRateData("udp.testBadIP", 1);
}
return;
}
PeerTestState state = _activeTests.get(Long.valueOf(nonce));
if (state == null) {
// NEW TEST
if ( (testIP == null) || (testPort <= 0) ) {
// we are bob, since we haven't seen this nonce before AND its coming from alice
if (_activeTests.size() >= MAX_ACTIVE_TESTS) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many active tests, droppping from Alice " + Addresses.toString(fromIP, fromPort));
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("test IP/port are blank coming from " + from + ", assuming we are Bob and they are alice");
receiveFromAliceAsBob(from, testInfo, nonce, null);
} else {
if (_recentTests.contains(Long.valueOf(nonce))) {
if (_recentTests.contains(lNonce)) {
// ignore the packet, as its a holdover from a recently completed locally
// initiated test
} else {
if (_activeTests.size() >= MAX_ACTIVE_TESTS) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many active tests, droppping from Bob " + Addresses.toString(fromIP, fromPort));
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("We are charlie, as the testIP/port is " + Addresses.toString(testIP, testPort) + " and the state is unknown for " + nonce);
// we are charlie, since alice never sends us her IP and port, only bob does (and,
@@ -455,20 +592,21 @@ class PeerTestManager {
}
}
} else {
if (state.getOurRole() == PeerTestState.BOB) {
if (DataHelper.eq(from.getIP(), state.getAliceIP().getAddress()) &&
(from.getPort() == state.getAlicePort()) ) {
// EXISTING TEST
if (state.getOurRole() == BOB) {
if (DataHelper.eq(fromIP, state.getAliceIP().getAddress()) &&
(fromPort == state.getAlicePort()) ) {
receiveFromAliceAsBob(from, testInfo, nonce, state);
} else if (DataHelper.eq(from.getIP(), state.getCharlieIP().getAddress()) &&
(from.getPort() == state.getCharliePort()) ) {
} else if (DataHelper.eq(fromIP, state.getCharlieIP().getAddress()) &&
(fromPort == state.getCharliePort()) ) {
receiveFromCharlieAsBob(from, state);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Received from a fourth party as bob! alice: " + state.getAliceIP() + ", charlie: " + state.getCharlieIP() + ", dave: " + from);
}
} else if (state.getOurRole() == PeerTestState.CHARLIE) {
} else if (state.getOurRole() == CHARLIE) {
if ( (testIP == null) || (testPort <= 0) ) {
receiveFromAliceAsCharlie(from, testInfo, nonce);
receiveFromAliceAsCharlie(from, testInfo, nonce, state);
} else {
receiveFromBobAsCharlie(from, testInfo, nonce, state);
}
@@ -478,29 +616,34 @@ class PeerTestManager {
// Below here are methods for when we are Bob or Charlie
private static final int MAX_RELAYED_PER_TEST = 5;
/**
* The packet's IP/port does not match the IP/port included in the message,
* so we must be Charlie receiving a PeerTest from Bob.
*
* @param state null if new
*/
private void receiveFromBobAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
long now = _context.clock().now();
boolean isNew = false;
if (state == null) {
isNew = true;
state = new PeerTestState();
state.setOurRole(PeerTestState.CHARLIE);
state = new PeerTestState(CHARLIE, nonce, now);
} else {
if (state.getReceiveBobTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too soon, not retransmitting: " + state);
return;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test as charlie nonce " + nonce);
// TODO should only do most of this if isNew
int sz = testInfo.readIPSize();
byte aliceIPData[] = new byte[sz];
try {
testInfo.readIP(aliceIPData, 0);
int alicePort = testInfo.readPort();
if (alicePort == 0)
throw new UnknownHostException("port 0");
InetAddress aliceIP = InetAddress.getByAddress(aliceIPData);
InetAddress bobIP = InetAddress.getByAddress(from.getIP());
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
@@ -509,12 +652,10 @@ class PeerTestManager {
state.setAliceIP(aliceIP);
state.setAlicePort(alicePort);
state.setAliceIntroKey(aliceIntroKey);
state.setNonce(nonce);
state.setBobIP(bobIP);
state.setBobPort(from.getPort());
state.setLastSendTime(_context.clock().now());
state.setOurRole(PeerTestState.CHARLIE);
state.setReceiveBobTime(_context.clock().now());
state.setLastSendTime(now);
state.setReceiveBobTime(now);
PeerState bob = _transport.getPeerState(from);
if (bob == null) {
@@ -526,16 +667,15 @@ class PeerTestManager {
state.setBobMACKey(bob.getCurrentMACKey());
}
state.incrementPacketsRelayed();
if (state.getPacketsRelayed() > MAX_RELAYED_PER_TEST) {
// we send two packets below, but increment just once
if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_CHARLIE) {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive from bob (" + from + ") as charlie with alice @ " + aliceIP + ":" + alicePort
+ ", but we've already relayed too many packets to that test, so we're dropping it");
_log.warn("Too many, not retransmitting: " + state);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from bob (" + from + ") as charlie, sending back to bob and sending to alice @ " + aliceIP + ":" + alicePort);
_log.debug("Receive from Bob: " + state);
if (isNew) {
_activeTests.put(Long.valueOf(nonce), state);
@@ -550,6 +690,7 @@ class PeerTestManager {
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to build the aliceIP from " + from + ", ip size: " + sz + " ip val: " + Base64.encode(aliceIPData), uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
}
@@ -557,20 +698,20 @@ class PeerTestManager {
* The PeerTest message came from the peer referenced in the message (or there wasn't
* any info in the message), plus we are not acting as Charlie (so we've got to be Bob).
*
* testInfo IP/port ignored
* @param state null if new
*/
private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
// we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info
PeerState charlie = null;
PeerState charlie;
RouterInfo charlieInfo = null;
if (state == null) { // pick a new charlie
charlie = _transport.pickTestPeer(from);
if (charlie != null)
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
} else {
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
if (charlie != null)
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
}
if (charlie != null)
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
if ( (charlie == null) || (charlieInfo == null) ) {
if (_log.shouldLog(Log.WARN))
@@ -578,6 +719,7 @@ class PeerTestManager {
return;
}
// TODO should only do most of this if isNew
InetAddress aliceIP = null;
SessionKey aliceIntroKey = null;
try {
@@ -597,28 +739,30 @@ class PeerTestManager {
//UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, charlieIntroKey, nonce);
//_transport.send(packet);
long now = _context.clock().now();
boolean isNew = false;
if (state == null) {
isNew = true;
state = new PeerTestState();
state.setBeginTime(_context.clock().now());
state = new PeerTestState(BOB, nonce, now);
} else {
if (state.getReceiveAliceTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too soon, not retransmitting: " + state);
return;
}
}
state.setAliceIP(aliceIP);
state.setAlicePort(from.getPort());
state.setAliceIntroKey(aliceIntroKey);
state.setNonce(nonce);
state.setCharlieIP(charlie.getRemoteIPAddress());
state.setCharliePort(charlie.getRemotePort());
state.setCharlieIntroKey(charlieIntroKey);
state.setLastSendTime(_context.clock().now());
state.setOurRole(PeerTestState.BOB);
state.setReceiveAliceTime(_context.clock().now());
state.setLastSendTime(now);
state.setReceiveAliceTime(now);
state.incrementPacketsRelayed();
if (state.getPacketsRelayed() > MAX_RELAYED_PER_TEST) {
if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_BOB) {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive from alice (" + aliceIP + ":" + from.getPort()
+ ") as bob, but we've already relayed too many packets to that test, so we're dropping it");
_log.warn("Too many, not retransmitting: " + state);
return;
}
@@ -634,13 +778,13 @@ class PeerTestManager {
charlie.getCurrentMACKey());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from alice as bob for " + nonce + ", picking charlie @ " + charlie.getRemoteIPAddress() + ":"
+ charlie.getRemotePort() + " for alice @ " + aliceIP + ":" + from.getPort());
_log.debug("Receive from Alice: " + state);
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to build the aliceIP from " + from, uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
}
@@ -648,23 +792,30 @@ class PeerTestManager {
* The PeerTest message came from one of the Charlies picked for an existing test, so send Alice the
* packet verifying participation.
*
* testInfo IP/port ignored
* @param state non-null
*/
private void receiveFromCharlieAsBob(RemoteHostId from, PeerTestState state) {
state.setReceiveCharlieTime(_context.clock().now());
state.incrementPacketsRelayed();
if (state.getPacketsRelayed() > MAX_RELAYED_PER_TEST) {
long now = _context.clock().now();
if (state.getReceiveCharlieTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received from charlie (" + from + ") as bob (" + state + "), but we've already relayed too many, so drop it");
_log.warn("Too soon, not retransmitting: " + state);
return;
}
if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_BOB) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many, not retransmitting: " + state);
return;
}
state.setReceiveCharlieTime(now);
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(state.getAliceIP(), state.getAlicePort(),
state.getAliceIntroKey(), state.getCharlieIntroKey(),
state.getNonce());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from charlie @ " + from + " as bob, sending alice back the ok @ " + state.getAliceIP() + ":" + state.getAlicePort());
_log.debug("Receive from Charlie, sending Alice back the OK: " + state);
_transport.send(packet);
}
@@ -672,32 +823,52 @@ class PeerTestManager {
/**
* We are charlie, so send Alice her PeerTest message
*
* testInfo IP/port ignored
* @param state non-null
*/
private void receiveFromAliceAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce) {
private void receiveFromAliceAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo,
long nonce, PeerTestState state) {
long now = _context.clock().now();
if (state.getReceiveAliceTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too soon, not retransmitting: " + state);
return;
}
if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_CHARLIE) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many, not retransmitting: " + state);
return;
}
state.setReceiveAliceTime(now);
try {
InetAddress aliceIP = InetAddress.getByAddress(from.getIP());
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, _transport.getIntroKey(), nonce);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from alice as charlie, w/ alice @ " + aliceIP + ":" + from.getPort() + " and nonce " + nonce);
_log.debug("Receive from Alice: " + state);
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to build the aliceIP from " + from, uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
}
/**
* forget about charlie's nonce after 60s.
* forget about charlie's nonce after a short while.
*/
private class RemoveTest implements SimpleTimer.TimedEvent {
private long _nonce;
private final long _nonce;
public RemoveTest(long nonce) {
_nonce = nonce;
}
public void timeReached() {
_activeTests.remove(Long.valueOf(_nonce));
}

View File

@@ -1,15 +1,17 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.data.SessionKey;
/**
*
* Track the state of a peer test.
* Used only by PeerTestManager.
*/
class PeerTestState {
private long _testNonce;
private short _ourRole;
private final long _testNonce;
private final Role _ourRole;
private InetAddress _aliceIP;
private int _alicePort;
private InetAddress _bobIP;
@@ -22,22 +24,26 @@ class PeerTestState {
private SessionKey _charlieIntroKey;
private SessionKey _bobCipherKey;
private SessionKey _bobMACKey;
private long _beginTime;
private final long _beginTime;
private long _lastSendTime;
private long _receiveAliceTime;
private long _receiveBobTime;
private long _receiveCharlieTime;
private int _packetsRelayed;
private final AtomicInteger _packetsRelayed = new AtomicInteger();
public static final short ALICE = 1;
public static final short BOB = 2;
public static final short CHARLIE = 3;
public enum Role {ALICE, BOB, CHARLIE};
public PeerTestState(Role role, long nonce, long now) {
_ourRole = role;
_testNonce = nonce;
_beginTime = now;
}
public long getNonce() { return _testNonce; }
public void setNonce(long nonce) { _testNonce = nonce; }
/** Are we Alice, bob, or Charlie. */
public short getOurRole() { return _ourRole; }
public void setOurRole(short role) { _ourRole = role; }
public Role getOurRole() { return _ourRole; }
/**
* If we are Alice, this will contain the IP that Bob says we
* can be reached at - the IP Charlie says we can be reached
@@ -79,47 +85,49 @@ class PeerTestState {
/** when did this test begin? */
public long getBeginTime() { return _beginTime; }
public void setBeginTime(long when) { _beginTime = when; }
/** when did we last send out a packet? */
public long getLastSendTime() { return _lastSendTime; }
public void setLastSendTime(long when) { _lastSendTime = when; }
/** when did we last hear from alice? */
/**
* when did we last hear from alice?
*/
public long getReceiveAliceTime() { return _receiveAliceTime; }
public void setReceiveAliceTime(long when) { _receiveAliceTime = when; }
/** when did we last hear from bob? */
public long getReceiveBobTime() { return _receiveBobTime; }
public void setReceiveBobTime(long when) { _receiveBobTime = when; }
/** when did we last hear from charlie? */
public long getReceiveCharlieTime() { return _receiveCharlieTime; }
public void setReceiveCharlieTime(long when) { _receiveCharlieTime = when; }
public int getPacketsRelayed() { return _packetsRelayed; }
public void incrementPacketsRelayed() { ++_packetsRelayed; }
/** @return new value */
public int incrementPacketsRelayed() { return _packetsRelayed.incrementAndGet(); }
@Override
public String toString() {
StringBuilder buf = new StringBuilder(512);
buf.append("Role: ");
if (_ourRole == ALICE) buf.append("Alice");
else if (_ourRole == BOB) buf.append("Bob");
else if (_ourRole == CHARLIE) buf.append("Charlie");
else buf.append("unkown!");
StringBuilder buf = new StringBuilder(256);
buf.append("PeerTest ").append(_testNonce)
.append(" as ").append(_ourRole.toString());
if (_aliceIP != null)
buf.append(" alice: ").append(_aliceIP).append(':').append(_alicePort);
buf.append("; Alice: ").append(_aliceIP).append(':').append(_alicePort);
if (_aliceIPFromCharlie != null)
buf.append(" (fromCharlie ").append(_aliceIPFromCharlie).append(':').append(_alicePortFromCharlie).append(')');
if (_bobIP != null)
buf.append(" bob: ").append(_bobIP).append(':').append(_bobPort);
buf.append("; Bob: ").append(_bobIP).append(':').append(_bobPort);
if (_charlieIP != null)
buf.append(" charlie: ").append(_charlieIP).append(':').append(_charliePort);
buf.append(" last send after ").append(_lastSendTime - _beginTime).append("ms");
buf.append(" Charlie: ").append(_charlieIP).append(':').append(_charliePort);
buf.append("; last send after ").append(_lastSendTime - _beginTime);
if (_receiveAliceTime > 0)
buf.append(" receive from alice after ").append(_receiveAliceTime - _beginTime).append("ms");
buf.append("; rcvd from Alice after ").append(_receiveAliceTime - _beginTime);
if (_receiveBobTime > 0)
buf.append(" receive from bob after ").append(_receiveBobTime - _beginTime).append("ms");
buf.append("; rcvd from Bob after ").append(_receiveBobTime - _beginTime);
if (_receiveCharlieTime > 0)
buf.append(" receive from charlie after ").append(_receiveCharlieTime - _beginTime).append("ms");
buf.append(" packets relayed: ").append(_packetsRelayed);
buf.append("; rcvd from Charlie after ").append(_receiveCharlieTime - _beginTime);
buf.append("; pkts relayed: ").append(_packetsRelayed.get());
return buf.toString();
}
}

View File

@@ -96,6 +96,8 @@ class UDPEndpoint {
private DatagramSocket getSocket() {
DatagramSocket socket = null;
int port = _listenPort;
if (port > 0 && port < 1024)
_log.logAlways(Log.WARN, "Specified UDP port is " + port + ", ports lower than 1024 not recommended");
for (int i = 0; i < MAX_PORT_RETRIES; i++) {
if (port <= 0) {
@@ -113,7 +115,7 @@ class UDPEndpoint {
break;
} catch (SocketException se) {
if (_log.shouldLog(Log.WARN))
_log.warn("Binding to port " + port + " failed: " + se);
_log.warn("Binding to port " + port + " failed", se);
}
port = -1;
}
@@ -157,6 +159,7 @@ class UDPEndpoint {
* @since 0.9.2
*/
public void clearOutbound() {
_sender.clear();
if (_sender != null)
_sender.clear();
}
}

View File

@@ -32,6 +32,7 @@ import net.i2p.router.CommSystemFacade;
import net.i2p.router.OutNetMessage;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.Transport;
import net.i2p.router.transport.TransportBid;
import net.i2p.router.transport.TransportImpl;
@@ -51,6 +52,7 @@ import net.i2p.util.Translate;
public class UDPTransport extends TransportImpl implements TimedWeightedPriorityMessageQueue.FailedListener {
private final Log _log;
private UDPEndpoint _endpoint;
private final Object _addDropLock = new Object();
/** Peer (Hash) to PeerState */
private final Map<Hash, PeerState> _peersByIdent;
/** RemoteHostId to PeerState */
@@ -110,6 +112,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
public static final int DEFAULT_INTERNAL_PORT = 8887;
/**
* To prevent trouble. To be raised to 1024 in 0.9.4.
*
* @since 0.9.3
*/
static final int MIN_PEER_PORT = 500;
/** Limits on port told to us by others,
* We should have an exception if it matches the existing low port.
*/
@@ -404,7 +413,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
SessionKey getIntroKey() { return _introKey; }
public int getLocalPort() { return _externalListenPort; }
/** @deprecated unused */
public int getLocalPort() {
return _endpoint != null ? _endpoint.getListenPort() : -1;
}
public InetAddress getLocalAddress() { return _externalListenHost; }
public int getExternalPort() { return _externalListenPort; }
@@ -708,7 +721,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private boolean getIsPortFixed() {
String prop = _context.getProperty(PROP_FIXED_PORT);
if (prop != null)
return Boolean.valueOf(prop).booleanValue();
return Boolean.parseBoolean(prop);
int status = getReachabilityStatus();
return status != CommSystemFacade.STATUS_REJECT_UNSOLICITED;
}
@@ -720,6 +733,23 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
PeerState getPeerState(RemoteHostId hostInfo) {
return _peersByRemoteHost.get(hostInfo);
}
/**
* Get the states for all peers at the given remote host, ignoring port.
* Used for a last-chance search for a peer that changed port, by PacketHandler.
* @since 0.9.3
*/
List<PeerState> getPeerStatesByIP(RemoteHostId hostInfo) {
List<PeerState> rv = new ArrayList(4);
byte[] ip = hostInfo.getIP();
if (ip != null) {
for (PeerState ps : _peersByIdent.values()) {
if (DataHelper.eq(ip, ps.getRemoteIP()))
rv.add(ps);
}
}
return rv;
}
/**
* get the state for the peer with the given ident, or null
@@ -729,6 +759,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return _peersByIdent.get(remotePeer);
}
/**
* Remove and add to peersByRemoteHost map
* @since 0.9.3
*/
public void changePeerPort(PeerState peer, int newPort) {
int oldPort;
synchronized (_addDropLock) {
oldPort = peer.getRemotePort();
if (oldPort != newPort) {
_peersByRemoteHost.remove(peer.getRemoteHostId());
peer.changePort(newPort);
_peersByRemoteHost.put(peer.getRemoteHostId(), peer);
}
}
if (_log.shouldLog(Log.WARN) && oldPort != newPort)
_log.warn("Changed port from " + oldPort + " to " + newPort + " for " + peer);
}
/**
* For IntroductionManager
* @return may be null if not started
@@ -799,47 +847,69 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
boolean addRemotePeerState(PeerState peer) {
if (_log.shouldLog(Log.INFO))
_log.info("Add remote peer state: " + peer);
synchronized(_addDropLock) {
return locked_addRemotePeerState(peer);
}
}
private boolean locked_addRemotePeerState(PeerState peer) {
Hash remotePeer = peer.getRemotePeer();
long oldEstablishedOn = -1;
PeerState oldPeer = null;
if (remotePeer != null) {
oldPeer = _peersByIdent.put(remotePeer, peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
// transfer over the old state/inbound message fragments/etc
peer.loadFrom(oldPeer);
oldEstablishedOn = oldPeer.getKeyEstablishedTime();
}
oldPeer = _peersByIdent.put(remotePeer, peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Peer already connected (PBID): old=" + oldPeer + " new=" + peer);
// transfer over the old state/inbound message fragments/etc
peer.loadFrom(oldPeer);
oldEstablishedOn = oldPeer.getKeyEstablishedTime();
}
}
RemoteHostId remoteId = peer.getRemoteHostId();
if (oldPeer != null) {
oldPeer.dropOutbound();
_introManager.remove(oldPeer);
_expireEvent.remove(oldPeer);
RemoteHostId oldID = oldPeer.getRemoteHostId();
if (!remoteId.equals(oldID)) {
// leak fix, remove old address
if (_log.shouldLog(Log.WARN))
_log.warn(remotePeer + " changed address FROM " + oldID + " TO " + remoteId);
PeerState oldPeer2 = _peersByRemoteHost.remove(oldID);
// different ones in the two maps? shouldn't happen
if (oldPeer2 != oldPeer) {
oldPeer2.dropOutbound();
_introManager.remove(oldPeer2);
_expireEvent.remove(oldPeer2);
}
}
}
oldPeer = null;
RemoteHostId remoteId = peer.getRemoteHostId();
if (remoteId == null) return false;
// Should always be direct... except maybe for hidden mode?
// or do we always know the IP by now?
if (remoteId.getIP() == null && _log.shouldLog(Log.WARN))
_log.warn("Add indirect: " + peer);
oldPeer = _peersByRemoteHost.put(remoteId, peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
// don't do this twice
PeerState oldPeer2 = _peersByRemoteHost.put(remoteId, peer);
if (oldPeer2 != null && oldPeer2 != peer && oldPeer2 != oldPeer) {
// this shouldn't happen, should have been removed above
if (_log.shouldLog(Log.WARN))
_log.warn("Peer already connected (PBRH): old=" + oldPeer2 + " new=" + peer);
// transfer over the old state/inbound message fragments/etc
peer.loadFrom(oldPeer);
oldEstablishedOn = oldPeer.getKeyEstablishedTime();
peer.loadFrom(oldPeer2);
oldEstablishedOn = oldPeer2.getKeyEstablishedTime();
oldPeer2.dropOutbound();
_introManager.remove(oldPeer2);
_expireEvent.remove(oldPeer2);
}
if (oldPeer != null) {
oldPeer.dropOutbound();
_introManager.remove(oldPeer);
_expireEvent.remove(oldPeer);
}
if ( (oldPeer != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("Peer already connected: old=" + oldPeer + " new=" + peer, new Exception("dup"));
if (_log.shouldLog(Log.WARN) && _peersByIdent.size() != _peersByRemoteHost.size())
_log.warn("Size Mismatch after add: " + peer
+ " byIDsz = " + _peersByIdent.size()
+ " byHostsz = " + _peersByRemoteHost.size());
_activeThrottle.unchoke(peer.getRemotePeer());
markReachable(peer.getRemotePeer(), peer.isInbound());
@@ -996,15 +1066,20 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
_log.info(buf.toString(), new Exception("Dropped by"));
}
synchronized(_addDropLock) {
locked_dropPeer(peer, shouldShitlist, why);
}
if (needsRebuild())
rebuildExternalAddress();
}
private void locked_dropPeer(PeerState peer, boolean shouldShitlist, String why) {
peer.dropOutbound();
peer.expireInboundMessages();
_introManager.remove(peer);
_fragments.dropPeer(peer);
PeerState altByIdent = null;
PeerState altByHost = null;
if (peer.getRemotePeer() != null) {
dropPeerCapacities(peer);
@@ -1018,9 +1093,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
RemoteHostId remoteId = peer.getRemoteHostId();
if (remoteId != null) {
altByHost = _peersByRemoteHost.remove(remoteId);
}
PeerState altByHost = _peersByRemoteHost.remove(remoteId);
if (altByIdent != altByHost && _log.shouldLog(Log.WARN))
_log.warn("Mismatch on remove, RHID = " + remoteId
+ " byID = " + altByIdent
+ " byHost = " + altByHost
+ " byIDsz = " + _peersByIdent.size()
+ " byHostsz = " + _peersByRemoteHost.size());
// unchoke 'em, but just because we'll never talk again...
_activeThrottle.unchoke(peer.getRemotePeer());
@@ -1029,12 +1109,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
// _flooder.removePeer(peer);
_expireEvent.remove(peer);
if (needsRebuild())
rebuildExternalAddress();
// deal with races to make sure we drop the peers fully
if ( (altByIdent != null) && (peer != altByIdent) ) dropPeer(altByIdent, shouldShitlist, "recurse");
if ( (altByHost != null) && (peer != altByHost) ) dropPeer(altByHost, shouldShitlist, "recurse");
if ( (altByIdent != null) && (peer != altByIdent) ) locked_dropPeer(altByIdent, shouldShitlist, "recurse");
if ( (altByHost != null) && (peer != altByHost) ) locked_dropPeer(altByHost, shouldShitlist, "recurse");
}
private boolean needsRebuild() {
@@ -1145,20 +1222,33 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
/**
* Send a session destroy message to everybody
* BLOCKING if OB queue is full.
* Send a session destroy message to everybody.
* BLOCKING for at least 1 sec per 1K peers, more if BW is very low or if OB queue is full.
*
* @since 0.8.9
*/
private void destroyAll() {
_endpoint.clearOutbound();
int howMany = _peersByIdent.size();
// use no more than 1/4 of configured bandwidth
final int burst = 8;
int pps = Math.max(48, (_context.bandwidthLimiter().getOutboundKBytesPerSecond() * 1000 / 4) / 48);
int burstps = pps / burst;
// max of 1000 pps
int toSleep = Math.max(8, (1000 / burstps));
int count = 0;
if (_log.shouldLog(Log.WARN))
_log.warn("Sending destroy to : " + howMany + " peers");
for (PeerState peer : _peersByIdent.values()) {
sendDestroy(peer);
// 1000 per second * 48 bytes = 400 KBps
if ((++count) % burst == 0) {
try {
Thread.sleep(toSleep);
} catch (InterruptedException ie) {}
}
}
int toSleep = Math.min(howMany / 3, 750);
toSleep = Math.min(howMany / 3, 750);
if (toSleep > 0) {
try {
Thread.sleep(toSleep);
@@ -1205,7 +1295,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if (addr.getOption("ihost0") == null) {
byte[] ip = addr.getIP();
int port = addr.getPort();
if (ip == null || port <= 0 ||
if (ip == null || port < MIN_PEER_PORT ||
(!isValid(ip)) ||
Arrays.equals(ip, getExternalIP())) {
markUnreachable(to);
@@ -1503,7 +1593,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* it's usually false positives.
******************
String forceIntroducers = _context.getProperty(PROP_FORCE_INTRODUCERS);
if ( (forceIntroducers != null) && (Boolean.valueOf(forceIntroducers).booleanValue()) ) {
if ( (forceIntroducers != null) && (Boolean.parseBoolean(forceIntroducers)) ) {
if (_log.shouldLog(Log.INFO))
_log.info("Force introducers specified");
return true;
@@ -1526,6 +1616,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
}
/**
* For EstablishmentManager
* @since 0.9.3
*/
boolean canIntroduce() {
// we don't expect inbound connections when hidden, but it could happen
// Don't offer if we are approaching max connections. While Relay Intros do not
// count as connections, we have to keep the connection to this peer up longer if
// we are offering introductions.
return
(!_context.router().isHidden()) &&
(!introducersRequired()) &&
haveCapacity() &&
(!((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) &&
_introManager.introducedCount() < IntroductionManager.MAX_OUTBOUND &&
_introManager.introducedCount() < getMaxConnections() / 4;
}
/** default true */
private boolean allowDirectUDP() {
return _context.getBooleanPropertyDefaultTrue(PROP_ALLOW_DIRECT);
@@ -1688,7 +1796,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
public boolean allowConnection() {
return _peersByIdent.size() < getMaxConnections();
}
@@ -1698,20 +1805,17 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
@Override
public Vector<Long> getClockSkews() {
Vector<Long> skews = new Vector();
Vector<PeerState> peers = new Vector();
peers.addAll(_peersByIdent.values());
// If our clock is way off, we may not have many (or any) successful connections,
// so try hard in that case to return good data
boolean includeEverybody = _context.router().getUptime() < 10*60*1000 || peers.size() < 10;
boolean includeEverybody = _context.router().getUptime() < 10*60*1000 || _peersByIdent.size() < 10;
long now = _context.clock().now();
for (Iterator<PeerState> iter = peers.iterator(); iter.hasNext(); ) {
PeerState peer = iter.next();
if ((!includeEverybody) && now - peer.getLastReceiveTime() > 15*60*1000)
for (PeerState peer : _peersByIdent.values()) {
if ((!includeEverybody) && now - peer.getLastReceiveTime() > 5*60*1000)
continue; // skip old peers
if (peer.getRTT() > PeerState.INIT_RTT - 250)
continue; // Big RTT makes for a poor calculation
skews.addElement(Long.valueOf(peer.getClockSkew() / 1000));
}
if (_log.shouldLog(Log.DEBUG))
@@ -1744,7 +1848,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private static final int FLAG_CWND= 6;
private static final int FLAG_SSTHRESH = 7;
private static final int FLAG_RTT = 8;
private static final int FLAG_DEV = 9;
//private static final int FLAG_DEV = 9;
private static final int FLAG_RTO = 10;
private static final int FLAG_MTU = 11;
private static final int FLAG_SEND = 12;
@@ -1784,9 +1888,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
case FLAG_RTT:
rv = RTTComparator.instance();
break;
case FLAG_DEV:
rv = DevComparator.instance();
break;
//case FLAG_DEV:
// rv = DevComparator.instance();
// break;
case FLAG_RTO:
rv = RTOComparator.instance();
break;
@@ -1927,6 +2031,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return rv;
}
}
/***
private static class DevComparator extends PeerComparator {
private static final DevComparator _instance = new DevComparator();
public static final DevComparator instance() { return _instance; }
@@ -1939,6 +2045,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return (int)rv;
}
}
****/
/** */
private static class RTOComparator extends PeerComparator {
private static final RTOComparator _instance = new RTOComparator();
public static final RTOComparator instance() { return _instance; }
@@ -2088,8 +2197,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.rtt\">RTT</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _("Sort by round trip time"), FLAG_RTT);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.dev\">").append(_("Dev")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _("Sort by round trip time deviation"), FLAG_DEV);
//buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.dev\">").append(_("Dev")).append("</a><br>");
//appendSortLinks(buf, urlBase, sortFlags, _("Sort by round trip time deviation"), FLAG_DEV);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.rto\">RTO</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _("Sort by retransmission timeout"), FLAG_RTO);
buf.append("</th>\n");
@@ -2212,9 +2321,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
buf.append(DataHelper.formatDuration2(rtt));
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(peer.getRTTDeviation()));
buf.append("</td>");
//buf.append("<td class=\"cells\" align=\"right\">");
//buf.append(DataHelper.formatDuration2(peer.getRTTDeviation()));
//buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(rto));
@@ -2291,7 +2400,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
buf.append("</b></td><td>&nbsp;</td>\n" +
"<td align=\"center\"><b>");
buf.append(numPeers > 0 ? DataHelper.formatDuration2(rttTotal/numPeers) : '0');
buf.append("</b></td><td>&nbsp;</td><td align=\"center\"><b>");
//buf.append("</b></td><td>&nbsp;</td><td align=\"center\"><b>");
buf.append("</b></td><td align=\"center\"><b>");
buf.append(numPeers > 0 ? DataHelper.formatDuration2(rtoTotal/numPeers) : '0');
buf.append("</b></td><td align=\"center\"><b>").append(_mtu).append("</b></td><td align=\"center\"><b>");
buf.append(sendTotal).append("</b></td><td align=\"center\"><b>").append(recvTotal).append("</b></td>\n" +
@@ -2365,9 +2475,18 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
private class ExpirePeerEvent extends SimpleTimer2.TimedEvent {
// TODO why have separate Set, just use _peersByIdent.values()
private final Set<PeerState> _expirePeers;
private final List<PeerState> _expireBuffer;
private volatile boolean _alive;
private int _runCount;
// we've seen firewalls change ports after 40 seconds
private static final long PING_FIREWALL_TIME = 30*1000;
private static final long PING_FIREWALL_CUTOFF = PING_FIREWALL_TIME / 2;
// ping 1/4 of the peers every loop
private static final int SLICES = 4;
private static final long SHORT_LOOP_TIME = PING_FIREWALL_CUTOFF / (SLICES + 1);
private static final long LONG_LOOP_TIME = 25*1000;
public ExpirePeerEvent() {
super(_context.simpleTimer2());
@@ -2381,10 +2500,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_expireTimeout = Math.min(_expireTimeout + 15*1000, EXPIRE_TIMEOUT);
else
_expireTimeout = Math.max(_expireTimeout - 45*1000, MIN_EXPIRE_TIMEOUT);
long shortInactivityCutoff = _context.clock().now() - _expireTimeout;
long longInactivityCutoff = _context.clock().now() - EXPIRE_TIMEOUT;
long pingCutoff = _context.clock().now() - (2 * 60*60*1000);
long now = _context.clock().now();
long shortInactivityCutoff = now - _expireTimeout;
long longInactivityCutoff = now - EXPIRE_TIMEOUT;
long pingCutoff = now - (2 * 60*60*1000);
long pingFirewallCutoff = now - PING_FIREWALL_CUTOFF;
boolean shouldPingFirewall = _reachabilityStatus != CommSystemFacade.STATUS_OK;
boolean pingOneOnly = shouldPingFirewall && _externalListenPort == _endpoint.getListenPort();
boolean shortLoop = shouldPingFirewall;
_expireBuffer.clear();
_runCount++;
for (Iterator<PeerState> iter = _expirePeers.iterator(); iter.hasNext(); ) {
PeerState peer = iter.next();
@@ -2397,7 +2522,25 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if ( (peer.getLastReceiveTime() < inactivityCutoff) && (peer.getLastSendTime() < inactivityCutoff) ) {
_expireBuffer.add(peer);
iter.remove();
}
} else if (shouldPingFirewall &&
((_runCount ^ peer.hashCode()) & (SLICES - 1)) == 0 &&
peer.getLastSendOrPingTime() < pingFirewallCutoff &&
peer.getLastReceiveTime() < pingFirewallCutoff) {
// ping if firewall is mapping the port to keep port the same...
// if the port changes we are screwed
if (_log.shouldLog(Log.DEBUG))
_log.debug("Pinging for firewall: " + peer);
// don't update or idle time won't be right and peer won't get dropped
// TODO if both sides are firewalled should only one ping
// or else session will stay open forever?
//peer.setLastSendTime(now);
send(_destroyBuilder.buildPing(peer));
peer.setLastPingTime(now);
// If external port is different, it may be changing the port for every
// session, so ping all of them. Otherwise only one.
if (pingOneOnly)
shouldPingFirewall = false;
}
}
for (PeerState peer : _expireBuffer) {
@@ -2407,18 +2550,21 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_expireBuffer.clear();
if (_alive)
schedule(30*1000);
schedule(shortLoop ? SHORT_LOOP_TIME : LONG_LOOP_TIME);
}
public void add(PeerState peer) {
_expirePeers.add(peer);
}
public void remove(PeerState peer) {
_expirePeers.remove(peer);
}
public void setIsAlive(boolean isAlive) {
_alive = isAlive;
if (isAlive) {
reschedule(30*1000);
reschedule(LONG_LOOP_TIME);
} else {
cancel();
_expirePeers.clear();
@@ -2507,8 +2653,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if (peerInfo == null)
continue;
RouterAddress addr = peerInfo.getTargetAddress(STYLE);
if (addr != null)
return peer;
if (addr == null)
continue;
byte[] ip = addr.getIP();
if (ip == null)
continue;
if (DataHelper.eq(ip, 0, getExternalIP(), 0, 2))
continue;
return peer;
}
return null;
}

View File

@@ -392,6 +392,7 @@ class BuildExecutor implements Runnable {
pools.clear();
} catch (RuntimeException e) {
_log.log(Log.CRIT, "B0rked in the tunnel builder", e);
try { Thread.sleep(LOOP_TIME); } catch (InterruptedException ie) {}
}
}

View File

@@ -460,7 +460,7 @@ public abstract class TunnelPeerSelector {
else
val = ctx.getProperty(PROP_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE, DEFAULT_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE);
boolean rv = (val != null ? Boolean.valueOf(val).booleanValue() : def);
boolean rv = (val != null ? Boolean.parseBoolean(val) : def);
//System.err.println("Filter unreachable? " + rv + " (inbound? " + isInbound + ", exploratory? " + isExploratory);
return rv;
}
@@ -490,7 +490,7 @@ public abstract class TunnelPeerSelector {
else
val = ctx.getProperty(PROP_OUTBOUND_CLIENT_EXCLUDE_SLOW);
boolean rv = (val != null ? Boolean.valueOf(val).booleanValue() : def);
boolean rv = (val != null ? Boolean.parseBoolean(val) : def);
//System.err.println("Filter unreachable? " + rv + " (inbound? " + isInbound + ", exploratory? " + isExploratory);
return rv;
}
@@ -519,7 +519,7 @@ public abstract class TunnelPeerSelector {
else
val = ctx.getProperty(PROP_OUTBOUND_CLIENT_EXCLUDE_UPTIME);
boolean rv = (val != null ? Boolean.valueOf(val).booleanValue() : def);
boolean rv = (val != null ? Boolean.parseBoolean(val) : def);
//System.err.println("Filter unreachable? " + rv + " (inbound? " + isInbound + ", exploratory? " + isExploratory);
return rv;
}

View File

@@ -75,7 +75,7 @@ public class TunnelPool {
* Destination (i.e. for servers or clients w/ persistent key,
* or restarting close-on-idle clients)
*/
void startup() {
synchronized void startup() {
synchronized (_inProgress) {
_inProgress.clear();
}
@@ -102,7 +102,7 @@ public class TunnelPool {
new long[] { 5*60*1000l });
}
void shutdown() {
synchronized void shutdown() {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": Shutdown called");
_alive = false;

View File

@@ -37,8 +37,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
private final Map<Hash, TunnelPool> _clientInboundPools;
/** Hash (destination) to TunnelPool */
private final Map<Hash, TunnelPool> _clientOutboundPools;
private TunnelPool _inboundExploratory;
private TunnelPool _outboundExploratory;
private final TunnelPool _inboundExploratory;
private final TunnelPool _outboundExploratory;
private final BuildExecutor _executor;
private final BuildHandler _handler;
private final TunnelPeerSelector _clientPeerSelector;
@@ -62,10 +62,15 @@ public class TunnelPoolManager implements TunnelManagerFacade {
_clientInboundPools = new ConcurrentHashMap(4);
_clientOutboundPools = new ConcurrentHashMap(4);
_clientPeerSelector = new ClientPeerSelector(ctx);
ExploratoryPeerSelector selector = new ExploratoryPeerSelector(_context);
TunnelPoolSettings inboundSettings = new TunnelPoolSettings(true, true);
_inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector);
TunnelPoolSettings outboundSettings = new TunnelPoolSettings(true, false);
_outboundExploratory = new TunnelPool(_context, this, outboundSettings, selector);
// threads will be started in startup()
_executor = new BuildExecutor(ctx, this);
I2PThread execThread = new I2PThread(_executor, "BuildExecutor", true);
execThread.start();
_handler = new BuildHandler(ctx, this, _executor);
int numHandlerThreads;
int share = TunnelDispatcher.getShareBandwidth(ctx);
@@ -76,10 +81,6 @@ public class TunnelPoolManager implements TunnelManagerFacade {
else
numHandlerThreads = 1;
_numHandlerThreads = ctx.getProperty("router.buildHandlerThreads", numHandlerThreads);
for (int i = 1; i <= _numHandlerThreads; i++) {
I2PThread hThread = new I2PThread(_handler, "BuildHandler " + i + '/' + numHandlerThreads, true);
hThread.start();
}
// The following are for TestJob
ctx.statManager().createRequiredRateStat("tunnel.testFailedTime", "Time for tunnel test failure (ms)", "Tunnels",
@@ -104,9 +105,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
* @return null if none
*/
public TunnelInfo selectInboundTunnel() {
TunnelPool pool = _inboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
TunnelInfo info = _inboundExploratory.selectTunnel();
if (info == null) {
_inboundExploratory.buildFallback();
// still can be null, but probably not
@@ -139,13 +138,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
* @return null if none
*/
public TunnelInfo selectOutboundTunnel() {
TunnelPool pool = _outboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
TunnelInfo info = _outboundExploratory.selectTunnel();
if (info == null) {
pool.buildFallback();
_outboundExploratory.buildFallback();
// still can be null, but probably not
info = pool.selectTunnel();
info = _outboundExploratory.selectTunnel();
}
return info;
}
@@ -176,9 +173,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
* @since 0.8.10
*/
public TunnelInfo selectInboundExploratoryTunnel(Hash closestTo) {
TunnelPool pool = _inboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
TunnelInfo info = _inboundExploratory.selectTunnel();
if (info == null) {
_inboundExploratory.buildFallback();
// still can be null, but probably not
@@ -222,13 +217,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
* @since 0.8.10
*/
public TunnelInfo selectOutboundExploratoryTunnel(Hash closestTo) {
TunnelPool pool = _outboundExploratory;
if (pool == null) return null;
TunnelInfo info = pool.selectTunnel();
TunnelInfo info = _outboundExploratory.selectTunnel();
if (info == null) {
pool.buildFallback();
_outboundExploratory.buildFallback();
// still can be null, but probably not
info = pool.selectTunnel(closestTo);
info = _outboundExploratory.selectTunnel(closestTo);
}
return info;
}
@@ -261,14 +254,10 @@ public class TunnelPoolManager implements TunnelManagerFacade {
if (info != null)
return info;
}
if (_inboundExploratory != null) {
info = _inboundExploratory.getTunnel(id);
if (info != null) return info;
}
if (_outboundExploratory != null) {
info = _outboundExploratory.getTunnel(id);
if (info != null) return info;
}
info = _inboundExploratory.getTunnel(id);
if (info != null) return info;
info = _outboundExploratory.getTunnel(id);
if (info != null) return info;
return null;
}
@@ -282,9 +271,6 @@ public class TunnelPoolManager implements TunnelManagerFacade {
/** @return number of outbound exploratory tunnels */
public int getOutboundTunnelCount() {
if (_outboundExploratory == null)
return 0;
else
return _outboundExploratory.size();
}
@@ -507,22 +493,15 @@ public class TunnelPoolManager implements TunnelManagerFacade {
public synchronized void startup() {
_isShutdown = false;
if (!_executor.isRunning()) {
I2PThread t = new I2PThread(_executor, "BuildExecutor");
t.setDaemon(true);
I2PThread t = new I2PThread(_executor, "BuildExecutor", true);
t.start();
for (int i = 1; i <= _numHandlerThreads; i++) {
I2PThread hThread = new I2PThread(_handler, "BuildHandler " + i + '/' + _numHandlerThreads, true);
hThread.start();
}
}
ExploratoryPeerSelector selector = new ExploratoryPeerSelector(_context);
TunnelPoolSettings inboundSettings = new TunnelPoolSettings();
inboundSettings.setIsExploratory(true);
inboundSettings.setIsInbound(true);
_inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector);
_inboundExploratory.startup();
TunnelPoolSettings outboundSettings = new TunnelPoolSettings();
outboundSettings.setIsExploratory(true);
outboundSettings.setIsInbound(false);
_outboundExploratory = new TunnelPool(_context, this, outboundSettings, selector);
_context.simpleScheduler().addEvent(new DelayedStartup(_outboundExploratory), 3*1000);
// try to build up longer tunnels
@@ -554,9 +533,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
}
private void shutdownExploratory() {
if (_inboundExploratory != null)
_inboundExploratory.shutdown();
if (_outboundExploratory != null)
_outboundExploratory.shutdown();
}
@@ -564,10 +541,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
public void listPools(List<TunnelPool> out) {
out.addAll(_clientInboundPools.values());
out.addAll(_clientOutboundPools.values());
if (_inboundExploratory != null)
out.add(_inboundExploratory);
if (_outboundExploratory != null)
out.add(_outboundExploratory);
out.add(_inboundExploratory);
out.add(_outboundExploratory);
}
void tunnelFailed() { _executor.repoll(); }
BuildExecutor getExecutor() { return _executor; }
@@ -639,12 +614,18 @@ public class TunnelPoolManager implements TunnelManagerFacade {
return new HashMap(_clientOutboundPools);
}
/** for TunnelRenderer in router console */
/**
* For TunnelRenderer in router console
* @return non-null
*/
public TunnelPool getInboundExploratoryPool() {
return _inboundExploratory;
}
/** for TunnelRenderer in router console */
/**
* For TunnelRenderer in router console
* @return non-null
*/
public TunnelPool getOutboundExploratoryPool() {
return _outboundExploratory;
}
@@ -658,13 +639,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
* @since 0.8.13
*/
public void fail(Hash peer) {
if (_outboundExploratory != null)
failTunnelsWithFirstHop(_outboundExploratory, peer);
failTunnelsWithFirstHop(_outboundExploratory, peer);
for (TunnelPool pool : _clientOutboundPools.values()) {
failTunnelsWithFirstHop(pool, peer);
}
if (_inboundExploratory != null)
failTunnelsWithLastHop(_inboundExploratory, peer);
failTunnelsWithLastHop(_inboundExploratory, peer);
for (TunnelPool pool : _clientInboundPools.values()) {
failTunnelsWithLastHop(pool, peer);
}

View File

@@ -37,9 +37,11 @@ public class EventLog {
public static final String CHANGE_PORT = "changePort";
public static final String CLOCK_SHIFT = "clockShift";
public static final String CRASHED = "crashed";
public static final String CRITICAL = "critical";
public static final String INSTALLED = "installed";
public static final String INSTALL_FAILED = "intallFailed";
public static final String NEW_IDENT = "newIdent";
public static final String OOM = "oom";
public static final String REKEYED = "rekeyed";
public static final String SOFT_RESTART = "softRestart";
public static final String STARTED = "started";
@@ -48,12 +50,11 @@ public class EventLog {
public static final String WATCHDOG = "watchdog";
/**
* @param file must be absolute
* @throws IllegalArgumentException if not absolute
* @param file should be absolute
*/
public EventLog(I2PAppContext ctx, File file) {
if (!file.isAbsolute())
throw new IllegalArgumentException();
//if (!file.isAbsolute())
// throw new IllegalArgumentException();
_context = ctx;
_file = file;
_cache = new HashMap(4);
@@ -128,7 +129,7 @@ public class EventLog {
continue;
Long ltime = Long.valueOf(time);
String info = s.length > 2 ? s[2] : "";
rv.put(time, info);
rv.put(ltime, info);
} catch (IndexOutOfBoundsException ioobe) {
} catch (NumberFormatException nfe) {
}

View File

@@ -11,20 +11,24 @@ import java.util.Locale;
*/
public abstract class RFC822Date {
// SimpleDateFormat is not thread-safe, methods must be synchronized
private static final SimpleDateFormat OUTPUT_FORMAT = new SimpleDateFormat("d MMM yyyy HH:mm:ss z", Locale.US);
/**
* http://jimyjoshi.com/blog/2007/08/rfc822dateparsinginjava.html
* Apparently public domain
* Probably don't need all of these...
*/
private static final SimpleDateFormat rfc822DateFormats[] = new SimpleDateFormat[] {
OUTPUT_FORMAT,
new SimpleDateFormat("EEE, d MMM yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, d MMM yy HH:mm z", Locale.US),
new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US),
new SimpleDateFormat("EEE, d MMM yyyy HH:mm z", Locale.US),
new SimpleDateFormat("d MMM yy HH:mm z", Locale.US),
new SimpleDateFormat("d MMM yy HH:mm:ss z", Locale.US),
new SimpleDateFormat("d MMM yyyy HH:mm z", Locale.US),
new SimpleDateFormat("d MMM yyyy HH:mm:ss z", Locale.US)
new SimpleDateFormat("d MMM yyyy HH:mm z", Locale.US)
};
/**
@@ -33,7 +37,7 @@ public abstract class RFC822Date {
* @param s non-null
* @return -1 on failure
*/
public static long parse822Date(String s) {
public synchronized static long parse822Date(String s) {
for (int i = 0; i < rfc822DateFormats.length; i++) {
try {
Date date = rfc822DateFormats[i].parse(s);
@@ -44,8 +48,12 @@ public abstract class RFC822Date {
return -1;
}
/** @since 0.8.2 */
public static String to822Date(long t) {
return (new SimpleDateFormat("d MMM yyyy HH:mm:ss z", Locale.US)).format(new Date(t));
/**
* Format is "d MMM yyyy HH:mm:ss z"
*
* @since 0.8.2
*/
public synchronized static String to822Date(long t) {
return OUTPUT_FORMAT.format(new Date(t));
}
}