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