1
1
/*
2
- * Copyright (c) 2000, 2023 , Oracle and/or its affiliates. All rights reserved.
2
+ * Copyright (c) 2000, 2024 , Oracle and/or its affiliates. All rights reserved.
3
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
4
*
5
5
* This code is free software; you can redistribute it and/or modify it
46
46
import javax .naming .OperationNotSupportedException ;
47
47
import javax .naming .ServiceUnavailableException ;
48
48
49
+ import java .time .Duration ;
49
50
import java .util .Arrays ;
50
51
import java .util .Collections ;
51
52
import java .util .Map ;
52
53
import java .util .HashMap ;
54
+ import java .util .concurrent .TimeUnit ;
55
+ import java .util .concurrent .atomic .AtomicLong ;
53
56
import java .util .concurrent .locks .ReentrantLock ;
57
+ import java .util .stream .IntStream ;
54
58
55
59
import jdk .internal .ref .CleanerFactory ;
56
60
import sun .security .jca .JCAUtil ;
@@ -95,7 +99,7 @@ public class DnsClient {
95
99
96
100
private static final int DEFAULT_PORT = 53 ;
97
101
private static final int TRANSACTION_ID_BOUND = 0x10000 ;
98
- private static final int MIN_TIMEOUT = 50 ; // msec after which there are no retries.
102
+ private static final int MIN_TIMEOUT = 0 ; // msec after which there are no retries.
99
103
private static final SecureRandom random = JCAUtil .getSecureRandom ();
100
104
private InetAddress [] servers ;
101
105
private int [] serverPorts ;
@@ -223,20 +227,28 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
223
227
224
228
Exception caughtException = null ;
225
229
boolean [] doNotRetry = new boolean [servers .length ];
226
-
230
+ // Holder for unfulfilled timeouts left for each server
231
+ AtomicLong [] unfulfilledUdpTimeouts = IntStream .range (0 , servers .length )
232
+ .mapToObj (_ -> new AtomicLong ())
233
+ .toArray (AtomicLong []::new );
227
234
try {
228
235
//
229
236
// The UDP retry strategy is to try the 1st server, and then
230
237
// each server in order. If no answer, double the timeout
231
238
// and try each server again.
232
239
//
233
- for (int retry = 0 ; retry < retries ; retry ++) {
234
-
240
+ for (int retry = 0 ; retry <= retries ; retry ++) {
241
+ boolean isLastRetry = retry == retries ;
235
242
// Try each name server.
236
243
for (int i = 0 ; i < servers .length ; i ++) {
237
244
if (doNotRetry [i ]) {
238
245
continue ;
239
246
}
247
+ // unfulfilledServerTimeout is always >= 0
248
+ AtomicLong unfulfilledServerTimeout = unfulfilledUdpTimeouts [i ];
249
+ if (isLastRetry && unfulfilledServerTimeout .get () == 0 ) {
250
+ continue ;
251
+ }
240
252
241
253
// send the request packet and wait for a response.
242
254
try {
@@ -245,7 +257,7 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
245
257
}
246
258
247
259
byte [] msg = doUdpQuery (pkt , servers [i ], serverPorts [i ],
248
- retry , xid );
260
+ retry , xid , unfulfilledServerTimeout , isLastRetry );
249
261
assert msg != null ;
250
262
Header hdr = new Header (msg , msg .length );
251
263
@@ -259,7 +271,12 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
259
271
260
272
// Try each server, starting with the one that just
261
273
// provided the truncated message.
262
- int retryTimeout = (timeout * (1 << retry ));
274
+ long retryTimeout = Math .clamp (
275
+ timeout * (1L << (isLastRetry
276
+ ? retry - 1
277
+ : retry )),
278
+ 0L , Integer .MAX_VALUE );
279
+ ;
263
280
for (int j = 0 ; j < servers .length ; j ++) {
264
281
int ij = (i + j ) % servers .length ;
265
282
if (doNotRetry [ij ]) {
@@ -301,15 +318,23 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
301
318
if (debug ) {
302
319
dprint ("Caught Exception:" + ex );
303
320
}
304
- if (caughtException == null ) {
321
+ if (caughtException == null || servers .length == 1 ) {
322
+ // If there are several servers we continue trying with other
323
+ // servers, otherwise this exception will be reported
305
324
caughtException = ex ;
325
+ } else {
326
+ // Best reporting effort
327
+ caughtException .addSuppressed (ex );
306
328
}
307
329
doNotRetry [i ] = true ;
308
330
} catch (IOException e ) {
309
331
if (debug ) {
310
332
dprint ("Caught IOException:" + e );
311
333
}
312
- if (caughtException == null ) {
334
+ if (caughtException instanceof CommunicationException ce ) {
335
+ e .addSuppressed (ce );
336
+ caughtException = e ;
337
+ } else if (caughtException == null ) {
313
338
caughtException = e ;
314
339
}
315
340
} catch (ClosedSelectorException e ) {
@@ -327,8 +352,13 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
327
352
caughtException = e ;
328
353
}
329
354
} catch (NamingException e ) {
330
- if (caughtException == null ) {
355
+ if (caughtException == null || servers .length == 1 ) {
356
+ // If there are several servers we continue trying with other
357
+ // servers, otherwise this exception will be reported
331
358
caughtException = e ;
359
+ } else {
360
+ // Best reporting effort
361
+ caughtException .addSuppressed (e );
332
362
}
333
363
doNotRetry [i ] = true ;
334
364
}
@@ -339,8 +369,8 @@ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
339
369
reqs .remove (xid ); // cleanup
340
370
}
341
371
342
- if (caughtException instanceof NamingException ) {
343
- throw ( NamingException ) caughtException ;
372
+ if (caughtException instanceof NamingException ne ) {
373
+ throw ne ;
344
374
}
345
375
// A network timeout or other error occurred.
346
376
NamingException ne = new CommunicationException ("DNS error" );
@@ -424,10 +454,32 @@ ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
424
454
* is enqueued with the corresponding xid in 'resps'.
425
455
*/
426
456
private byte [] doUdpQuery (Packet pkt , InetAddress server ,
427
- int port , int retry , int xid )
457
+ int port , int retry , int xid ,
458
+ AtomicLong unfulfilledTimeout ,
459
+ boolean unfulfilledOnly )
428
460
throws IOException , NamingException {
429
461
430
462
udpChannelLock .lock ();
463
+
464
+
465
+ // use 1L below to ensure conversion to long and avoid potential
466
+ // integer overflow (timeout is an int).
467
+ // no point in supporting timeout > Integer.MAX_VALUE, clamp if needed
468
+ // timeout remaining after successive 'blockingReceive()'.
469
+ long thisIterationTimeout = unfulfilledOnly
470
+ ? 0L
471
+ : Math .clamp (timeout * (1L << retry ), 0L , Integer .MAX_VALUE );
472
+
473
+ // Compensate with server's positive unfulfilled timeout.
474
+ // Calling method never supplies zero 'unfulfilledTimeout' when
475
+ // 'unfulfilledOnly' is 'true', therefore 'thisIterationTimeout'
476
+ // will always be a positive number, ie infinite timeout
477
+ // is not possible.
478
+ thisIterationTimeout += unfulfilledTimeout .get ();
479
+
480
+ // Track left timeout for the current retry
481
+ long timeoutLeft = thisIterationTimeout ;
482
+ long start = 0 ;
431
483
try {
432
484
try (DatagramChannel udpChannel = getDatagramChannel ()) {
433
485
ByteBuffer opkt = ByteBuffer .wrap (pkt .getData (), 0 , pkt .length ());
@@ -436,13 +488,11 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
436
488
// Packets may only be sent to or received from this server address
437
489
InetSocketAddress target = new InetSocketAddress (server , port );
438
490
udpChannel .connect (target );
439
- int pktTimeout = (timeout * (1 << retry ));
440
491
udpChannel .write (opkt );
441
492
442
- // timeout remaining after successive 'blockingReceive()'
443
- int timeoutLeft = pktTimeout ;
444
493
int cnt = 0 ;
445
494
boolean gotData = false ;
495
+ start = System .nanoTime ();
446
496
do {
447
497
// prepare for retry
448
498
if (gotData ) {
@@ -456,9 +506,7 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
456
506
") for:" + xid + " sock-timeout:" +
457
507
timeoutLeft + " ms." );
458
508
}
459
- long start = System .currentTimeMillis ();
460
- gotData = blockingReceive (udpChannel , ipkt , timeoutLeft );
461
- long end = System .currentTimeMillis ();
509
+ gotData = blockingReceive (udpChannel , target , ipkt , timeoutLeft );
462
510
assert gotData || ipkt .position () == 0 ;
463
511
if (gotData && isMatchResponse (data , xid )) {
464
512
return data ;
@@ -471,17 +519,23 @@ private byte[] doUdpQuery(Packet pkt, InetAddress server,
471
519
return cachedMsg ;
472
520
}
473
521
}
474
- timeoutLeft = pktTimeout - ((int ) (end - start ));
522
+ long elapsedMillis = TimeUnit .NANOSECONDS
523
+ .toMillis (System .nanoTime () - start );
524
+ timeoutLeft = thisIterationTimeout - elapsedMillis ;
475
525
} while (timeoutLeft > MIN_TIMEOUT );
476
526
// no matching packets received within the timeout
477
527
throw new SocketTimeoutException ();
478
528
}
479
529
} finally {
530
+ long carryoverTimeout = thisIterationTimeout -
531
+ TimeUnit .NANOSECONDS .toMillis (System .nanoTime () - start );
532
+ unfulfilledTimeout .set (Math .max (0 , carryoverTimeout ));
480
533
udpChannelLock .unlock ();
481
534
}
482
535
}
483
536
484
- boolean blockingReceive (DatagramChannel dc , ByteBuffer buffer , long timeout ) throws IOException {
537
+ boolean blockingReceive (DatagramChannel dc , InetSocketAddress target ,
538
+ ByteBuffer buffer , long timeout ) throws IOException {
485
539
boolean dataReceived = false ;
486
540
// The provided datagram channel will be used by the caller only to receive data after
487
541
// it is put to non-blocking mode
@@ -491,10 +545,15 @@ boolean blockingReceive(DatagramChannel dc, ByteBuffer buffer, long timeout) thr
491
545
udpChannelSelector .select (timeout );
492
546
var keys = udpChannelSelector .selectedKeys ();
493
547
if (keys .contains (selectionKey ) && selectionKey .isReadable ()) {
494
- dc .receive (buffer );
495
- dataReceived = true ;
548
+ int before = buffer .position ();
549
+ var senderAddress = dc .receive (buffer );
550
+ // Empty packets are ignored
551
+ dataReceived = target .equals (senderAddress ) && buffer .position () > before ;
552
+ }
553
+ // Avoid contention with Selector.close() if called by a clean-up thread
554
+ synchronized (keys ) {
555
+ keys .clear ();
496
556
}
497
- keys .clear ();
498
557
} finally {
499
558
selectionKey .cancel ();
500
559
// Flush the canceled key out of the selected key set
@@ -750,14 +809,19 @@ class Tcp {
750
809
private final Socket sock ;
751
810
private final java .io .InputStream in ;
752
811
final java .io .OutputStream out ;
753
- private int timeoutLeft ;
812
+ private long timeoutLeft ;
754
813
755
- Tcp (InetAddress server , int port , int timeout ) throws IOException {
814
+ Tcp (InetAddress server , int port , long timeout ) throws IOException {
756
815
sock = new Socket ();
757
816
try {
758
- long start = System .currentTimeMillis ();
759
- sock .connect (new InetSocketAddress (server , port ), timeout );
760
- timeoutLeft = (int ) (timeout - (System .currentTimeMillis () - start ));
817
+ long start = System .nanoTime ();
818
+ // It is safe to cast to int since the value is
819
+ // clamped by the caller
820
+ int intTimeout = (int ) timeout ;
821
+ sock .connect (new InetSocketAddress (server , port ), intTimeout );
822
+ timeoutLeft = Duration .ofMillis (timeout )
823
+ .minus (Duration .ofNanos ((System .nanoTime () - start )))
824
+ .toMillis ();
761
825
if (timeoutLeft <= 0 )
762
826
throw new SocketTimeoutException ();
763
827
@@ -785,14 +849,16 @@ private interface SocketReadOp {
785
849
private int readWithTimeout (SocketReadOp reader ) throws IOException {
786
850
if (timeoutLeft <= 0 )
787
851
throw new SocketTimeoutException ();
788
-
789
- sock .setSoTimeout (timeoutLeft );
790
- long start = System .currentTimeMillis ();
852
+ // It is safe to cast to int since the value is clamped
853
+ int intTimeout = (int ) timeoutLeft ;
854
+ sock .setSoTimeout (intTimeout );
855
+ long start = System .nanoTime ();
791
856
try {
792
857
return reader .read ();
793
858
}
794
859
finally {
795
- timeoutLeft -= (int ) (System .currentTimeMillis () - start );
860
+ timeoutLeft -= TimeUnit .NANOSECONDS .toMillis (
861
+ System .nanoTime () - start );
796
862
}
797
863
}
798
864
0 commit comments