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 setSave(String moo) { _saveRequested = true; }
|
||||||
public void setEnabletimesync(String moo) { }
|
|
||||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||||
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
||||||
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
||||||
@ -289,10 +288,6 @@ public class ConfigNetHandler extends FormHandler {
|
|||||||
removes.add(UDPTransport.PROP_FORCE_INTRODUCERS);
|
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
|
// Hidden in the GUI
|
||||||
//LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting);
|
//LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import net.i2p.router.Router;
|
|||||||
import net.i2p.router.transport.TransportManager;
|
import net.i2p.router.transport.TransportManager;
|
||||||
import net.i2p.router.transport.udp.UDPAddress;
|
import net.i2p.router.transport.udp.UDPAddress;
|
||||||
import net.i2p.router.transport.udp.UDPTransport;
|
import net.i2p.router.transport.udp.UDPTransport;
|
||||||
import net.i2p.time.Timestamper;
|
|
||||||
import net.i2p.util.Addresses;
|
import net.i2p.util.Addresses;
|
||||||
|
|
||||||
public class ConfigNetHelper extends HelperBase {
|
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);
|
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 */
|
/** @param prop must default to false */
|
||||||
public String getChecked(String prop) {
|
public String getChecked(String prop) {
|
||||||
if (_context.getBooleanProperty(prop))
|
if (_context.getBooleanProperty(prop))
|
||||||
|
@ -1,336 +1,31 @@
|
|||||||
package net.i2p.time;
|
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
|
* Dummy. Real thing moved to net.i2p.router.time.RouterTimestamper.
|
||||||
* listeners. It tries the NTP servers in order, contacting them using
|
* What remains here is essentially an interface,
|
||||||
* SNTP (UDP port 123). By default, it does this every 5 minutes,
|
* containing only what is needed to keep external apps
|
||||||
* forever.
|
* 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 {
|
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;
|
/** dummy */
|
||||||
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
|
public Timestamper() {}
|
||||||
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";
|
/** dummy */
|
||||||
public static final String PROP_SERVER_LIST = "time.sntpServerList";
|
public void waitForInitialization() {}
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the time immediately.
|
* Update the time immediately.
|
||||||
|
* Dummy
|
||||||
* @since 0.8.8
|
* @since 0.8.8
|
||||||
*/
|
*/
|
||||||
public void timestampNow() {
|
public void timestampNow() {}
|
||||||
if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null)
|
|
||||||
_timestamperThread.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @since 0.8.8 */
|
/** dummy */
|
||||||
private class Shutdown implements Runnable {
|
public void run() {}
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
****/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to receive update notifications for when we query the time
|
* Interface to receive update notifications for when we query the time
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -19,7 +19,6 @@ import net.i2p.time.Timestamper;
|
|||||||
*/
|
*/
|
||||||
public class Clock implements Timestamper.UpdateListener {
|
public class Clock implements Timestamper.UpdateListener {
|
||||||
protected final I2PAppContext _context;
|
protected final I2PAppContext _context;
|
||||||
private final Timestamper _timestamper;
|
|
||||||
protected long _startedOn;
|
protected long _startedOn;
|
||||||
protected boolean _statCreated;
|
protected boolean _statCreated;
|
||||||
protected volatile long _offset;
|
protected volatile long _offset;
|
||||||
@ -29,7 +28,6 @@ public class Clock implements Timestamper.UpdateListener {
|
|||||||
public Clock(I2PAppContext context) {
|
public Clock(I2PAppContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_listeners = new CopyOnWriteArraySet();
|
_listeners = new CopyOnWriteArraySet();
|
||||||
_timestamper = new Timestamper(context, this);
|
|
||||||
_startedOn = System.currentTimeMillis();
|
_startedOn = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +35,10 @@ public class Clock implements Timestamper.UpdateListener {
|
|||||||
return I2PAppContext.getGlobalContext().clock();
|
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) */
|
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||||
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
|
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
|
2012-05-28 kytv
|
||||||
* i2prouter: Add support so that 'i2prouter install' will work in ArchLinux
|
* i2prouter: Add support so that 'i2prouter install' will work in ArchLinux
|
||||||
* jbigi/jcpuid scripts: Improve support for ArchLinux
|
* jbigi/jcpuid scripts: Improve support for ArchLinux
|
||||||
|
|
||||||
2012-05-25 kytv
|
2012-05-25 kytv
|
||||||
* German, Italian, Spanish, and Swedish translation updates from Transifex
|
* German, Italian, Spanish, and Swedish translation updates from Transifex
|
||||||
|
@ -4,6 +4,8 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
import net.i2p.data.DataHelper;
|
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.Clock;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ public class RouterClock extends Clock {
|
|||||||
/** use system time for this */
|
/** use system time for this */
|
||||||
private long _lastChanged;
|
private long _lastChanged;
|
||||||
private int _lastStratum;
|
private int _lastStratum;
|
||||||
|
private final Timestamper _timeStamper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the system clock shifts by this much,
|
* If the system clock shifts by this much,
|
||||||
@ -56,8 +59,15 @@ public class RouterClock extends Clock {
|
|||||||
_lastSlewed = System.currentTimeMillis();
|
_lastSlewed = System.currentTimeMillis();
|
||||||
_shiftListeners = new CopyOnWriteArraySet();
|
_shiftListeners = new CopyOnWriteArraySet();
|
||||||
_lastShiftNanos = System.nanoTime();
|
_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
|
* 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.
|
* 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 */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 6;
|
public final static long BUILD = 7;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.i2p.time;
|
package net.i2p.router.time;
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2004, Adam Buckley
|
* Copyright (c) 2004, Adam Buckley
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -49,8 +49,9 @@ import java.util.Collections;
|
|||||||
*
|
*
|
||||||
* @author Adam Buckley
|
* @author Adam Buckley
|
||||||
* (minor refactoring by jrandom)
|
* (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) */
|
/** 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 double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||||
private final static int NTP_PORT = 123;
|
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
|
* Copyright (c) 2004, Adam Buckley
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -72,8 +72,9 @@ import net.i2p.util.RandomSource;
|
|||||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||||
*
|
*
|
||||||
* @author Adam Buckley
|
* @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
|
* 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
|
* 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