diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 14aa275a40..12379505b2 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -1006,4 +1006,22 @@ public class I2PAppContext { public ClientAppManager clientAppManager() { return _appManager; } + + /** + * How long this router was down before it started, or 0 if unknown. + * + * This may be used for a determination of whether to regenerate keys, for example. + * We use the timestamp of the previous ping file left behind on crash, + * as set by isOnlyRouterRunning(), if present. + * Otherwise, the last STOPPED entry in the event log. + * + * May take a while to run the first time, if it has to go through the event log. + * Once called, the result is cached. + * + * @return 0 always in app context + * @since 0.0.47 + */ + public long getEstimatedDowntime() { + return 0L; + } } diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index abb736c803..e1984d8172 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -105,6 +105,7 @@ public class Router implements RouterClock.ClockShiftListener { private boolean _familyKeyCryptoFail; public final Object _familyKeyLock = new Object(); private UPnPScannerCallback _upnpScannerCallback; + private long _downtime = -1; public final static String PROP_CONFIG_FILE = "router.configLocation"; @@ -711,7 +712,8 @@ public class Router implements RouterClock.ClockShiftListener { synchronized(_configFileLock) { // persistent key for peer ordering since 0.9.17 // These will be replaced in CreateRouterInfoJob if we rekey - if (!_config.containsKey(PROP_IB_RANDOM_KEY)) { + if (!_config.containsKey(PROP_IB_RANDOM_KEY) || + getEstimatedDowntime() > 12*60*60*1000L) { byte rk[] = new byte[32]; _context.random().nextBytes(rk); _config.put(PROP_IB_RANDOM_KEY, Base64.encode(rk)); @@ -1809,6 +1811,9 @@ public class Router implements RouterClock.ClockShiftListener { ((RouterClock) _context.clock()).removeShiftListener(this); // Let's not stop accepting tunnels, etc //_started = _context.clock().now(); + synchronized(_configFileLock) { + _downtime = 1; + } Thread t = new I2PThread(new Restarter(_context), "Router Restart"); t.setPriority(Thread.NORM_PRIORITY + 1); t.start(); @@ -1915,7 +1920,12 @@ public class Router implements RouterClock.ClockShiftListener { File f = getPingFile(); if (f.exists()) { long lastWritten = f.lastModified(); - if (System.currentTimeMillis()-lastWritten > LIVELINESS_DELAY) { + long downtime = System.currentTimeMillis() - lastWritten; + synchronized(_configFileLock) { + if (downtime > 0 && _downtime < 0) + _downtime = downtime; + } + if (downtime > LIVELINESS_DELAY) { System.err.println("WARN: Old router was not shut down gracefully, deleting " + f); f.delete(); } else { @@ -1934,6 +1944,49 @@ public class Router implements RouterClock.ClockShiftListener { _context.simpleTimer2().addPeriodicEvent(new MarkLiveliness(this, f), 0, LIVELINESS_DELAY - (5*1000)); } + /** + * How long this router was down before it started, or 0 if unknown. + * + * This may be used for a determination of whether to regenerate keys, for example. + * We use the timestamp of the previous ping file left behind on crash, + * as set by isOnlyRouterRunning(), if present. + * Otherwise, the last STOPPED entry in the event log. + * + * May take a while to run the first time, if it has to go through the event log. + * Once called, the result is cached. + * + * @since 0.0.47 + */ + public long getEstimatedDowntime() { + synchronized(_configFileLock) { + if (_downtime >= 0) + return _downtime; + long begin = System.currentTimeMillis(); + long stopped = _eventLog.getLastEvent(EventLog.STOPPED, _context.clock().now() - 365*24*60*60*1000L); + long downtime = stopped > 0 ? _started - stopped : 0; + if (downtime < 0) + downtime = 0; + if (_log.shouldWarn()) + _log.warn("Downtime was " + DataHelper.formatDuration(downtime) + + "; calculation took " + DataHelper.formatDuration(System.currentTimeMillis() - begin)); + _downtime = downtime; + return downtime; + } + } + + /** + * Only for soft restart. Not for external use. + * + * @since 0.0.47 + */ + public void setEstimatedDowntime(long downtime) { + if (downtime <= 0) + downtime = 1; + synchronized(_configFileLock) { + _downtime = downtime; + } + } + public static final String PROP_BANDWIDTH_SHARE_PERCENTAGE = "router.sharePercentage"; public static final int DEFAULT_SHARE_PERCENTAGE = 80; diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 2764517ff8..8a4096fdfb 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -706,4 +706,24 @@ public class RouterContext extends I2PAppContext { public ECIESAEADEngine eciesEngine() { return _eciesEngine; } + + /** + * How long this router was down before it started, or 0 if unknown. + * + * This may be used for a determination of whether to regenerate keys, for example. + * We use the timestamp of the previous ping file left behind on crash, + * as set by isOnlyRouterRunning(), if present. + * Otherwise, the last STOPPED entry in the event log. + * + * May take a while to run the first time, if it has to go through the event log. + * Once called, the result is cached. + * + * @return downtime in ms or 0 if unknown + * @since 0.0.47 + */ + public long getEstimatedDowntime() { + if (_router == null) + return 0L; + return _router.getEstimatedDowntime(); + } } diff --git a/router/java/src/net/i2p/router/tasks/Restarter.java b/router/java/src/net/i2p/router/tasks/Restarter.java index c904cad507..bd28eed5ca 100644 --- a/router/java/src/net/i2p/router/tasks/Restarter.java +++ b/router/java/src/net/i2p/router/tasks/Restarter.java @@ -17,6 +17,7 @@ public class Restarter implements Runnable { } public void run() { + Long start = System.currentTimeMillis(); _context.router().eventLog().addEvent(EventLog.SOFT_RESTART); Log log = _context.logManager().getLog(Router.class); log.error("Stopping the router for a restart..."); @@ -36,6 +37,7 @@ public class Restarter implements Runnable { log.logAlways(Log.WARN, "Router teardown complete, restarting the router..."); try { Thread.sleep(10*1000); } catch (InterruptedException ie) {} + _context.router().setEstimatedDowntime(System.currentTimeMillis() - start); log.logAlways(Log.WARN, "Restarting the comm system"); log.logAlways(Log.WARN, "Restarting the tunnel manager"); diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index 0025d32106..d56c59c7e1 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -252,12 +252,7 @@ public class NTCPTransport extends TransportImpl { String b64IV = null; String s = null; // try to determine if we've been down for 30 days or more - // no stopping, no crashes, and only one start (this one) - EventLog el = _context.router().eventLog(); - long since = _context.clock().now() - MIN_DOWNTIME_TO_REKEY; - boolean shouldRekey = el.getEvents(EventLog.STOPPED, since).isEmpty() && - el.getEvents(EventLog.CRASHED, since).isEmpty() && - el.getEvents(EventLog.STARTED, since).size() <= 1; + boolean shouldRekey = _context.getEstimatedDowntime() >= MIN_DOWNTIME_TO_REKEY; if (!shouldRekey) { s = ctx.getProperty(PROP_NTCP2_SP); if (s != null) { diff --git a/router/java/src/net/i2p/router/util/EventLog.java b/router/java/src/net/i2p/router/util/EventLog.java index c1ec7c4644..255f4c1207 100644 --- a/router/java/src/net/i2p/router/util/EventLog.java +++ b/router/java/src/net/i2p/router/util/EventLog.java @@ -190,4 +190,40 @@ public class EventLog { } return rv; } + + /** + * Timestamp of last event. + * + * @param event matching this event, case sensitive + * @param since since this time, 0 for all + * @return last event time, or 0 for none + * @since 0.9.47 + */ + public synchronized long getLastEvent(String event, long since) { + long rv = 0; + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader( + new FileInputStream(_file), "UTF-8")); + String line = null; + while ( (line = br.readLine()) != null) { + try { + String[] s = DataHelper.split(line.trim(), " ", 3); + if (s.length < 2) + continue; + if (!s[1].equals(event)) + continue; + long time = Long.parseLong(s[0]); + if (time <= since) + continue; + rv = time; + } catch (NumberFormatException nfe) { + } + } + } catch (IOException ioe) { + } finally { + if (br != null) try { br.close(); } catch (IOException ioe) {} + } + return rv; + } }