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.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
@ -53,9 +56,12 @@ import java.util.Collections;
*/
class NtpClient {
/** 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 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
@ -63,6 +69,7 @@ class NtpClient {
* @return milliseconds since january 1, 1970 (UTC)
* @throws IllegalArgumentException if none of the servers are reachable
*/
/****
public static long currentTime(String serverNames[]) {
if (serverNames == null)
throw new IllegalArgumentException("No NTP servers specified");
@ -77,15 +84,18 @@ class NtpClient {
}
throw new IllegalArgumentException("No reachable NTP servers specified");
}
****/
/**
* Query the ntp servers, returning the current time from first one we find
* Hack to return time and stratum
*
* @param log may be null
* @return time in rv[0] and stratum in rv[1]
* @throws IllegalArgumentException if none of the servers are reachable
* @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)
throw new IllegalArgumentException("No NTP servers specified");
ArrayList<String> names = new ArrayList<String>(serverNames.length);
@ -93,7 +103,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);
long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, log);
if (rv != null && rv[0] > 0)
return rv;
}
@ -105,19 +115,23 @@ class NtpClient {
*
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
*/
/****
public static long currentTime(String serverName) {
long[] la = currentTimeAndStratum(serverName, DEFAULT_TIMEOUT);
if (la != null)
return la[0];
return -1;
}
****/
/**
* 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
* @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;
try {
// Send request
@ -125,14 +139,19 @@ class NtpClient {
InetAddress address = InetAddress.getByName(serverName);
byte[] buf = new NtpMessage().toByteArray();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
byte[] txtime = new byte[8];
// Set the transmit timestamp *just* before sending the packet
// ToDo: Does this actually improve performance or not?
NtpMessage.encodeTimestamp(packet.getData(), 40,
NtpMessage.encodeTimestamp(packet.getData(), OFF_TXTIME,
(System.currentTimeMillis()/1000.0)
+ SECONDS_1900_TO_EPOCH);
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
packet = new DatagramPacket(buf, buf.length);
@ -142,28 +161,51 @@ class NtpClient {
// Immediately record the incoming timestamp
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
NtpMessage msg = new NtpMessage(packet.getData());
//double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
// (msg.receiveTimestamp-msg.transmitTimestamp);
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
(msg.transmitTimestamp - destinationTimestamp)) / 2;
if (log != null && log.shouldDebug())
log.debug("Received from: " + packet.getAddress().getHostAddress() +
'\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)) {
//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;
}
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];
rv[0] = (long)(System.currentTimeMillis() + localClockOffset*1000);
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;
} catch (IOException ioe) {
//ioe.printStackTrace();
if (log != null && log.shouldWarn())
log.warn("NTP failure from " + serverName, ioe);
return null;
} finally {
if (socket != null)
@ -171,20 +213,19 @@ class NtpClient {
}
}
/****
public static void main(String[] args) throws IOException {
// Process command-line args
if(args.length <= 0) {
printUsage();
return;
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
args = new String[] { "pool.ntp.org" };
}
long now = currentTime(args);
System.out.println("Current time: " + new java.util.Date(now));
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] + ')');
}
static void printUsage() {
/****
private static void printUsage() {
System.out.println(
"NtpClient - an NTP client for Java.\n" +
"\n" +

View File

@ -31,6 +31,7 @@ package net.i2p.router.time;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
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
* 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,
* 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.
*
* @param array 48 bytes minimum
*/
public NtpMessage(byte[] array) {
// 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
// appropriate default values.
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.
*
* @return 48 bytes
*/
public byte[] toByteArray() {
// 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>
* and return it as a double, according to the NTP 64-bit timestamp
* 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) {
double r = 0.0;
@ -383,10 +392,21 @@ class NtpMessage {
/**
* 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) {
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
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
double base = Math.pow(2, (3-i)*8);
@ -401,7 +421,8 @@ class NtpMessage {
// low order bits of the timestamp with a random, unbiased
// bitstring, both to avoid systematic roundoff errors and as
// 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
// to 1970
double utc = timestamp - (2208988800.0);
double utc = timestamp - NtpClient.SECONDS_1900_TO_EPOCH;
// milliseconds
long ms = (long) (utc * 1000.0);
@ -425,7 +446,7 @@ class NtpMessage {
// fraction
double fraction = timestamp - ((long) timestamp);
String fractionSting = new DecimalFormat(".000000").format(fraction);
String fractionSting = new DecimalFormat(".000000000").format(fraction);
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
// try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
//}
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout);
long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout, _log);
now = timeAndStratum[0];
stratum = (int) timeAndStratum[1];
long delta = now - _context.clock().now();