diff --git a/core/java/src/net/i2p/time/BuildTime.java b/core/java/src/net/i2p/time/BuildTime.java new file mode 100644 index 000000000..be47ce7a1 --- /dev/null +++ b/core/java/src/net/i2p/time/BuildTime.java @@ -0,0 +1,157 @@ +package net.i2p.time; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import net.i2p.I2PAppContext; +import net.i2p.util.SystemVersion; + +/** + * Get the build date as set in i2p.jar, + * and reasonable min and max values for the current time, + * to be used as sanity checks. + * + * Idea taken from Chrome, which assumes any clock more than + * 2 days before or 1 year after the build date is bad. + * + * Not maintained as a public API, not for use by plugins or applications. + * + * @since 0.9.25 modded from FileDumpHelper + */ +public class BuildTime { + + private static final long _buildTime; + private static final long _earliestTime; + private static final long _latestTime; + private static final long YEARS_25 = 25L*365*24*60*60*1000; + /** update this periodically */ + private static final String EARLIEST = "2016-02-19 12:00:00 UTC"; + + static { + // this is the standard format of build.timestamp as set in the top-level build.xml + SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US); + TimeZone utc = TimeZone.getTimeZone("GMT"); + fmt.setTimeZone(utc); + long min; + try { + Date date = fmt.parse(EARLIEST); + if (date == null) + throw new RuntimeException("BuildTime FAIL"); + min = date.getTime(); + } catch (ParseException pe) { + System.out.println("BuildTime FAIL"); + pe.printStackTrace(); + throw new RuntimeException("BuildTime FAIL", pe); + } + long max = min + YEARS_25; + long build = getBuildTime(fmt, "i2p.jar"); + if (build > max) { + System.out.println("Warning: Strange build time, contact packager: " + new Date(build)); + build = max; + } else if (build < min) { + if (build > 0) + System.out.println("Warning: Strange build time, contact packager: " + new Date(build)); + build = min; + } else { + // build time looks reasonable + // allow 24h skew on build machine + min = build - 24*60*60*1000L; + } + _earliestTime = min; + _latestTime = max; + _buildTime = build; + } + + /** + * Get the build date for i2p.jar. + * + * @return the earliest possible time if actual build date is unknown + */ + public static long getBuildTime() { + return _buildTime; + } + + /** + * Get the earliest it could possibly be right now. + * Latest of the build time minus a day, or a hardcoded time. + * + * @return the time + */ + public static long getEarliestTime() { + return _earliestTime; + } + + /** + * Get the latest it could possibly be right now. + * Hardcoded. + * + * @return the time + */ + public static long getLatestTime() { + return _latestTime; + } + + private BuildTime() {} + + /** + * Won't be available on Android or on any builds not using our build.xml. + * + * @return 0 if unknown + */ + private static long getBuildTime(SimpleDateFormat fmt, String jar) { + if (SystemVersion.isAndroid()) + return 0; + File f = new File(I2PAppContext.getGlobalContext().getBaseDir(), "lib"); + f = new File(f, jar); + Attributes atts = attributes(f); + if (atts == null) + return 0; + String s = atts.getValue("Build-Date"); + if (s == null) + return 0; + try { + Date date = fmt.parse(s); + if (date != null) { + return date.getTime(); + } + } catch (ParseException pe) {} + return 0; + } + + private static Attributes attributes(File f) { + InputStream in = null; + try { + in = (new URL("jar:file:" + f.getAbsolutePath() + "!/META-INF/MANIFEST.MF")).openStream(); + Manifest man = new Manifest(in); + return man.getMainAttributes(); + } catch (IOException ioe) { + return null; + } finally { + if (in != null) try { in.close(); } catch (IOException e) {} + } + } + +/**** + public static void main(String[] args) { + long date = getEarliestTime(); + System.out.println("Earliest date: " + new Date(date)); + date = getBuildTime(); + System.out.println("Build date: " + new Date(date)); + date = System.currentTimeMillis(); + System.out.println("System time: " + new Date(date)); + date = I2PAppContext.getGlobalContext().clock().now(); + System.out.println("I2P time: " + new Date(date)); + date = getLatestTime(); + System.out.println("Latest date: " + new Date(date)); + } +****/ +} diff --git a/core/java/src/net/i2p/util/Clock.java b/core/java/src/net/i2p/util/Clock.java index 521facd2d..537f4685d 100644 --- a/core/java/src/net/i2p/util/Clock.java +++ b/core/java/src/net/i2p/util/Clock.java @@ -1,9 +1,11 @@ package net.i2p.util; +import java.util.Date; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import net.i2p.I2PAppContext; +import net.i2p.time.BuildTime; import net.i2p.time.Timestamper; /** @@ -19,6 +21,7 @@ import net.i2p.time.Timestamper; */ public class Clock implements Timestamper.UpdateListener { protected final I2PAppContext _context; + protected final boolean _isSystemClockBad; protected long _startedOn; protected boolean _statCreated; protected volatile long _offset; @@ -28,7 +31,28 @@ public class Clock implements Timestamper.UpdateListener { public Clock(I2PAppContext context) { _context = context; _listeners = new CopyOnWriteArraySet(); - _startedOn = System.currentTimeMillis(); + long now = System.currentTimeMillis(); + long min = BuildTime.getEarliestTime(); + long max = BuildTime.getLatestTime(); + // If the system clock is obviously bad, set our offset so our time is something "close" + // We do not call setOffset() here as it sets _alreadyChanged. + // Don't use Log here. + if (now < min) { + // positive offset + _offset = min - now; + System.out.println("ERROR: System clock is invalid: " + new Date(now)); + now = min; + _isSystemClockBad = true; + } else if (now > max) { + // negative offset + _offset = max - now; + System.out.println("ERROR: System clock is invalid: " + new Date(now)); + now = max; + _isSystemClockBad = true; + } else { + _isSystemClockBad = false; + } + _startedOn = now; } public static Clock getInstance() { @@ -63,6 +87,7 @@ public class Clock implements Timestamper.UpdateListener { /** * Specify how far away from the "correct" time the computer is - a positive * value means that the system time is slow, while a negative value means the system time is fast. + * * Warning - overridden in RouterClock * * @param offsetMs the delta from System.currentTimeMillis() (NOT the delta from now()) @@ -70,7 +95,7 @@ public class Clock implements Timestamper.UpdateListener { public synchronized void setOffset(long offsetMs, boolean force) { long delta = offsetMs - _offset; if (!force) { - if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) { + if (!_isSystemClockBad && (offsetMs > MAX_OFFSET || offsetMs < 0 - MAX_OFFSET)) { Log log = getLog(); if (log.shouldLog(Log.WARN)) log.warn("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT"); @@ -128,17 +153,27 @@ public class Clock implements Timestamper.UpdateListener { public void setNow(long realTime) { + if (realTime < BuildTime.getEarliestTime() || realTime > BuildTime.getLatestTime()) { + Log log = getLog(); + String msg = "Invalid time received: " + new Date(realTime); + if (log.shouldWarn()) + log.warn(msg, new Exception()); + else + log.logAlways(Log.WARN, msg); + return; + } long diff = realTime - System.currentTimeMillis(); setOffset(diff); } /** + * Warning - overridden in RouterClock + * * @param stratum ignored * @since 0.7.12 */ public void setNow(long realTime, int stratum) { - long diff = realTime - System.currentTimeMillis(); - setOffset(diff); + setNow(realTime); } /** diff --git a/history.txt b/history.txt index 6a4160883..c8bb053ab 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,17 @@ +2016-02-19 zzz + * Clock: Add sanity checks to detect invalid system clock + +2016-02-18 zzz + * Console: Clean up display and form handling + for specifying a fixed host name or IP on /confignet + * Crypto: Add utilities for loading CRLs from disk; + check for revocation when reading in certificates + * Transport: + - Implement mayDisconnect() for outbound connections also, + use when publishing RI directly to floodfill + - Run SSU idle disconnect check faster if floodfill or near connection limit + * NetDB: Fix bug publishing router info too often + 2016-02-17 zzz * i2psnark: Increase max files per torrent to 2000 * I2PTunnel: Improve layout of blacklist radio buttons diff --git a/router/java/src/net/i2p/router/RouterClock.java b/router/java/src/net/i2p/router/RouterClock.java index 68a45ac61..b22d6b55f 100644 --- a/router/java/src/net/i2p/router/RouterClock.java +++ b/router/java/src/net/i2p/router/RouterClock.java @@ -1,10 +1,12 @@ package net.i2p.router; +import java.util.Date; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import net.i2p.data.DataHelper; import net.i2p.router.time.RouterTimestamper; +import net.i2p.time.BuildTime; import net.i2p.time.Timestamper; import net.i2p.util.Clock; import net.i2p.util.Log; @@ -108,7 +110,7 @@ public class RouterClock extends Clock { private synchronized void setOffset(long offsetMs, boolean force, int stratum) { long delta = offsetMs - _offset; if (!force) { - if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) { + if (!_isSystemClockBad && (offsetMs > MAX_OFFSET || offsetMs < 0 - MAX_OFFSET)) { Log log = getLog(); if (log.shouldLog(Log.WARN)) log.warn("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT"); @@ -218,6 +220,15 @@ public class RouterClock extends Clock { */ @Override public void setNow(long realTime, int stratum) { + if (realTime < BuildTime.getEarliestTime() || realTime > BuildTime.getLatestTime()) { + Log log = getLog(); + String msg = "Invalid time received: " + new Date(realTime); + if (log.shouldWarn()) + log.warn(msg, new Exception()); + else + log.logAlways(Log.WARN, msg); + return; + } long diff = realTime - System.currentTimeMillis(); setOffset(diff, stratum); } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c10128fe6..fecba78d6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; /** for example "-test" */ public final static String EXTRA = "";