forked from I2P_Developers/i2p.i2p
* Timestamper:
- Move from core to router, leave stub in core so it doesn't break compatibility. This removes a thread in app context and prevents any app context from running NTP; external clients must use the time received from the router. - Increase query interval
This commit is contained in:
@ -61,7 +61,6 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
|
||||
public void setSave(String moo) { _saveRequested = true; }
|
||||
public void setEnabletimesync(String moo) { }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
||||
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
||||
@ -289,10 +288,6 @@ public class ConfigNetHandler extends FormHandler {
|
||||
removes.add(UDPTransport.PROP_FORCE_INTRODUCERS);
|
||||
}
|
||||
|
||||
// Time sync enable, means NOT disabled
|
||||
// Hmm router sets this at startup, not required here
|
||||
//changes.put(Timestamper.PROP_DISABLED, "false");
|
||||
|
||||
// Hidden in the GUI
|
||||
//LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting);
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import net.i2p.router.Router;
|
||||
import net.i2p.router.transport.TransportManager;
|
||||
import net.i2p.router.transport.udp.UDPAddress;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.util.Addresses;
|
||||
|
||||
public class ConfigNetHelper extends HelperBase {
|
||||
@ -65,14 +64,6 @@ public class ConfigNetHelper extends HelperBase {
|
||||
return "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, UDPTransport.DEFAULT_INTERNAL_PORT);
|
||||
}
|
||||
|
||||
public String getEnableTimeSyncChecked() {
|
||||
boolean disabled = _context.getBooleanProperty(Timestamper.PROP_DISABLED);
|
||||
if (disabled)
|
||||
return "";
|
||||
else
|
||||
return CHECKED;
|
||||
}
|
||||
|
||||
/** @param prop must default to false */
|
||||
public String getChecked(String prop) {
|
||||
if (_context.getBooleanProperty(prop))
|
||||
|
@ -1,336 +1,31 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically query a series of NTP servers and update any associated
|
||||
* listeners. It tries the NTP servers in order, contacting them using
|
||||
* SNTP (UDP port 123). By default, it does this every 5 minutes,
|
||||
* forever.
|
||||
* Dummy. Real thing moved to net.i2p.router.time.RouterTimestamper.
|
||||
* What remains here is essentially an interface,
|
||||
* containing only what is needed to keep external apps
|
||||
* compiled with old libs from breaking, since
|
||||
* net.i2p.util.Clock returns a Timestamper in getTimestamper()
|
||||
*
|
||||
* Deprecated outside of the router.
|
||||
*/
|
||||
public class Timestamper implements Runnable {
|
||||
private final I2PAppContext _context;
|
||||
private Log _log;
|
||||
private final List<String> _servers;
|
||||
private List<String> _priorityServers;
|
||||
private final List<UpdateListener> _listeners;
|
||||
private int _queryFrequency;
|
||||
private int _concurringServers;
|
||||
private int _consecutiveFails;
|
||||
private volatile boolean _disabled;
|
||||
private final boolean _daemon;
|
||||
private boolean _initialized;
|
||||
private boolean _wellSynced;
|
||||
private volatile boolean _isRunning;
|
||||
private Thread _timestamperThread;
|
||||
|
||||
private static final int MIN_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org";
|
||||
private static final String DEFAULT_DISABLED = "true";
|
||||
/** how many times do we have to query if we are changing the clock? */
|
||||
private static final int DEFAULT_CONCURRING_SERVERS = 3;
|
||||
private static final int MAX_CONSECUTIVE_FAILS = 10;
|
||||
/** dummy */
|
||||
public Timestamper() {}
|
||||
|
||||
public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs";
|
||||
public static final String PROP_SERVER_LIST = "time.sntpServerList";
|
||||
public static final String PROP_DISABLED = "time.disabled";
|
||||
public static final String PROP_CONCURRING_SERVERS = "time.concurringServers";
|
||||
public static final String PROP_IP_COUNTRY = "i2np.lastCountry";
|
||||
|
||||
/** if different SNTP servers differ by more than 10s, someone is b0rked */
|
||||
private static final int MAX_VARIANCE = 10*1000;
|
||||
|
||||
public Timestamper(I2PAppContext ctx) {
|
||||
this(ctx, null, true);
|
||||
}
|
||||
|
||||
public Timestamper(I2PAppContext ctx, UpdateListener lsnr) {
|
||||
this(ctx, lsnr, true);
|
||||
}
|
||||
public Timestamper(I2PAppContext ctx, UpdateListener lsnr, boolean daemon) {
|
||||
// moved here to prevent problems with synchronized statements.
|
||||
_servers = new ArrayList(3);
|
||||
_listeners = new CopyOnWriteArrayList();
|
||||
_context = ctx;
|
||||
_daemon = daemon;
|
||||
// DO NOT initialize _log here, stack overflow via LogManager init loop
|
||||
|
||||
// Don't bother starting a thread if we are disabled.
|
||||
// This means we no longer check every 5 minutes to see if we got enabled,
|
||||
// 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()) {
|
||||
_initialized = true;
|
||||
return;
|
||||
}
|
||||
if (lsnr != null)
|
||||
_listeners.add(lsnr);
|
||||
updateConfig();
|
||||
startTimestamper();
|
||||
}
|
||||
|
||||
public int getServerCount() {
|
||||
synchronized (_servers) {
|
||||
return _servers.size();
|
||||
}
|
||||
}
|
||||
public String getServer(int index) {
|
||||
synchronized (_servers) {
|
||||
return _servers.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
public int getQueryFrequencyMs() { return _queryFrequency; }
|
||||
|
||||
public boolean getIsDisabled() { return _disabled; }
|
||||
|
||||
public void addListener(UpdateListener lsnr) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
public void removeListener(UpdateListener lsnr) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
public int getListenerCount() {
|
||||
return _listeners.size();
|
||||
}
|
||||
public UpdateListener getListener(int index) {
|
||||
return _listeners.get(index);
|
||||
}
|
||||
|
||||
private void startTimestamper() {
|
||||
_timestamperThread = new I2PThread(this, "Timestamper", _daemon);
|
||||
_timestamperThread.setPriority(I2PThread.MIN_PRIORITY);
|
||||
_isRunning = true;
|
||||
_timestamperThread.start();
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
|
||||
public void waitForInitialization() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
if (!_initialized)
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
/** dummy */
|
||||
public void waitForInitialization() {}
|
||||
|
||||
/**
|
||||
* Update the time immediately.
|
||||
* Dummy
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public void timestampNow() {
|
||||
if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
public void timestampNow() {}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
_log = _context.logManager().getLog(Timestamper.class);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting timestamper");
|
||||
boolean lastFailed = false;
|
||||
try {
|
||||
while (_isRunning) {
|
||||
updateConfig();
|
||||
if (!_disabled) {
|
||||
// first the servers for our country, if we know what country we're in...
|
||||
if (_priorityServers != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _priorityServers);
|
||||
try {
|
||||
lastFailed = !queryTime(_priorityServers.toArray(new String[_priorityServers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!lastFailed) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Unable to reach country-specific NTP servers");
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
// ... and then the global list, if that failed
|
||||
if (_priorityServers == null || lastFailed) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _servers);
|
||||
try {
|
||||
lastFailed = !queryTime(_servers.toArray(new String[_servers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!_initialized) && (_log.shouldLog(Log.ERROR)) ) {
|
||||
List<String> all = new ArrayList();
|
||||
if (_priorityServers != null)
|
||||
all.addAll(_priorityServers);
|
||||
all.addAll(_servers);
|
||||
_log.error("Unable to reach any of the NTP servers " + all + " - network disconnected? Or set time.sntpServerList=myserver1.com,myserver2.com in advanced configuration.");
|
||||
}
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
synchronized (this) { notifyAll(); }
|
||||
long sleepTime;
|
||||
if (lastFailed) {
|
||||
if (++_consecutiveFails >= MAX_CONSECUTIVE_FAILS)
|
||||
sleepTime = 30*60*1000;
|
||||
else
|
||||
sleepTime = 30*1000;
|
||||
} else {
|
||||
_consecutiveFails = 0;
|
||||
sleepTime = _context.random().nextInt(_queryFrequency) + _queryFrequency;
|
||||
if (_wellSynced)
|
||||
sleepTime *= 3;
|
||||
}
|
||||
try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
synchronized (this) { notifyAll(); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the time was queried successfully, false if it couldn't be
|
||||
*/
|
||||
private boolean queryTime(String serverList[]) throws IllegalArgumentException {
|
||||
long found[] = new long[_concurringServers];
|
||||
long now = -1;
|
||||
int stratum = -1;
|
||||
long expectedDelta = 0;
|
||||
_wellSynced = false;
|
||||
for (int i = 0; i < _concurringServers; i++) {
|
||||
if (i > 0) {
|
||||
// this delays startup when net is disconnected or the timeserver list is bad, don't make it too long
|
||||
try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList);
|
||||
now = timeAndStratum[0];
|
||||
stratum = (int) timeAndStratum[1];
|
||||
long delta = now - _context.clock().now();
|
||||
found[i] = delta;
|
||||
if (i == 0) {
|
||||
if (Math.abs(delta) < MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("a single SNTP query was within the tolerance (" + delta + "ms)");
|
||||
// If less than a half second on the first try, we're in good shape
|
||||
_wellSynced = Math.abs(delta) < 500;
|
||||
break;
|
||||
} else {
|
||||
// outside the tolerance, lets iterate across the concurring queries
|
||||
expectedDelta = delta;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(delta - expectedDelta) > MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
StringBuilder err = new StringBuilder(96);
|
||||
err.append("SNTP client variance exceeded at query ").append(i);
|
||||
err.append(". expected = ");
|
||||
err.append(expectedDelta);
|
||||
err.append(", found = ");
|
||||
err.append(delta);
|
||||
err.append(" all deltas: ");
|
||||
for (int j = 0; j < found.length; j++)
|
||||
err.append(found[j]).append(' ');
|
||||
_log.error(err.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
stampTime(now, stratum);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("Deltas: ");
|
||||
for (int i = 0; i < found.length; i++)
|
||||
buf.append(found[i]).append(' ');
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners
|
||||
*
|
||||
* @since stratum param added in 0.7.12
|
||||
*/
|
||||
private void stampTime(long now, int stratum) {
|
||||
long before = _context.clock().now();
|
||||
for (UpdateListener lsnr : _listeners) {
|
||||
lsnr.setNow(now, stratum);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamped the time as " + now + " (delta=" + (now-before) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload all the config elements from the appContext
|
||||
*
|
||||
*/
|
||||
private void updateConfig() {
|
||||
String serverList = _context.getProperty(PROP_SERVER_LIST);
|
||||
if ( (serverList == null) || (serverList.trim().length() <= 0) ) {
|
||||
serverList = DEFAULT_SERVER_LIST;
|
||||
String country = _context.getProperty(PROP_IP_COUNTRY);
|
||||
if (country == null) {
|
||||
country = Locale.getDefault().getCountry();
|
||||
if (country != null)
|
||||
country = country.toLowerCase(Locale.US);
|
||||
}
|
||||
if (country != null && country.length() > 0) {
|
||||
_priorityServers = new ArrayList(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
_priorityServers.add(i + "." + country + ".pool.ntp.org");
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
_servers.clear();
|
||||
StringTokenizer tok = new StringTokenizer(serverList, ", ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = tok.nextToken();
|
||||
val = val.trim();
|
||||
if (val.length() > 0)
|
||||
_servers.add(val);
|
||||
}
|
||||
|
||||
_queryFrequency = Math.max(MIN_QUERY_FREQUENCY,
|
||||
_context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY));
|
||||
|
||||
String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
|
||||
_disabled = Boolean.valueOf(disabled).booleanValue();
|
||||
|
||||
_concurringServers = Math.min(4, Math.max(1,
|
||||
_context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS)));
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
System.setProperty(PROP_DISABLED, "false");
|
||||
System.setProperty(PROP_QUERY_FREQUENCY, "30000");
|
||||
I2PAppContext.getGlobalContext();
|
||||
for (int i = 0; i < 5*60*1000; i += 61*1000) {
|
||||
try { Thread.sleep(61*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
****/
|
||||
/** dummy */
|
||||
public void run() {}
|
||||
|
||||
/**
|
||||
* Interface to receive update notifications for when we query the time
|
||||
|
@ -1,7 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Provides classes for time synchronisation using NTP.
|
||||
Provides a stub class for time synchronization.
|
||||
Full implementation is now in net.i2p.router.time.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -19,7 +19,6 @@ import net.i2p.time.Timestamper;
|
||||
*/
|
||||
public class Clock implements Timestamper.UpdateListener {
|
||||
protected final I2PAppContext _context;
|
||||
private final Timestamper _timestamper;
|
||||
protected long _startedOn;
|
||||
protected boolean _statCreated;
|
||||
protected volatile long _offset;
|
||||
@ -29,7 +28,6 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
_listeners = new CopyOnWriteArraySet();
|
||||
_timestamper = new Timestamper(context, this);
|
||||
_startedOn = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@ -37,7 +35,10 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
return I2PAppContext.getGlobalContext().clock();
|
||||
}
|
||||
|
||||
public Timestamper getTimestamper() { return _timestamper; }
|
||||
/**
|
||||
* This is a dummy, see RouterClock and RouterTimestamper for the real thing
|
||||
*/
|
||||
public Timestamper getTimestamper() { return new Timestamper(); }
|
||||
|
||||
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
|
19
history.txt
19
history.txt
@ -1,6 +1,21 @@
|
||||
2012-05-30 zzz
|
||||
* Graphs: Reduce log EOF error to warn
|
||||
* i2psnark:
|
||||
- Increase max upload (ticket #645)
|
||||
- Increase per-minute conn limit from 6 to 8
|
||||
- Improve rarest-first behavior
|
||||
- Handle URI encoding, UTF-8, and multiple trackers in magnet links
|
||||
* Timestamper:
|
||||
- Move from core to router, leave stub in core
|
||||
so it doesn't break compatibility. This removes a
|
||||
thread in app context and prevents any app context from
|
||||
running NTP; external clients must use the time
|
||||
received from the router.
|
||||
- Increase query interval
|
||||
|
||||
2012-05-28 kytv
|
||||
* i2prouter: Add support so that 'i2prouter install' will work in ArchLinux
|
||||
* jbigi/jcpuid scripts: Improve support for ArchLinux
|
||||
* i2prouter: Add support so that 'i2prouter install' will work in ArchLinux
|
||||
* jbigi/jcpuid scripts: Improve support for ArchLinux
|
||||
|
||||
2012-05-25 kytv
|
||||
* German, Italian, Spanish, and Swedish translation updates from Transifex
|
||||
|
@ -4,6 +4,8 @@ import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.time.RouterTimestamper;
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -38,6 +40,7 @@ public class RouterClock extends Clock {
|
||||
/** use system time for this */
|
||||
private long _lastChanged;
|
||||
private int _lastStratum;
|
||||
private final Timestamper _timeStamper;
|
||||
|
||||
/**
|
||||
* If the system clock shifts by this much,
|
||||
@ -56,8 +59,15 @@ public class RouterClock extends Clock {
|
||||
_lastSlewed = System.currentTimeMillis();
|
||||
_shiftListeners = new CopyOnWriteArraySet();
|
||||
_lastShiftNanos = System.nanoTime();
|
||||
_timeStamper = new RouterTimestamper(context, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The RouterTimestamper
|
||||
*/
|
||||
@Override
|
||||
public Timestamper getTimestamper() { return _timeStamper; }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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 = "";
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.i2p.time;
|
||||
package net.i2p.router.time;
|
||||
/*
|
||||
* Copyright (c) 2004, Adam Buckley
|
||||
* All rights reserved.
|
||||
@ -49,8 +49,9 @@ import java.util.Collections;
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* (minor refactoring by jrandom)
|
||||
* @since 0.9.1 moved from net.i2p.time
|
||||
*/
|
||||
public class NtpClient {
|
||||
class NtpClient {
|
||||
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
|
||||
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
private final static int NTP_PORT = 123;
|
@ -1,4 +1,4 @@
|
||||
package net.i2p.time;
|
||||
package net.i2p.router.time;
|
||||
/*
|
||||
* Copyright (c) 2004, Adam Buckley
|
||||
* All rights reserved.
|
||||
@ -72,8 +72,9 @@ import net.i2p.util.RandomSource;
|
||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* @since 0.9.1 moved from net.i2p.time
|
||||
*/
|
||||
public class NtpMessage {
|
||||
class NtpMessage {
|
||||
/**
|
||||
* This is a two-bit code warning of an impending leap second to be
|
||||
* inserted/deleted in the last minute of the current day. It's values
|
340
router/java/src/net/i2p/router/time/RouterTimestamper.java
Normal file
340
router/java/src/net/i2p/router/time/RouterTimestamper.java
Normal file
@ -0,0 +1,340 @@
|
||||
package net.i2p.router.time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically query a series of NTP servers and update any associated
|
||||
* listeners. It tries the NTP servers in order, contacting them using
|
||||
* SNTP (UDP port 123).
|
||||
*
|
||||
* @since 0.9.1 moved from net.i2p.time
|
||||
*/
|
||||
public class RouterTimestamper extends Timestamper {
|
||||
private final I2PAppContext _context;
|
||||
private Log _log;
|
||||
private final List<String> _servers;
|
||||
private List<String> _priorityServers;
|
||||
private final List<UpdateListener> _listeners;
|
||||
private int _queryFrequency;
|
||||
private int _concurringServers;
|
||||
private int _consecutiveFails;
|
||||
private volatile boolean _disabled;
|
||||
private final boolean _daemon;
|
||||
private boolean _initialized;
|
||||
private boolean _wellSynced;
|
||||
private volatile boolean _isRunning;
|
||||
private Thread _timestamperThread;
|
||||
|
||||
private static final int MIN_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final int DEFAULT_QUERY_FREQUENCY = 11*60*1000;
|
||||
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org";
|
||||
private static final String DEFAULT_DISABLED = "true";
|
||||
/** how many times do we have to query if we are changing the clock? */
|
||||
private static final int DEFAULT_CONCURRING_SERVERS = 3;
|
||||
private static final int MAX_CONSECUTIVE_FAILS = 10;
|
||||
|
||||
public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs";
|
||||
public static final String PROP_SERVER_LIST = "time.sntpServerList";
|
||||
public static final String PROP_DISABLED = "time.disabled";
|
||||
public static final String PROP_CONCURRING_SERVERS = "time.concurringServers";
|
||||
public static final String PROP_IP_COUNTRY = "i2np.lastCountry";
|
||||
|
||||
/** if different SNTP servers differ by more than 10s, someone is b0rked */
|
||||
private static final int MAX_VARIANCE = 10*1000;
|
||||
|
||||
public RouterTimestamper(I2PAppContext ctx) {
|
||||
this(ctx, null, true);
|
||||
}
|
||||
|
||||
public RouterTimestamper(I2PAppContext ctx, UpdateListener lsnr) {
|
||||
this(ctx, lsnr, true);
|
||||
}
|
||||
public RouterTimestamper(I2PAppContext ctx, UpdateListener lsnr, boolean daemon) {
|
||||
super();
|
||||
// moved here to prevent problems with synchronized statements.
|
||||
_servers = new ArrayList(3);
|
||||
_listeners = new CopyOnWriteArrayList();
|
||||
_context = ctx;
|
||||
_daemon = daemon;
|
||||
// DO NOT initialize _log here, stack overflow via LogManager init loop
|
||||
|
||||
// Don't bother starting a thread if we are disabled.
|
||||
// This means we no longer check every 5 minutes to see if we got enabled,
|
||||
// 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()) {
|
||||
_initialized = true;
|
||||
return;
|
||||
}
|
||||
if (lsnr != null)
|
||||
_listeners.add(lsnr);
|
||||
updateConfig();
|
||||
startTimestamper();
|
||||
}
|
||||
|
||||
public int getServerCount() {
|
||||
synchronized (_servers) {
|
||||
return _servers.size();
|
||||
}
|
||||
}
|
||||
public String getServer(int index) {
|
||||
synchronized (_servers) {
|
||||
return _servers.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
public int getQueryFrequencyMs() { return _queryFrequency; }
|
||||
|
||||
public boolean getIsDisabled() { return _disabled; }
|
||||
|
||||
public void addListener(UpdateListener lsnr) {
|
||||
_listeners.add(lsnr);
|
||||
}
|
||||
public void removeListener(UpdateListener lsnr) {
|
||||
_listeners.remove(lsnr);
|
||||
}
|
||||
public int getListenerCount() {
|
||||
return _listeners.size();
|
||||
}
|
||||
public UpdateListener getListener(int index) {
|
||||
return _listeners.get(index);
|
||||
}
|
||||
|
||||
private void startTimestamper() {
|
||||
_timestamperThread = new I2PThread(this, "Timestamper", _daemon);
|
||||
_timestamperThread.setPriority(I2PThread.MIN_PRIORITY);
|
||||
_isRunning = true;
|
||||
_timestamperThread.start();
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForInitialization() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
if (!_initialized)
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the time immediately.
|
||||
* @since 0.8.8
|
||||
*/
|
||||
@Override
|
||||
public void timestampNow() {
|
||||
if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_timestamperThread != null)
|
||||
_timestamperThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
_log = _context.logManager().getLog(Timestamper.class);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting timestamper");
|
||||
boolean lastFailed = false;
|
||||
try {
|
||||
while (_isRunning) {
|
||||
updateConfig();
|
||||
if (!_disabled) {
|
||||
// first the servers for our country, if we know what country we're in...
|
||||
if (_priorityServers != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _priorityServers);
|
||||
try {
|
||||
lastFailed = !queryTime(_priorityServers.toArray(new String[_priorityServers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!lastFailed) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Unable to reach country-specific NTP servers");
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
// ... and then the global list, if that failed
|
||||
if (_priorityServers == null || lastFailed) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _servers);
|
||||
try {
|
||||
lastFailed = !queryTime(_servers.toArray(new String[_servers.size()]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if ( (!_initialized) && (_log.shouldLog(Log.ERROR)) ) {
|
||||
List<String> all = new ArrayList();
|
||||
if (_priorityServers != null)
|
||||
all.addAll(_priorityServers);
|
||||
all.addAll(_servers);
|
||||
_log.error("Unable to reach any of the NTP servers " + all + " - network disconnected? Or set time.sntpServerList=myserver1.com,myserver2.com in advanced configuration.");
|
||||
}
|
||||
lastFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
synchronized (this) { notifyAll(); }
|
||||
long sleepTime;
|
||||
if (lastFailed) {
|
||||
if (++_consecutiveFails >= MAX_CONSECUTIVE_FAILS)
|
||||
sleepTime = 30*60*1000;
|
||||
else
|
||||
sleepTime = 30*1000;
|
||||
} else {
|
||||
_consecutiveFails = 0;
|
||||
sleepTime = _context.random().nextInt(_queryFrequency / 2) + _queryFrequency;
|
||||
if (_wellSynced)
|
||||
sleepTime *= 3;
|
||||
}
|
||||
try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
synchronized (this) { notifyAll(); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the time was queried successfully, false if it couldn't be
|
||||
*/
|
||||
private boolean queryTime(String serverList[]) throws IllegalArgumentException {
|
||||
long found[] = new long[_concurringServers];
|
||||
long now = -1;
|
||||
int stratum = -1;
|
||||
long expectedDelta = 0;
|
||||
_wellSynced = false;
|
||||
for (int i = 0; i < _concurringServers; i++) {
|
||||
if (i > 0) {
|
||||
// this delays startup when net is disconnected or the timeserver list is bad, don't make it too long
|
||||
try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList);
|
||||
now = timeAndStratum[0];
|
||||
stratum = (int) timeAndStratum[1];
|
||||
long delta = now - _context.clock().now();
|
||||
found[i] = delta;
|
||||
if (i == 0) {
|
||||
if (Math.abs(delta) < MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("a single SNTP query was within the tolerance (" + delta + "ms)");
|
||||
// If less than a half second on the first try, we're in good shape
|
||||
_wellSynced = Math.abs(delta) < 500;
|
||||
break;
|
||||
} else {
|
||||
// outside the tolerance, lets iterate across the concurring queries
|
||||
expectedDelta = delta;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(delta - expectedDelta) > MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
StringBuilder err = new StringBuilder(96);
|
||||
err.append("SNTP client variance exceeded at query ").append(i);
|
||||
err.append(". expected = ");
|
||||
err.append(expectedDelta);
|
||||
err.append(", found = ");
|
||||
err.append(delta);
|
||||
err.append(" all deltas: ");
|
||||
for (int j = 0; j < found.length; j++)
|
||||
err.append(found[j]).append(' ');
|
||||
_log.error(err.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
stampTime(now, stratum);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("Deltas: ");
|
||||
for (int i = 0; i < found.length; i++)
|
||||
buf.append(found[i]).append(' ');
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners
|
||||
*
|
||||
* @since stratum param added in 0.7.12
|
||||
*/
|
||||
private void stampTime(long now, int stratum) {
|
||||
long before = _context.clock().now();
|
||||
for (UpdateListener lsnr : _listeners) {
|
||||
lsnr.setNow(now, stratum);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamped the time as " + now + " (delta=" + (now-before) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload all the config elements from the appContext
|
||||
*
|
||||
*/
|
||||
private void updateConfig() {
|
||||
String serverList = _context.getProperty(PROP_SERVER_LIST);
|
||||
if ( (serverList == null) || (serverList.trim().length() <= 0) ) {
|
||||
serverList = DEFAULT_SERVER_LIST;
|
||||
String country = _context.getProperty(PROP_IP_COUNTRY);
|
||||
if (country == null) {
|
||||
country = Locale.getDefault().getCountry();
|
||||
if (country != null)
|
||||
country = country.toLowerCase(Locale.US);
|
||||
}
|
||||
if (country != null && country.length() > 0) {
|
||||
_priorityServers = new ArrayList(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
_priorityServers.add(i + "." + country + ".pool.ntp.org");
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
} else {
|
||||
_priorityServers = null;
|
||||
}
|
||||
_servers.clear();
|
||||
StringTokenizer tok = new StringTokenizer(serverList, ", ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = tok.nextToken();
|
||||
val = val.trim();
|
||||
if (val.length() > 0)
|
||||
_servers.add(val);
|
||||
}
|
||||
|
||||
_queryFrequency = Math.max(MIN_QUERY_FREQUENCY,
|
||||
_context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY));
|
||||
|
||||
String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED);
|
||||
_disabled = Boolean.valueOf(disabled).booleanValue();
|
||||
|
||||
_concurringServers = Math.min(4, Math.max(1,
|
||||
_context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS)));
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
System.setProperty(PROP_DISABLED, "false");
|
||||
System.setProperty(PROP_QUERY_FREQUENCY, "30000");
|
||||
I2PAppContext.getGlobalContext();
|
||||
for (int i = 0; i < 5*60*1000; i += 61*1000) {
|
||||
try { Thread.sleep(61*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
7
router/java/src/net/i2p/router/time/package.html
Normal file
7
router/java/src/net/i2p/router/time/package.html
Normal file
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Provides classes for time synchronization using NTP.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user