Changeset fbbfd8a


Ignore:
Timestamp:
Dec 21, 2016 5:48:47 PM (3 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
0819857
Parents:
95fb2df6
Message:

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
Location:
router/java/src/net/i2p/router
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • router/java/src/net/i2p/router/CommandLine.java

    r95fb2df6 rfbbfd8a  
    2222        "net.i2p.router.peermanager.ProfileOrganizer",
    2323        "net.i2p.router.tasks.CryptoChecker",
     24        "net.i2p.router.time.NtpClient",
    2425        "net.i2p.router.transport.GeoIPv6",
    2526        "net.i2p.router.transport.udp.MTU",
  • router/java/src/net/i2p/router/time/NtpClient.java

    r95fb2df6 rfbbfd8a  
    3434import java.net.DatagramSocket;
    3535import java.net.InetAddress;
     36import java.net.Inet6Address;
     37import java.net.UnknownHostException;
    3638import java.util.ArrayList;
     39import java.util.Arrays;
    3740import java.util.Collections;
     41import java.util.Map;
     42import java.util.concurrent.ConcurrentHashMap;
    3843
    3944import net.i2p.data.DataHelper;
     
    5055 * Note that on windows platforms, the curent time-of-day timestamp is limited
    5156 * to an resolution of 10ms and adversely affects the accuracy of the results.
     57 *
     58 * Public only for main(), not a public API, not for external use.
     59 *
     60 * TODO NOT 2036-compliant, see RFC 4330
    5261 *
    5362 * @author Adam Buckley
     
    5564 * @since 0.9.1 moved from net.i2p.time
    5665 */
    57 class NtpClient {
     66public class NtpClient {
    5867    /** difference between the unix epoch and jan 1 1900 (NTP uses that) */
    59     public final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
     68    final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
    6069    private final static int NTP_PORT = 123;
    6170    private static final int DEFAULT_TIMEOUT = 10*1000;
     
    6372    private static final int OFF_TXTIME = 40;
    6473    private static final int MIN_PKT_LEN = 48;
     74    // IP:reason for servers that sent us a kiss of death
     75    private static final Map<String, String> kisses = new ConcurrentHashMap<String, String>(2);
    6576
    6677    /**
     
    96107     * @since 0.7.12
    97108     */
    98     public static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout, Log log) {
     109    static long[] currentTimeAndStratum(String serverNames[], int perServerTimeout, boolean preferIPv6, Log log) {
    99110        if (serverNames == null)
    100111            throw new IllegalArgumentException("No NTP servers specified");
     
    104115        Collections.shuffle(names);
    105116        for (int i = 0; i < names.size(); i++) {
    106             long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, log);
     117            long[] rv = currentTimeAndStratum(names.get(i), perServerTimeout, preferIPv6, log);
    107118            if (rv != null && rv[0] > 0)
    108119                return rv;
     
    132143     * @since 0.7.12
    133144     */
    134     private static long[] currentTimeAndStratum(String serverName, int timeout, Log log) {
     145    private static long[] currentTimeAndStratum(String serverName, int timeout, boolean preferIPv6, Log log) {
    135146        DatagramSocket socket = null;
    136147        try {
    137148            // Send request
    138             socket = new DatagramSocket();
    139             InetAddress address = InetAddress.getByName(serverName);
     149            InetAddress address;
     150            if (preferIPv6) {
     151                InetAddress[] addrs = InetAddress.getAllByName(serverName);
     152                if (addrs == null || addrs.length == 0)
     153                    throw new UnknownHostException();
     154                address = null;
     155                for (int i = 0; i < addrs.length; i++) {
     156                    if (addrs[i] instanceof Inet6Address) {
     157                        address = addrs[i];
     158                        break;
     159                    }
     160                    if (address == null)
     161                        address = addrs[0];
     162                }
     163            } else {
     164                address = InetAddress.getByName(serverName);
     165            }
     166            String who = address.getHostAddress();
     167            String why = kisses.get(who);
     168            if (why != null) {
     169                if (log != null)
     170                    log.warn("Not querying, previous KoD from NTP server " + serverName + " (" + who + ") " + why);
     171                return null;
     172            }
    140173            byte[] buf = new NtpMessage().toByteArray();
    141174            DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
    142175            byte[] txtime = new byte[8];
    143176
     177            socket = new DatagramSocket();
    144178            // Set the transmit timestamp *just* before sending the packet
    145179            // ToDo: Does this actually improve performance or not?
     
    152186            System.arraycopy(packet.getData(), OFF_TXTIME, txtime, 0, 8);
    153187            if (log != null && log.shouldDebug())
    154                 log.debug("Sent:\n" + HexDump.dump(buf));
     188                log.debug("Sent to " + serverName + " (" + who + ")\n" + HexDump.dump(buf));
    155189
    156190            // Get response
     
    171205            NtpMessage msg = new NtpMessage(packet.getData());
    172206
     207            String from = packet.getAddress().getHostAddress();
     208            int port = packet.getPort();
    173209            if (log != null && log.shouldDebug())
    174                 log.debug("Received from: " + packet.getAddress().getHostAddress() +
     210                log.debug("Received from: " + from + " port " + port +
    175211                          '\n' + msg + '\n' + HexDump.dump(packet.getData()));
     212
     213            // spoof check
     214            if (port != NTP_PORT || !who.equals(from)) {
     215                if (log != null && log.shouldWarn())
     216                    log.warn("Sent to " + who + " port " + NTP_PORT+ " but received from " + packet.getSocketAddress());
     217                return null;
     218            }
    176219
    177220            // Stratum must be between 1 (atomic) and 15 (maximum defined value)
    178221            // Anything else is right out, treat such responses like errors
    179             if ((msg.stratum < 1) || (msg.stratum > 15)) {
     222            // KoD (stratum 0) processing is below, after origin time check
     223            if (msg.stratum > 15) {
    180224                if (log != null && log.shouldWarn())
    181                     log.warn("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
    182                 return null;
    183             }
    184 
     225                    log.warn("NTP server " + serverName + " bad stratum " + msg.stratum);
     226                return null;
     227            }
     228
     229            // spoof check
    185230            if (!DataHelper.eq(txtime, 0, packet.getData(), OFF_ORIGTIME, 8)) {
    186231                if (log != null && log.shouldWarn())
    187232                    log.warn("Origin time mismatch sent:\n" + HexDump.dump(txtime) +
    188233                             "rcvd:\n" + HexDump.dump(packet.getData(), OFF_ORIGTIME, 8));
     234                return null;
     235            }
     236
     237            // KoD check (AFTER spoof checks)
     238            if (msg.stratum == 0) {
     239                why = msg.referenceIdentifierToString();
     240                // Remember the specific IP, not the server name, although RFC 4330
     241                // probably wants us to block the name
     242                kisses.put(who, why);
     243                if (log != null)
     244                    log.logAlways(Log.WARN, "KoD from NTP server " + serverName + " (" + who + ") " + why);
    189245                return null;
    190246            }
     
    214270    }
    215271   
     272    /**
     273     * Usage: NtpClient [-6] [servers...]
     274     * default pool.ntp.org
     275     */
    216276    public static void main(String[] args) throws IOException {
    217         // Process command-line args
    218         if(args.length <= 0) {
     277        boolean ipv6 = false;
     278        if (args.length > 0 && args[0].equals("-6")) {
     279            ipv6 = true;
     280            if (args.length == 1)
     281                args = new String[0];
     282            else
     283                args = Arrays.copyOfRange(args, 1, args.length);
     284        }
     285        if (args.length <= 0) {
    219286           args = new String[] { "pool.ntp.org" };
    220287        }
     288        System.out.println("Querying " + Arrays.toString(args));
    221289
    222290        Log log = new Log(NtpClient.class);
    223         long[] rv = currentTimeAndStratum(args, DEFAULT_TIMEOUT, log);
    224         System.out.println("Current time: " + new java.util.Date(rv[0]) + " (stratum " + rv[1] + ')');
     291        try {
     292            long[] rv = currentTimeAndStratum(args, DEFAULT_TIMEOUT, ipv6, log);
     293            System.out.println("Current time: " + new java.util.Date(rv[0]) + " (stratum " + rv[1] +
     294                               ") offset " + (rv[0] - System.currentTimeMillis()) + "ms");
     295        } catch (IllegalArgumentException iae) {
     296            System.out.println("Failed: " + iae.getMessage());
     297        }
    225298    }
    226299   
  • router/java/src/net/i2p/router/time/NtpMessage.java

    r95fb2df6 rfbbfd8a  
    7373 * NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
    7474 *
     75 * TODO NOT 2036-compliant, see RFC 4330
     76 *
    7577 * @author Adam Buckley
    7678 * @since 0.9.1 moved from net.i2p.time
     
    207209     * GOES     Geostationary Orbit Environment Satellite
    208210     */
    209     public byte[] referenceIdentifier = {0, 0, 0, 0};
     211    public final byte[] referenceIdentifier = {0, 0, 0, 0};
    210212   
    211213   
     
    348350               "Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
    349351               "Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
    350                "Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
     352               "Reference identifier: " + referenceIdentifierToString() + "\n" +
    351353               "Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
    352354               "Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
     
    361363     * a byte is signed.
    362364     */
    363     public static short unsignedByteToShort(byte b) {
     365    private static short unsignedByteToShort(byte b) {
    364366        if((b & 0x80)==0x80)
    365367            return (short) (128 + (b & 0x7f));
     
    379381     * @return the time since 1900 (NOT Java time)
    380382     */
    381     public static double decodeTimestamp(byte[] array, int pointer) {
     383    private static double decodeTimestamp(byte[] array, int pointer) {
    382384        double r = 0.0;
    383385       
     
    432434     * formatted date/time string.
    433435     */
    434     public static String timestampToString(double timestamp) {
     436    private static String timestampToString(double timestamp) {
    435437        if(timestamp==0) return "0";
    436438       
     
    452454    }
    453455   
    454    
     456    /**
     457     * @since 0.9.29
     458     * @return non-null, "" if unset
     459     */
     460    public String referenceIdentifierToString() {
     461        return referenceIdentifierToString(referenceIdentifier, stratum, version);
     462    }
    455463   
    456464    /**
    457465     * Returns a string representation of a reference identifier according
    458466     * to the rules set out in RFC 2030.
    459      */
    460     public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
     467     * @return non-null, "" if unset
     468     */
     469    private static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
    461470        // From the RFC 2030:
    462471        // In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
     
    485494        // of the latest transmit timestamp of the reference source.
    486495        else if(version==4) {
     496            // Unimplemented RFC 4330:
     497            // For IPv6 and OSI secondary servers, the value is the first 32 bits of
     498            // the MD5 hash of the IPv6 or NSAP address of the synchronization
     499            // source.
    487500            return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
    488501                   (unsignedByteToShort(ref[1]) / 65536.0) +
  • router/java/src/net/i2p/router/time/RouterTimestamper.java

    r95fb2df6 rfbbfd8a  
    274274            //    try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
    275275            //}
    276             long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout, _log);
     276            // IPv6 arg TODO
     277            long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList, perServerTimeout, false, _log);
    277278            now = timeAndStratum[0];
    278279            stratum = (int) timeAndStratum[1];
Note: See TracChangeset for help on using the changeset viewer.