Don't put random data in zeroed fields
Increase random data in originate timestamp from 1 byte to 2 bytes
Verify originate timestamp to prevent injection
Verify received packet size
Log tweaks, javadocs, cleanups
This commit is contained in:
zzz
2016-05-12 13:48:44 +00:00
parent f9bd4952f4
commit d6638f3e00
3 changed files with 89 additions and 27 deletions

View File

@@ -36,6 +36,9 @@ import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import net.i2p.data.DataHelper;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/** /**
* NtpClient - an NTP client for Java. This program connects to an NTP server * NtpClient - an NTP client for Java. This program connects to an NTP server
@@ -53,9 +56,12 @@ import java.util.Collections;
*/ */
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; public final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
private final static int NTP_PORT = 123; private final static int NTP_PORT = 123;
private static final int DEFAULT_TIMEOUT = 10*1000; 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;
/** /**
* Query the ntp servers, returning the current time from first one we find * Query the ntp servers, returning the current time from first one we find
@@ -63,6 +69,7 @@ class NtpClient {
* @return milliseconds since january 1, 1970 (UTC) * @return milliseconds since january 1, 1970 (UTC)
* @throws IllegalArgumentException if none of the servers are reachable * @throws IllegalArgumentException if none of the servers are reachable
*/ */
/****
public static long currentTime(String serverNames[]) { public static long currentTime(String serverNames[]) {
if (serverNames == null) if (serverNames == null)
throw new IllegalArgumentException("No NTP servers specified"); throw new IllegalArgumentException("No NTP servers specified");
@@ -77,15 +84,18 @@ class NtpClient {
} }
throw new IllegalArgumentException("No reachable NTP servers specified"); throw new IllegalArgumentException("No reachable NTP servers specified");
} }
****/
/** /**
* Query the ntp servers, returning the current time from first one we find * Query the ntp servers, returning the current time from first one we find
* Hack to return time and stratum * Hack to return time and stratum
*
* @param log may be null
* @return time in rv[0] and stratum in rv[1] * @return time in rv[0] and stratum in rv[1]
* @throws IllegalArgumentException if none of the servers are reachable * @throws IllegalArgumentException if none of the servers are reachable
* @since 0.7.12 * @since 0.7.12
*/ */
public static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout) { public static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout, Log log) {
if (serverNames == null) if (serverNames == null)
throw new IllegalArgumentException("No NTP servers specified"); throw new IllegalArgumentException("No NTP servers specified");
ArrayList<String> names = new ArrayList<String>(serverNames.length); ArrayList<String> names = new ArrayList<String>(serverNames.length);
@@ -93,7 +103,7 @@ class NtpClient {
names.add(serverNames[i]); names.add(serverNames[i]);
Collections.shuffle(names); Collections.shuffle(names);
for (int i = 0; i < names.size(); i++) { for (int i = 0; i < names.size(); i++) {
long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout); long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, log);
if (rv != null && rv[0] > 0) if (rv != null && rv[0] > 0)
return rv; return rv;
} }
@@ -105,19 +115,23 @@ class NtpClient {
* *
* @return milliseconds since january 1, 1970 (UTC), or -1 on error * @return milliseconds since january 1, 1970 (UTC), or -1 on error
*/ */
/****
public static long currentTime(String serverName) { public static long currentTime(String serverName) {
long[] la = currentTimeAndStratum(serverName, DEFAULT_TIMEOUT); long[] la = currentTimeAndStratum(serverName, DEFAULT_TIMEOUT);
if (la != null) if (la != null)
return la[0]; return la[0];
return -1; return -1;
} }
****/
/** /**
* Hack to return time and stratum * Hack to return time and stratum
*
* @param log may be null
* @return time in rv[0] and stratum in rv[1], or null for error * @return time in rv[0] and stratum in rv[1], or null for error
* @since 0.7.12 * @since 0.7.12
*/ */
private static long[] currentTimeAndStratum(String serverName, int timeout) { private static long[] currentTimeAndStratum(String serverName, int timeout, Log log) {
DatagramSocket socket = null; DatagramSocket socket = null;
try { try {
// Send request // Send request
@@ -125,14 +139,19 @@ class NtpClient {
InetAddress address = InetAddress.getByName(serverName); InetAddress address = InetAddress.getByName(serverName);
byte[] buf = new NtpMessage().toByteArray(); byte[] buf = new NtpMessage().toByteArray();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT); DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
byte[] txtime = new byte[8];
// Set the transmit timestamp *just* before sending the packet // Set the transmit timestamp *just* before sending the packet
// ToDo: Does this actually improve performance or not? // ToDo: Does this actually improve performance or not?
NtpMessage.encodeTimestamp(packet.getData(), 40, NtpMessage.encodeTimestamp(packet.getData(), OFF_TXTIME,
(System.currentTimeMillis()/1000.0) (System.currentTimeMillis()/1000.0)
+ SECONDS_1900_TO_EPOCH); + SECONDS_1900_TO_EPOCH);
socket.send(packet); socket.send(packet);
// save for check
System.arraycopy(packet.getData(), OFF_TXTIME, txtime, 0, 8);
if (log != null && log.shouldDebug())
log.debug("Sent:\n" + HexDump.dump(buf));
// Get response // Get response
packet = new DatagramPacket(buf, buf.length); packet = new DatagramPacket(buf, buf.length);
@@ -142,28 +161,51 @@ class NtpClient {
// Immediately record the incoming timestamp // Immediately record the incoming timestamp
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH; double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
if (packet.getLength() < MIN_PKT_LEN) {
if (log != null && log.shouldWarn())
log.warn("Short packet length " + packet.getLength());
return null;
}
// Process response // Process response
NtpMessage msg = new NtpMessage(packet.getData()); NtpMessage msg = new NtpMessage(packet.getData());
//double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) - if (log != null && log.shouldDebug())
// (msg.receiveTimestamp-msg.transmitTimestamp); log.debug("Received from: " + packet.getAddress().getHostAddress() +
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) + '\n' + msg + '\n' + HexDump.dump(packet.getData()));
(msg.transmitTimestamp - destinationTimestamp)) / 2;
// Stratum must be between 1 (atomic) and 15 (maximum defined value) // Stratum must be between 1 (atomic) and 15 (maximum defined value)
// Anything else is right out, treat such responses like errors // Anything else is right out, treat such responses like errors
if ((msg.stratum < 1) || (msg.stratum > 15)) { if ((msg.stratum < 1) || (msg.stratum > 15)) {
//System.out.println("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing."); if (log != null && log.shouldWarn())
log.warn("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
return null; return null;
} }
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) +
"rcvd:\n" + HexDump.dump(packet.getData(), OFF_ORIGTIME, 8));
return null;
}
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
(msg.transmitTimestamp - destinationTimestamp)) / 2;
long[] rv = new long[2]; long[] rv = new long[2];
rv[0] = (long)(System.currentTimeMillis() + localClockOffset*1000); rv[0] = (long)(System.currentTimeMillis() + localClockOffset*1000);
rv[1] = msg.stratum; rv[1] = msg.stratum;
//System.out.println("host: " + address.getHostAddress() + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds"); if (log != null && log.shouldInfo()) {
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
(msg.receiveTimestamp-msg.transmitTimestamp);
log.info("host: " + packet.getAddress().getHostAddress() + " rtt: " +
roundTripDelay + " offset: " + localClockOffset + " seconds");
}
return rv; return rv;
} catch (IOException ioe) { } catch (IOException ioe) {
//ioe.printStackTrace(); if (log != null && log.shouldWarn())
log.warn("NTP failure from " + serverName, ioe);
return null; return null;
} finally { } finally {
if (socket != null) if (socket != null)
@@ -171,20 +213,19 @@ class NtpClient {
} }
} }
/****
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
// Process command-line args // Process command-line args
if(args.length <= 0) { if(args.length <= 0) {
printUsage(); args = new String[] { "pool.ntp.org" };
return;
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
} }
long now = currentTime(args); Log log = new Log(NtpClient.class);
System.out.println("Current time: " + new java.util.Date(now)); long[] rv = currentTimeAndStratum(args, DEFAULT_TIMEOUT, log);
System.out.println("Current time: " + new java.util.Date(rv[0]) + " (stratum " + rv[1] + ')');
} }
static void printUsage() { /****
private static void printUsage() {
System.out.println( System.out.println(
"NtpClient - an NTP client for Java.\n" + "NtpClient - an NTP client for Java.\n" +
"\n" + "\n" +

View File

@@ -31,6 +31,7 @@ package net.i2p.router.time;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import net.i2p.util.RandomSource; import net.i2p.util.RandomSource;
@@ -117,7 +118,7 @@ class NtpMessage {
* in the request and the server sets it to 4 (server) in the reply. In * in the request and the server sets it to 4 (server) in the reply. In
* multicast mode, the server sets this field to 5 (broadcast). * multicast mode, the server sets this field to 5 (broadcast).
*/ */
public byte mode = 0; public final byte mode;
/** /**
@@ -233,12 +234,14 @@ class NtpMessage {
* This is the time at which the reply departed the server for the client, * This is the time at which the reply departed the server for the client,
* in seconds since 00:00 1-Jan-1900. * in seconds since 00:00 1-Jan-1900.
*/ */
public double transmitTimestamp = 0; public final double transmitTimestamp;
/** /**
* Constructs a new NtpMessage from an array of bytes. * Constructs a new NtpMessage from an array of bytes.
*
* @param array 48 bytes minimum
*/ */
public NtpMessage(byte[] array) { public NtpMessage(byte[] array) {
// See the packet format diagram in RFC 2030 for details // See the packet format diagram in RFC 2030 for details
@@ -280,13 +283,15 @@ class NtpMessage {
// Note that all the other member variables are already set with // Note that all the other member variables are already set with
// appropriate default values. // appropriate default values.
this.mode = 3; this.mode = 3;
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0; this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + NtpClient.SECONDS_1900_TO_EPOCH;
} }
/** /**
* This method constructs the data bytes of a raw NTP packet. * This method constructs the data bytes of a raw NTP packet.
*
* @return 48 bytes
*/ */
public byte[] toByteArray() { public byte[] toByteArray() {
// All bytes are automatically set to 0 // All bytes are automatically set to 0
@@ -368,6 +373,10 @@ class NtpMessage {
* Will read 8 bytes of a message beginning at <code>pointer</code> * Will read 8 bytes of a message beginning at <code>pointer</code>
* and return it as a double, according to the NTP 64-bit timestamp * and return it as a double, according to the NTP 64-bit timestamp
* format. * format.
*
* @param array 8 bytes starting at pointer
* @param pointer the offset
* @return the time since 1900 (NOT Java time)
*/ */
public static double decodeTimestamp(byte[] array, int pointer) { public static double decodeTimestamp(byte[] array, int pointer) {
double r = 0.0; double r = 0.0;
@@ -383,10 +392,21 @@ class NtpMessage {
/** /**
* Encodes a timestamp in the specified position in the message * Encodes a timestamp in the specified position in the message
*
* @param array output 8 bytes starting at pointer
* @param pointer the offset
* @param timestamp the time to encode (since 1900, NOT Java time)
*/ */
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) { public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
if (timestamp == 0.0) {
// don't put in random data
Arrays.fill(array, pointer, pointer + 8, (byte) 0);
return;
}
// Converts a double into a 64-bit fixed point // Converts a double into a 64-bit fixed point
for(int i=0; i<8; i++) { // 6 bytes of real data
for(int i=0; i<7; i++) {
// 2^24, 2^16, 2^8, .. 2^-32 // 2^24, 2^16, 2^8, .. 2^-32
double base = Math.pow(2, (3-i)*8); double base = Math.pow(2, (3-i)*8);
@@ -401,7 +421,8 @@ class NtpMessage {
// low order bits of the timestamp with a random, unbiased // low order bits of the timestamp with a random, unbiased
// bitstring, both to avoid systematic roundoff errors and as // bitstring, both to avoid systematic roundoff errors and as
// a means of loop detection and replay detection. // a means of loop detection and replay detection.
array[7+pointer] = (byte) (RandomSource.getInstance().nextInt()); // 2 bytes of random data
RandomSource.getInstance().nextBytes(array, pointer + 6, 2);
} }
@@ -415,7 +436,7 @@ class NtpMessage {
// timestamp is relative to 1900, utc is used by Java and is relative // timestamp is relative to 1900, utc is used by Java and is relative
// to 1970 // to 1970
double utc = timestamp - (2208988800.0); double utc = timestamp - NtpClient.SECONDS_1900_TO_EPOCH;
// milliseconds // milliseconds
long ms = (long) (utc * 1000.0); long ms = (long) (utc * 1000.0);
@@ -425,7 +446,7 @@ class NtpMessage {
// fraction // fraction
double fraction = timestamp - ((long) timestamp); double fraction = timestamp - ((long) timestamp);
String fractionSting = new DecimalFormat(".000000").format(fraction); String fractionSting = new DecimalFormat(".000000000").format(fraction);
return date + fractionSting; return date + fractionSting;
} }

View File

@@ -273,7 +273,7 @@ public class RouterTimestamper extends Timestamper {
// // this delays startup when net is disconnected or the timeserver list is bad, don't make it too long // // 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) {} // try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
//} //}
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout); long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout, _log);
now = timeAndStratum[0]; now = timeAndStratum[0];
stratum = (int) timeAndStratum[1]; stratum = (int) timeAndStratum[1];
long delta = now - _context.clock().now(); long delta = now - _context.clock().now();