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