1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.net.sntp.Duration64;
22 import android.net.sntp.Timestamp64;
23 import android.os.SystemClock;
24 import android.util.Log;
25 import android.util.Slog;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.TrafficStatsConstants;
29 
30 import java.net.DatagramPacket;
31 import java.net.DatagramSocket;
32 import java.net.InetAddress;
33 import java.net.InetSocketAddress;
34 import java.net.UnknownHostException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.SecureRandom;
37 import java.time.Duration;
38 import java.time.Instant;
39 import java.util.Objects;
40 import java.util.Random;
41 import java.util.function.Supplier;
42 
43 /**
44  * Simple, single-use SNTP client class for retrieving network time.
45  *
46  * Sample usage:
47  * <pre>SntpClient client = new SntpClient();
48  * if (client.requestTime("time.foo.com")) {
49  *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
50  * }
51  * </pre>
52  *
53  * <p>This class is not thread-safe.
54  *
55  * @hide
56  */
57 public class SntpClient {
58     private static final String TAG = "SntpClient";
59     private static final boolean DBG = true;
60 
61     private static final int REFERENCE_TIME_OFFSET = 16;
62     private static final int ORIGINATE_TIME_OFFSET = 24;
63     private static final int RECEIVE_TIME_OFFSET = 32;
64     private static final int TRANSMIT_TIME_OFFSET = 40;
65     private static final int NTP_PACKET_SIZE = 48;
66 
67     public static final int STANDARD_NTP_PORT = 123;
68     private static final int NTP_MODE_CLIENT = 3;
69     private static final int NTP_MODE_SERVER = 4;
70     private static final int NTP_MODE_BROADCAST = 5;
71     private static final int NTP_VERSION = 3;
72 
73     private static final int NTP_LEAP_NOSYNC = 3;
74     private static final int NTP_STRATUM_DEATH = 0;
75     private static final int NTP_STRATUM_MAX = 15;
76 
77     // The source of the current system clock time, replaceable for testing.
78     private final Supplier<Instant> mSystemTimeSupplier;
79 
80     private final Random mRandom;
81 
82     // The last offset calculated from an NTP server response
83     private long mClockOffset;
84 
85     // The last system time computed from an NTP server response
86     private long mNtpTime;
87 
88     // The value of SystemClock.elapsedRealtime() corresponding to mNtpTime / mClockOffset
89     private long mNtpTimeReference;
90 
91     // The round trip (network) time in milliseconds
92     private long mRoundTripTime;
93 
94     // Details of the NTP server used to obtain the time last.
95     @Nullable private InetSocketAddress mServerSocketAddress;
96 
97     private static class InvalidServerReplyException extends Exception {
InvalidServerReplyException(String message)98         public InvalidServerReplyException(String message) {
99             super(message);
100         }
101     }
102 
103     @UnsupportedAppUsage
SntpClient()104     public SntpClient() {
105         this(Instant::now, defaultRandom());
106     }
107 
108     @VisibleForTesting
SntpClient(Supplier<Instant> systemTimeSupplier, Random random)109     public SntpClient(Supplier<Instant> systemTimeSupplier, Random random) {
110         mSystemTimeSupplier = Objects.requireNonNull(systemTimeSupplier);
111         mRandom = Objects.requireNonNull(random);
112     }
113 
114     /**
115      * Sends an SNTP request to the given host and processes the response.
116      *
117      * @param host host name of the server.
118      * @param port port of the server.
119      * @param timeout network timeout in milliseconds. the timeout doesn't include the DNS lookup
120      *                time, and it applies to each individual query to the resolved addresses of
121      *                the NTP server.
122      * @param network network over which to send the request.
123      * @return true if the transaction was successful.
124      */
requestTime(String host, int port, int timeout, Network network)125     public boolean requestTime(String host, int port, int timeout, Network network) {
126         final Network networkForResolv = network.getPrivateDnsBypassingCopy();
127         try {
128             final InetAddress[] addresses = networkForResolv.getAllByName(host);
129             for (int i = 0; i < addresses.length; i++) {
130                 if (requestTime(addresses[i], port, timeout, networkForResolv)) {
131                     return true;
132                 }
133             }
134         } catch (UnknownHostException e) {
135             Log.w(TAG, "Unknown host: " + host);
136             EventLogTags.writeNtpFailure(host, e.toString());
137         }
138 
139         if (DBG) Log.d(TAG, "request time failed");
140         return false;
141     }
142 
requestTime(InetAddress address, int port, int timeout, Network network)143     public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
144         DatagramSocket socket = null;
145         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
146                 TrafficStatsConstants.TAG_SYSTEM_NTP);
147         try {
148             socket = new DatagramSocket();
149             network.bindSocket(socket);
150             socket.setSoTimeout(timeout);
151             byte[] buffer = new byte[NTP_PACKET_SIZE];
152             DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);
153 
154             // set mode = 3 (client) and version = 3
155             // mode is in low 3 bits of first byte
156             // version is in bits 3-5 of first byte
157             buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
158 
159             // get current time and write it to the request packet
160             final Instant requestTime = mSystemTimeSupplier.get();
161             final Timestamp64 requestTimestamp = Timestamp64.fromInstant(requestTime);
162 
163             final Timestamp64 randomizedRequestTimestamp =
164                     requestTimestamp.randomizeSubMillis(mRandom);
165             final long requestTicks = SystemClock.elapsedRealtime();
166             writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, randomizedRequestTimestamp);
167 
168             socket.send(request);
169 
170             // read the response
171             DatagramPacket response = new DatagramPacket(buffer, buffer.length);
172             socket.receive(response);
173             final long responseTicks = SystemClock.elapsedRealtime();
174             final Instant responseTime = requestTime.plusMillis(responseTicks - requestTicks);
175             final Timestamp64 responseTimestamp = Timestamp64.fromInstant(responseTime);
176 
177             // extract the results
178             final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
179             final byte mode = (byte) (buffer[0] & 0x7);
180             final int stratum = (int) (buffer[1] & 0xff);
181             final Timestamp64 referenceTimestamp = readTimeStamp(buffer, REFERENCE_TIME_OFFSET);
182             final Timestamp64 originateTimestamp = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
183             final Timestamp64 receiveTimestamp = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
184             final Timestamp64 transmitTimestamp = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
185 
186             /* Do validation according to RFC */
187             checkValidServerReply(leap, mode, stratum, transmitTimestamp, referenceTimestamp,
188                     randomizedRequestTimestamp, originateTimestamp);
189 
190             long totalTransactionDurationMillis = responseTicks - requestTicks;
191             long serverDurationMillis =
192                     Duration64.between(receiveTimestamp, transmitTimestamp).toDuration().toMillis();
193             long roundTripTimeMillis = totalTransactionDurationMillis - serverDurationMillis;
194 
195             Duration clockOffsetDuration = calculateClockOffset(requestTimestamp,
196                     receiveTimestamp, transmitTimestamp, responseTimestamp);
197             long clockOffsetMillis = clockOffsetDuration.toMillis();
198 
199             EventLogTags.writeNtpSuccess(
200                     address.toString(), roundTripTimeMillis, clockOffsetMillis);
201             if (DBG) {
202                 Log.d(TAG, "round trip: " + roundTripTimeMillis + "ms, "
203                         + "clock offset: " + clockOffsetMillis + "ms");
204             }
205 
206             // save our results - use the times on this side of the network latency
207             // (response rather than request time)
208             mClockOffset = clockOffsetMillis;
209             mNtpTime = responseTime.plus(clockOffsetDuration).toEpochMilli();
210             mNtpTimeReference = responseTicks;
211             mRoundTripTime = roundTripTimeMillis;
212             mServerSocketAddress = new InetSocketAddress(address, port);
213         } catch (Exception e) {
214             EventLogTags.writeNtpFailure(address.toString(), e.toString());
215             if (DBG) Log.d(TAG, "request time failed: " + e);
216             return false;
217         } finally {
218             if (socket != null) {
219                 socket.close();
220             }
221             TrafficStats.setThreadStatsTag(oldTag);
222         }
223 
224         return true;
225     }
226 
227     /** Performs the NTP clock offset calculation. */
228     @VisibleForTesting
calculateClockOffset(Timestamp64 clientRequestTimestamp, Timestamp64 serverReceiveTimestamp, Timestamp64 serverTransmitTimestamp, Timestamp64 clientResponseTimestamp)229     public static Duration calculateClockOffset(Timestamp64 clientRequestTimestamp,
230             Timestamp64 serverReceiveTimestamp, Timestamp64 serverTransmitTimestamp,
231             Timestamp64 clientResponseTimestamp) {
232         // According to RFC4330:
233         // t is the system clock offset (the adjustment we are trying to find)
234         // t = ((T2 - T1) + (T3 - T4)) / 2
235         //
236         // Which is:
237         // t = (([server]receiveTimestamp - [client]requestTimestamp)
238         //       + ([server]transmitTimestamp - [client]responseTimestamp)) / 2
239         //
240         // See the NTP spec and tests: the numeric types used are deliberate:
241         // + Duration64.between() uses 64-bit arithmetic (32-bit for the seconds).
242         // + plus() / dividedBy() use Duration, which isn't the double precision floating point
243         //   used in NTPv4, but is good enough.
244         return Duration64.between(clientRequestTimestamp, serverReceiveTimestamp)
245                 .plus(Duration64.between(clientResponseTimestamp, serverTransmitTimestamp))
246                 .dividedBy(2);
247     }
248 
249     @Deprecated
250     @UnsupportedAppUsage
requestTime(String host, int timeout)251     public boolean requestTime(String host, int timeout) {
252         Log.w(TAG, "Shame on you for calling the hidden API requestTime()!");
253         return false;
254     }
255 
256     /**
257      * Returns the offset calculated to apply to the client clock to arrive at {@link #getNtpTime()}
258      */
259     @VisibleForTesting
getClockOffset()260     public long getClockOffset() {
261         return mClockOffset;
262     }
263 
264     /**
265      * Returns the time computed from the NTP transaction.
266      *
267      * @return time value computed from NTP server response.
268      */
269     @UnsupportedAppUsage
getNtpTime()270     public long getNtpTime() {
271         return mNtpTime;
272     }
273 
274     /**
275      * Returns the reference clock value (value of SystemClock.elapsedRealtime())
276      * corresponding to the NTP time.
277      *
278      * @return reference clock corresponding to the NTP time.
279      */
280     @UnsupportedAppUsage
getNtpTimeReference()281     public long getNtpTimeReference() {
282         return mNtpTimeReference;
283     }
284 
285     /**
286      * Returns the round trip time of the NTP transaction
287      *
288      * @return round trip time in milliseconds.
289      */
290     @UnsupportedAppUsage
getRoundTripTime()291     public long getRoundTripTime() {
292         return mRoundTripTime;
293     }
294 
295     /**
296      * Returns the address of the NTP server used in the NTP transaction
297      */
298     @Nullable
getServerSocketAddress()299     public InetSocketAddress getServerSocketAddress() {
300         return mServerSocketAddress;
301     }
302 
checkValidServerReply( byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp, Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp, Timestamp64 originateTimestamp)303     private static void checkValidServerReply(
304             byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp,
305             Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp,
306             Timestamp64 originateTimestamp) throws InvalidServerReplyException {
307         if (leap == NTP_LEAP_NOSYNC) {
308             throw new InvalidServerReplyException("unsynchronized server");
309         }
310         if ((mode != NTP_MODE_SERVER) && (mode != NTP_MODE_BROADCAST)) {
311             throw new InvalidServerReplyException("untrusted mode: " + mode);
312         }
313         if ((stratum == NTP_STRATUM_DEATH) || (stratum > NTP_STRATUM_MAX)) {
314             throw new InvalidServerReplyException("untrusted stratum: " + stratum);
315         }
316         if (!randomizedRequestTimestamp.equals(originateTimestamp)) {
317             throw new InvalidServerReplyException(
318                     "originateTimestamp != randomizedRequestTimestamp");
319         }
320         if (transmitTimestamp.equals(Timestamp64.ZERO)) {
321             throw new InvalidServerReplyException("zero transmitTimestamp");
322         }
323         if (referenceTimestamp.equals(Timestamp64.ZERO)) {
324             throw new InvalidServerReplyException("zero referenceTimestamp");
325         }
326     }
327 
328     /**
329      * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
330      */
readUnsigned32(byte[] buffer, int offset)331     private long readUnsigned32(byte[] buffer, int offset) {
332         int i0 = buffer[offset++] & 0xFF;
333         int i1 = buffer[offset++] & 0xFF;
334         int i2 = buffer[offset++] & 0xFF;
335         int i3 = buffer[offset] & 0xFF;
336 
337         int bits = (i0 << 24) | (i1 << 16) | (i2 << 8) | i3;
338         return bits & 0xFFFF_FFFFL;
339     }
340 
341     /**
342      * Reads the NTP time stamp from the given offset in the buffer.
343      */
readTimeStamp(byte[] buffer, int offset)344     private Timestamp64 readTimeStamp(byte[] buffer, int offset) {
345         long seconds = readUnsigned32(buffer, offset);
346         int fractionBits = (int) readUnsigned32(buffer, offset + 4);
347         return Timestamp64.fromComponents(seconds, fractionBits);
348     }
349 
350     /**
351      * Writes the NTP time stamp at the given offset in the buffer.
352      */
writeTimeStamp(byte[] buffer, int offset, Timestamp64 timestamp)353     private void writeTimeStamp(byte[] buffer, int offset, Timestamp64 timestamp) {
354         long seconds = timestamp.getEraSeconds();
355         // write seconds in big endian format
356         buffer[offset++] = (byte) (seconds >>> 24);
357         buffer[offset++] = (byte) (seconds >>> 16);
358         buffer[offset++] = (byte) (seconds >>> 8);
359         buffer[offset++] = (byte) (seconds);
360 
361         int fractionBits = timestamp.getFractionBits();
362         // write fraction in big endian format
363         buffer[offset++] = (byte) (fractionBits >>> 24);
364         buffer[offset++] = (byte) (fractionBits >>> 16);
365         buffer[offset++] = (byte) (fractionBits >>> 8);
366         buffer[offset] = (byte) (fractionBits);
367     }
368 
defaultRandom()369     private static Random defaultRandom() {
370         Random random;
371         try {
372             random = SecureRandom.getInstanceStrong();
373         } catch (NoSuchAlgorithmException e) {
374             // This should never happen.
375             Slog.wtf(TAG, "Unable to access SecureRandom", e);
376             random = new Random(System.currentTimeMillis());
377         }
378         return random;
379     }
380 }
381