- 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:
zzz
2016-12-21 17:48:47 +00:00
parent 95fb2df609
commit fbbfd8acf0
4 changed files with 113 additions and 25 deletions

View File

@ -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"

View File

@ -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());
}
}
/****

View File

@ -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) +

View File

@ -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();