8182125: Improve reliability of DNS lookups
Reviewed-by: chegar, rriggs, dfuchs
This commit is contained in:
parent
248948c08b
commit
5069e85826
@ -212,7 +212,8 @@ module java.base {
|
|||||||
exports jdk.internal.util.jar to
|
exports jdk.internal.util.jar to
|
||||||
jdk.jartool;
|
jdk.jartool;
|
||||||
exports sun.net to
|
exports sun.net to
|
||||||
jdk.incubator.httpclient;
|
jdk.incubator.httpclient,
|
||||||
|
jdk.naming.dns;
|
||||||
exports sun.net.ext to
|
exports sun.net.ext to
|
||||||
jdk.net;
|
jdk.net;
|
||||||
exports sun.net.dns to
|
exports sun.net.dns to
|
||||||
|
@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
package com.sun.jndi.dns;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.ProtocolFamily;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
class DNSDatagramSocketFactory {
|
||||||
|
static final int DEVIATION = 3;
|
||||||
|
static final int THRESHOLD = 6;
|
||||||
|
static final int BIT_DEVIATION = 2;
|
||||||
|
static final int HISTORY = 32;
|
||||||
|
static final int MAX_RANDOM_TRIES = 5;
|
||||||
|
/**
|
||||||
|
* The dynamic allocation port range (aka ephemeral ports), as configured
|
||||||
|
* on the system. Use nested class for lazy evaluation.
|
||||||
|
*/
|
||||||
|
static final class EphemeralPortRange {
|
||||||
|
private EphemeralPortRange() {}
|
||||||
|
static final int LOWER = sun.net.PortConfig.getLower();
|
||||||
|
static final int UPPER = sun.net.PortConfig.getUpper();
|
||||||
|
static final int RANGE = UPPER - LOWER + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records a subset of max {@code capacity} previously used ports
|
||||||
|
static final class PortHistory {
|
||||||
|
final int capacity;
|
||||||
|
final int[] ports;
|
||||||
|
final Random random;
|
||||||
|
int index;
|
||||||
|
PortHistory(int capacity, Random random) {
|
||||||
|
this.random = random;
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.ports = new int[capacity];
|
||||||
|
}
|
||||||
|
// returns true if the history contains the specified port.
|
||||||
|
public boolean contains(int port) {
|
||||||
|
int p = 0;
|
||||||
|
for (int i=0; i<capacity; i++) {
|
||||||
|
if ((p = ports[i]) == 0 || p == port) break;
|
||||||
|
}
|
||||||
|
return p == port;
|
||||||
|
}
|
||||||
|
// Adds the port to the history - doesn't check whether the port
|
||||||
|
// is already present. Always adds the port and always return true.
|
||||||
|
public boolean add(int port) {
|
||||||
|
if (ports[index] != 0) { // at max capacity
|
||||||
|
// remove one port at random and store the new port there
|
||||||
|
ports[random.nextInt(capacity)] = port;
|
||||||
|
} else { // there's a free slot
|
||||||
|
ports[index] = port;
|
||||||
|
}
|
||||||
|
if (++index == capacity) index = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Adds the port to the history if not already present.
|
||||||
|
// Return true if the port was added, false if the port was already
|
||||||
|
// present.
|
||||||
|
public boolean offer(int port) {
|
||||||
|
if (contains(port)) return false;
|
||||||
|
else return add(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastport = 0;
|
||||||
|
int suitablePortCount;
|
||||||
|
int unsuitablePortCount;
|
||||||
|
final ProtocolFamily family; // null (default) means dual stack
|
||||||
|
final int thresholdCount; // decision point
|
||||||
|
final int deviation;
|
||||||
|
final Random random;
|
||||||
|
final PortHistory history;
|
||||||
|
|
||||||
|
DNSDatagramSocketFactory() {
|
||||||
|
this(new Random());
|
||||||
|
}
|
||||||
|
|
||||||
|
DNSDatagramSocketFactory(Random random) {
|
||||||
|
this(Objects.requireNonNull(random), null, DEVIATION, THRESHOLD);
|
||||||
|
}
|
||||||
|
DNSDatagramSocketFactory(Random random,
|
||||||
|
ProtocolFamily family,
|
||||||
|
int deviation,
|
||||||
|
int threshold) {
|
||||||
|
this.random = Objects.requireNonNull(random);
|
||||||
|
this.history = new PortHistory(HISTORY, random);
|
||||||
|
this.family = family;
|
||||||
|
this.deviation = Math.max(1, deviation);
|
||||||
|
this.thresholdCount = Math.max(2, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a datagram socket listening to the wildcard address on a
|
||||||
|
* random port. If the underlying OS supports UDP port randomization
|
||||||
|
* out of the box (if binding a socket to port 0 binds it to a random
|
||||||
|
* port) then the underlying OS implementation is used. Otherwise, this
|
||||||
|
* method will allocate and bind a socket on a randomly selected ephemeral
|
||||||
|
* port in the dynamic range.
|
||||||
|
* @return A new DatagramSocket bound to a random port.
|
||||||
|
* @throws SocketException if the socket cannot be created.
|
||||||
|
*/
|
||||||
|
public synchronized DatagramSocket open() throws SocketException {
|
||||||
|
int lastseen = lastport;
|
||||||
|
DatagramSocket s;
|
||||||
|
|
||||||
|
boolean thresholdCrossed = unsuitablePortCount > thresholdCount;
|
||||||
|
if (thresholdCrossed) {
|
||||||
|
// Underlying stack does not support random UDP port out of the box.
|
||||||
|
// Use our own algorithm to allocate a random UDP port
|
||||||
|
s = openRandom();
|
||||||
|
if (s != null) return s;
|
||||||
|
|
||||||
|
// couldn't allocate a random port: reset all counters and fall
|
||||||
|
// through.
|
||||||
|
unsuitablePortCount = 0; suitablePortCount = 0; lastseen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate an ephemeral port (port 0)
|
||||||
|
s = openDefault();
|
||||||
|
lastport = s.getLocalPort();
|
||||||
|
if (lastseen == 0) {
|
||||||
|
history.offer(lastport);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
thresholdCrossed = suitablePortCount > thresholdCount;
|
||||||
|
boolean farEnough = Integer.bitCount(lastseen ^ lastport) > BIT_DEVIATION
|
||||||
|
&& Math.abs(lastport - lastseen) > deviation;
|
||||||
|
boolean recycled = history.contains(lastport);
|
||||||
|
boolean suitable = (thresholdCrossed || farEnough && !recycled);
|
||||||
|
if (suitable && !recycled) history.add(lastport);
|
||||||
|
|
||||||
|
if (suitable) {
|
||||||
|
if (!thresholdCrossed) {
|
||||||
|
suitablePortCount++;
|
||||||
|
} else if (!farEnough || recycled) {
|
||||||
|
unsuitablePortCount = 1;
|
||||||
|
suitablePortCount = thresholdCount/2;
|
||||||
|
}
|
||||||
|
// Either the underlying stack supports random UDP port allocation,
|
||||||
|
// or the new port is sufficiently distant from last port to make
|
||||||
|
// it look like it is. Let's use it.
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undecided... the new port was too close. Let's allocate a random
|
||||||
|
// port using our own algorithm
|
||||||
|
assert !thresholdCrossed;
|
||||||
|
DatagramSocket ss = openRandom();
|
||||||
|
if (ss == null) return s;
|
||||||
|
unsuitablePortCount++;
|
||||||
|
s.close();
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatagramSocket openDefault() throws SocketException {
|
||||||
|
if (family != null) {
|
||||||
|
try {
|
||||||
|
DatagramChannel c = DatagramChannel.open(family);
|
||||||
|
try {
|
||||||
|
DatagramSocket s = c.socket();
|
||||||
|
s.bind(null);
|
||||||
|
return s;
|
||||||
|
} catch (Throwable x) {
|
||||||
|
c.close();
|
||||||
|
throw x;
|
||||||
|
}
|
||||||
|
} catch (SocketException x) {
|
||||||
|
throw x;
|
||||||
|
} catch (IOException x) {
|
||||||
|
SocketException e = new SocketException(x.getMessage());
|
||||||
|
e.initCause(x);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DatagramSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isUsingNativePortRandomization() {
|
||||||
|
return unsuitablePortCount <= thresholdCount
|
||||||
|
&& suitablePortCount > thresholdCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isUsingJavaPortRandomization() {
|
||||||
|
return unsuitablePortCount > thresholdCount ;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isUndecided() {
|
||||||
|
return !isUsingJavaPortRandomization()
|
||||||
|
&& !isUsingNativePortRandomization();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatagramSocket openRandom() {
|
||||||
|
int maxtries = MAX_RANDOM_TRIES;
|
||||||
|
while (maxtries-- > 0) {
|
||||||
|
int port = EphemeralPortRange.LOWER
|
||||||
|
+ random.nextInt(EphemeralPortRange.RANGE);
|
||||||
|
try {
|
||||||
|
if (family != null) {
|
||||||
|
DatagramChannel c = DatagramChannel.open(family);
|
||||||
|
try {
|
||||||
|
DatagramSocket s = c.socket();
|
||||||
|
s.bind(new InetSocketAddress(port));
|
||||||
|
return s;
|
||||||
|
} catch (Throwable x) {
|
||||||
|
c.close();
|
||||||
|
throw x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DatagramSocket(port);
|
||||||
|
} catch (IOException x) {
|
||||||
|
// try again until maxtries == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -85,7 +85,9 @@ public class DnsClient {
|
|||||||
private int timeout; // initial timeout on UDP queries in ms
|
private int timeout; // initial timeout on UDP queries in ms
|
||||||
private int retries; // number of UDP retries
|
private int retries; // number of UDP retries
|
||||||
|
|
||||||
private DatagramSocket udpSocket;
|
private final Object udpSocketLock = new Object();
|
||||||
|
private static final DNSDatagramSocketFactory factory =
|
||||||
|
new DNSDatagramSocketFactory(random);
|
||||||
|
|
||||||
// Requests sent
|
// Requests sent
|
||||||
private Map<Integer, ResourceRecord> reqs;
|
private Map<Integer, ResourceRecord> reqs;
|
||||||
@ -105,14 +107,6 @@ public class DnsClient {
|
|||||||
throws NamingException {
|
throws NamingException {
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.retries = retries;
|
this.retries = retries;
|
||||||
try {
|
|
||||||
udpSocket = new DatagramSocket();
|
|
||||||
} catch (java.net.SocketException e) {
|
|
||||||
NamingException ne = new ConfigurationException();
|
|
||||||
ne.setRootCause(e);
|
|
||||||
throw ne;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.servers = new InetAddress[servers.length];
|
this.servers = new InetAddress[servers.length];
|
||||||
serverPorts = new int[servers.length];
|
serverPorts = new int[servers.length];
|
||||||
|
|
||||||
@ -142,6 +136,16 @@ public class DnsClient {
|
|||||||
resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());
|
resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatagramSocket getDatagramSocket() throws NamingException {
|
||||||
|
try {
|
||||||
|
return factory.open();
|
||||||
|
} catch (java.net.SocketException e) {
|
||||||
|
NamingException ne = new ConfigurationException();
|
||||||
|
ne.setRootCause(e);
|
||||||
|
throw ne;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
protected void finalize() {
|
protected void finalize() {
|
||||||
close();
|
close();
|
||||||
@ -151,7 +155,6 @@ public class DnsClient {
|
|||||||
private Object queuesLock = new Object();
|
private Object queuesLock = new Object();
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
udpSocket.close();
|
|
||||||
synchronized (queuesLock) {
|
synchronized (queuesLock) {
|
||||||
reqs.clear();
|
reqs.clear();
|
||||||
resps.clear();
|
resps.clear();
|
||||||
@ -393,43 +396,45 @@ public class DnsClient {
|
|||||||
|
|
||||||
int minTimeout = 50; // msec after which there are no retries.
|
int minTimeout = 50; // msec after which there are no retries.
|
||||||
|
|
||||||
synchronized (udpSocket) {
|
synchronized (udpSocketLock) {
|
||||||
DatagramPacket opkt = new DatagramPacket(
|
try (DatagramSocket udpSocket = getDatagramSocket()) {
|
||||||
pkt.getData(), pkt.length(), server, port);
|
DatagramPacket opkt = new DatagramPacket(
|
||||||
DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
|
pkt.getData(), pkt.length(), server, port);
|
||||||
// Packets may only be sent to or received from this server address
|
DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
|
||||||
udpSocket.connect(server, port);
|
// Packets may only be sent to or received from this server address
|
||||||
int pktTimeout = (timeout * (1 << retry));
|
udpSocket.connect(server, port);
|
||||||
try {
|
int pktTimeout = (timeout * (1 << retry));
|
||||||
udpSocket.send(opkt);
|
try {
|
||||||
|
udpSocket.send(opkt);
|
||||||
|
|
||||||
// timeout remaining after successive 'receive()'
|
// timeout remaining after successive 'receive()'
|
||||||
int timeoutLeft = pktTimeout;
|
int timeoutLeft = pktTimeout;
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
do {
|
do {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
cnt++;
|
cnt++;
|
||||||
dprint("Trying RECEIVE(" +
|
dprint("Trying RECEIVE(" +
|
||||||
cnt + ") retry(" + (retry + 1) +
|
cnt + ") retry(" + (retry + 1) +
|
||||||
") for:" + xid + " sock-timeout:" +
|
") for:" + xid + " sock-timeout:" +
|
||||||
timeoutLeft + " ms.");
|
timeoutLeft + " ms.");
|
||||||
}
|
}
|
||||||
udpSocket.setSoTimeout(timeoutLeft);
|
udpSocket.setSoTimeout(timeoutLeft);
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
udpSocket.receive(ipkt);
|
udpSocket.receive(ipkt);
|
||||||
long end = System.currentTimeMillis();
|
long end = System.currentTimeMillis();
|
||||||
|
|
||||||
byte[] data = ipkt.getData();
|
byte[] data = ipkt.getData();
|
||||||
if (isMatchResponse(data, xid)) {
|
if (isMatchResponse(data, xid)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
timeoutLeft = pktTimeout - ((int) (end - start));
|
timeoutLeft = pktTimeout - ((int) (end - start));
|
||||||
} while (timeoutLeft > minTimeout);
|
} while (timeoutLeft > minTimeout);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
udpSocket.disconnect();
|
udpSocket.disconnect();
|
||||||
|
}
|
||||||
|
return null; // no matching packet received within the timeout
|
||||||
}
|
}
|
||||||
return null; // no matching packet received within the timeout
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -355,8 +355,19 @@ public class ResourceRecord {
|
|||||||
pos += typeAndLen;
|
pos += typeAndLen;
|
||||||
} else if ((typeAndLen & 0xC0) == 0xC0) { // name compression
|
} else if ((typeAndLen & 0xC0) == 0xC0) { // name compression
|
||||||
++level;
|
++level;
|
||||||
endPos = pos + 2;
|
// cater for the case where the name pointed to is itself
|
||||||
|
// compressed: we don't want endPos to be reset by the second
|
||||||
|
// compression level.
|
||||||
|
int ppos = pos;
|
||||||
|
if (endPos == -1) endPos = pos + 2;
|
||||||
pos = getUShort(pos) & 0x3FFF;
|
pos = getUShort(pos) & 0x3FFF;
|
||||||
|
if (debug) {
|
||||||
|
dprint("decode: name compression at " + ppos
|
||||||
|
+ " -> " + pos + " endPos=" + endPos);
|
||||||
|
assert endPos > 0;
|
||||||
|
assert pos < ppos;
|
||||||
|
assert pos >= Header.HEADER_SIZE;
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
throw new IOException("Invalid label type: " + typeAndLen);
|
throw new IOException("Invalid label type: " + typeAndLen);
|
||||||
}
|
}
|
||||||
@ -405,6 +416,11 @@ public class ResourceRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unknown RR type/class
|
// Unknown RR type/class
|
||||||
|
if (debug) {
|
||||||
|
dprint("Unknown RR type for RR data: " + rrtype + " rdlen=" + rdlen
|
||||||
|
+ ", pos=" + pos +", msglen=" + msg.length + ", remaining="
|
||||||
|
+ (msg.length-pos));
|
||||||
|
}
|
||||||
byte[] rd = new byte[rdlen];
|
byte[] rd = new byte[rdlen];
|
||||||
System.arraycopy(msg, pos, rd, 0, rdlen);
|
System.arraycopy(msg, pos, rd, 0, rdlen);
|
||||||
return rd;
|
return rd;
|
||||||
@ -613,4 +629,15 @@ public class ResourceRecord {
|
|||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static final boolean debug = false;
|
||||||
|
|
||||||
|
private static void dprint(String mess) {
|
||||||
|
if (debug) {
|
||||||
|
System.err.println("DNS: " + mess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user