forked from I2P_Developers/i2p.i2p
NTP:
- Verify source address and port - Add to command line - main() test improvements - Add KoD support (ticket #1896) - Add initial IPv6 support (ticket #1897) - Make some methods private - Add year 2036 warning
This commit is contained in:
@ -21,6 +21,7 @@ public class CommandLine extends net.i2p.util.CommandLine {
|
||||
"net.i2p.router.RouterVersion",
|
||||
"net.i2p.router.peermanager.ProfileOrganizer",
|
||||
"net.i2p.router.tasks.CryptoChecker",
|
||||
"net.i2p.router.time.NtpClient",
|
||||
"net.i2p.router.transport.GeoIPv6",
|
||||
"net.i2p.router.transport.udp.MTU",
|
||||
"net.i2p.router.transport.UPnP"
|
||||
|
@ -33,8 +33,13 @@ import java.io.InterruptedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.HexDump;
|
||||
@ -50,18 +55,24 @@ import net.i2p.util.Log;
|
||||
* Note that on windows platforms, the curent time-of-day timestamp is limited
|
||||
* to an resolution of 10ms and adversely affects the accuracy of the results.
|
||||
*
|
||||
* Public only for main(), not a public API, not for external use.
|
||||
*
|
||||
* TODO NOT 2036-compliant, see RFC 4330
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* (minor refactoring by jrandom)
|
||||
* @since 0.9.1 moved from net.i2p.time
|
||||
*/
|
||||
class NtpClient {
|
||||
public class NtpClient {
|
||||
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
|
||||
public final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
private final static int NTP_PORT = 123;
|
||||
private static final int DEFAULT_TIMEOUT = 10*1000;
|
||||
private static final int OFF_ORIGTIME = 24;
|
||||
private static final int OFF_TXTIME = 40;
|
||||
private static final int MIN_PKT_LEN = 48;
|
||||
// IP:reason for servers that sent us a kiss of death
|
||||
private static final Map<String, String> kisses = new ConcurrentHashMap<String, String>(2);
|
||||
|
||||
/**
|
||||
* Query the ntp servers, returning the current time from first one we find
|
||||
@ -95,7 +106,7 @@ class NtpClient {
|
||||
* @throws IllegalArgumentException if none of the servers are reachable
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout, Log log) {
|
||||
static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout, boolean preferIPv6, Log log) {
|
||||
if (serverNames == null)
|
||||
throw new IllegalArgumentException("No NTP servers specified");
|
||||
ArrayList<String> names = new ArrayList<String>(serverNames.length);
|
||||
@ -103,7 +114,7 @@ class NtpClient {
|
||||
names.add(serverNames[i]);
|
||||
Collections.shuffle(names);
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, log);
|
||||
long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, preferIPv6, log);
|
||||
if (rv != null && rv[0] > 0)
|
||||
return rv;
|
||||
}
|
||||
@ -131,16 +142,39 @@ class NtpClient {
|
||||
* @return time in rv[0] and stratum in rv[1], or null for error
|
||||
* @since 0.7.12
|
||||
*/
|
||||
private static long[] currentTimeAndStratum(String serverName, int timeout, Log log) {
|
||||
private static long[] currentTimeAndStratum(String serverName, int timeout, boolean preferIPv6, Log log) {
|
||||
DatagramSocket socket = null;
|
||||
try {
|
||||
// Send request
|
||||
socket = new DatagramSocket();
|
||||
InetAddress address = InetAddress.getByName(serverName);
|
||||
InetAddress address;
|
||||
if (preferIPv6) {
|
||||
InetAddress[] addrs = InetAddress.getAllByName(serverName);
|
||||
if (addrs == null || addrs.length == 0)
|
||||
throw new UnknownHostException();
|
||||
address = null;
|
||||
for (int i = 0; i < addrs.length; i++) {
|
||||
if (addrs[i] instanceof Inet6Address) {
|
||||
address = addrs[i];
|
||||
break;
|
||||
}
|
||||
if (address == null)
|
||||
address = addrs[0];
|
||||
}
|
||||
} else {
|
||||
address = InetAddress.getByName(serverName);
|
||||
}
|
||||
String who = address.getHostAddress();
|
||||
String why = kisses.get(who);
|
||||
if (why != null) {
|
||||
if (log != null)
|
||||
log.warn("Not querying, previous KoD from NTP server " + serverName + " (" + who + ") " + why);
|
||||
return null;
|
||||
}
|
||||
byte[] buf = new NtpMessage().toByteArray();
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
|
||||
byte[] txtime = new byte[8];
|
||||
|
||||
socket = new DatagramSocket();
|
||||
// Set the transmit timestamp *just* before sending the packet
|
||||
// ToDo: Does this actually improve performance or not?
|
||||
NtpMessage.encodeTimestamp(packet.getData(), OFF_TXTIME,
|
||||
@ -151,7 +185,7 @@ class NtpClient {
|
||||
// save for check
|
||||
System.arraycopy(packet.getData(), OFF_TXTIME, txtime, 0, 8);
|
||||
if (log != null && log.shouldDebug())
|
||||
log.debug("Sent:\n" + HexDump.dump(buf));
|
||||
log.debug("Sent to " + serverName + " (" + who + ")\n" + HexDump.dump(buf));
|
||||
|
||||
// Get response
|
||||
packet = new DatagramPacket(buf, buf.length);
|
||||
@ -170,18 +204,29 @@ class NtpClient {
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
|
||||
String from = packet.getAddress().getHostAddress();
|
||||
int port = packet.getPort();
|
||||
if (log != null && log.shouldDebug())
|
||||
log.debug("Received from: " + packet.getAddress().getHostAddress() +
|
||||
log.debug("Received from: " + from + " port " + port +
|
||||
'\n' + msg + '\n' + HexDump.dump(packet.getData()));
|
||||
|
||||
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
|
||||
// Anything else is right out, treat such responses like errors
|
||||
if ((msg.stratum < 1) || (msg.stratum > 15)) {
|
||||
// spoof check
|
||||
if (port != NTP_PORT || !who.equals(from)) {
|
||||
if (log != null && log.shouldWarn())
|
||||
log.warn("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
|
||||
log.warn("Sent to " + who + " port " + NTP_PORT+ " but received from " + packet.getSocketAddress());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
|
||||
// Anything else is right out, treat such responses like errors
|
||||
// KoD (stratum 0) processing is below, after origin time check
|
||||
if (msg.stratum > 15) {
|
||||
if (log != null && log.shouldWarn())
|
||||
log.warn("NTP server " + serverName + " bad stratum " + msg.stratum);
|
||||
return null;
|
||||
}
|
||||
|
||||
// spoof check
|
||||
if (!DataHelper.eq(txtime, 0, packet.getData(), OFF_ORIGTIME, 8)) {
|
||||
if (log != null && log.shouldWarn())
|
||||
log.warn("Origin time mismatch sent:\n" + HexDump.dump(txtime) +
|
||||
@ -189,6 +234,17 @@ class NtpClient {
|
||||
return null;
|
||||
}
|
||||
|
||||
// KoD check (AFTER spoof checks)
|
||||
if (msg.stratum == 0) {
|
||||
why = msg.referenceIdentifierToString();
|
||||
// Remember the specific IP, not the server name, although RFC 4330
|
||||
// probably wants us to block the name
|
||||
kisses.put(who, why);
|
||||
if (log != null)
|
||||
log.logAlways(Log.WARN, "KoD from NTP server " + serverName + " (" + who + ") " + why);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
@ -213,15 +269,32 @@ class NtpClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage: NtpClient [-6] [servers...]
|
||||
* default pool.ntp.org
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Process command-line args
|
||||
if(args.length <= 0) {
|
||||
boolean ipv6 = false;
|
||||
if (args.length > 0 && args[0].equals("-6")) {
|
||||
ipv6 = true;
|
||||
if (args.length == 1)
|
||||
args = new String[0];
|
||||
else
|
||||
args = Arrays.copyOfRange(args, 1, args.length);
|
||||
}
|
||||
if (args.length <= 0) {
|
||||
args = new String[] { "pool.ntp.org" };
|
||||
}
|
||||
System.out.println("Querying " + Arrays.toString(args));
|
||||
|
||||
Log log = new Log(NtpClient.class);
|
||||
long[] rv = currentTimeAndStratum(args, DEFAULT_TIMEOUT, log);
|
||||
System.out.println("Current time: " + new java.util.Date(rv[0]) + " (stratum " + rv[1] + ')');
|
||||
try {
|
||||
long[] rv = currentTimeAndStratum(args, DEFAULT_TIMEOUT, ipv6, log);
|
||||
System.out.println("Current time: " + new java.util.Date(rv[0]) + " (stratum " + rv[1] +
|
||||
") offset " + (rv[0] - System.currentTimeMillis()) + "ms");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
System.out.println("Failed: " + iae.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -72,6 +72,8 @@ import net.i2p.util.RandomSource;
|
||||
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
|
||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||
*
|
||||
* TODO NOT 2036-compliant, see RFC 4330
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* @since 0.9.1 moved from net.i2p.time
|
||||
*/
|
||||
@ -206,7 +208,7 @@ class NtpMessage {
|
||||
* GPS Global Positioning Service
|
||||
* GOES Geostationary Orbit Environment Satellite
|
||||
*/
|
||||
public byte[] referenceIdentifier = {0, 0, 0, 0};
|
||||
public final byte[] referenceIdentifier = {0, 0, 0, 0};
|
||||
|
||||
|
||||
/**
|
||||
@ -347,7 +349,7 @@ class NtpMessage {
|
||||
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
|
||||
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
|
||||
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
|
||||
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
|
||||
"Reference identifier: " + referenceIdentifierToString() + "\n" +
|
||||
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
|
||||
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
|
||||
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
|
||||
@ -360,7 +362,7 @@ class NtpMessage {
|
||||
* Converts an unsigned byte to a short. By default, Java assumes that
|
||||
* a byte is signed.
|
||||
*/
|
||||
public static short unsignedByteToShort(byte b) {
|
||||
private static short unsignedByteToShort(byte b) {
|
||||
if((b & 0x80)==0x80)
|
||||
return (short) (128 + (b & 0x7f));
|
||||
else
|
||||
@ -378,7 +380,7 @@ class NtpMessage {
|
||||
* @param pointer the offset
|
||||
* @return the time since 1900 (NOT Java time)
|
||||
*/
|
||||
public static double decodeTimestamp(byte[] array, int pointer) {
|
||||
private static double decodeTimestamp(byte[] array, int pointer) {
|
||||
double r = 0.0;
|
||||
|
||||
for(int i=0; i<8; i++) {
|
||||
@ -431,7 +433,7 @@ class NtpMessage {
|
||||
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
|
||||
* formatted date/time string.
|
||||
*/
|
||||
public static String timestampToString(double timestamp) {
|
||||
private static String timestampToString(double timestamp) {
|
||||
if(timestamp==0) return "0";
|
||||
|
||||
// timestamp is relative to 1900, utc is used by Java and is relative
|
||||
@ -451,13 +453,20 @@ class NtpMessage {
|
||||
return date + fractionSting;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 0.9.29
|
||||
* @return non-null, "" if unset
|
||||
*/
|
||||
public String referenceIdentifierToString() {
|
||||
return referenceIdentifierToString(referenceIdentifier, stratum, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of a reference identifier according
|
||||
* to the rules set out in RFC 2030.
|
||||
* @return non-null, "" if unset
|
||||
*/
|
||||
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
|
||||
private static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
|
||||
// From the RFC 2030:
|
||||
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
|
||||
// or stratum-1 (primary) servers, this is a four-character ASCII
|
||||
@ -484,6 +493,10 @@ class NtpMessage {
|
||||
// In NTP Version 4 secondary servers, this is the low order 32 bits
|
||||
// of the latest transmit timestamp of the reference source.
|
||||
else if(version==4) {
|
||||
// Unimplemented RFC 4330:
|
||||
// For IPv6 and OSI secondary servers, the value is the first 32 bits of
|
||||
// the MD5 hash of the IPv6 or NSAP address of the synchronization
|
||||
// source.
|
||||
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
|
||||
(unsignedByteToShort(ref[1]) / 65536.0) +
|
||||
(unsignedByteToShort(ref[2]) / 16777216.0) +
|
||||
|
@ -273,7 +273,8 @@ public class RouterTimestamper extends Timestamper {
|
||||
// // 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, perServerTimeout, _log);
|
||||
// IPv6 arg TODO
|
||||
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout, false, _log);
|
||||
now = timeAndStratum[0];
|
||||
stratum = (int) timeAndStratum[1];
|
||||
long delta = now - _context.clock().now();
|
||||
|
Reference in New Issue
Block a user