--- a/src/java.base/share/classes/module-info.java Tue Jul 04 01:52:53 2017 +0000
+++ b/src/java.base/share/classes/module-info.java Sun Jul 23 10:33:13 2017 +0530
@@ -212,7 +212,8 @@
exports jdk.internal.util.jar to
jdk.jartool;
exports sun.net to
- jdk.incubator.httpclient;
+ jdk.incubator.httpclient,
+ jdk.naming.dns;
exports sun.net.ext to
jdk.net;
exports sun.net.dns to
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DNSDatagramSocketFactory.java Sun Jul 23 10:33:13 2017 +0530
@@ -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;
+ }
+
+}
--- a/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsClient.java Tue Jul 04 01:52:53 2017 +0000
+++ b/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsClient.java Sun Jul 23 10:33:13 2017 +0530
@@ -85,7 +85,9 @@
private int timeout; // initial timeout on UDP queries in ms
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
private Map<Integer, ResourceRecord> reqs;
@@ -105,14 +107,6 @@
throws NamingException {
this.timeout = timeout;
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];
serverPorts = new int[servers.length];
@@ -142,6 +136,16 @@
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")
protected void finalize() {
close();
@@ -151,7 +155,6 @@
private Object queuesLock = new Object();
public void close() {
- udpSocket.close();
synchronized (queuesLock) {
reqs.clear();
resps.clear();
@@ -393,43 +396,45 @@
int minTimeout = 50; // msec after which there are no retries.
- synchronized (udpSocket) {
- DatagramPacket opkt = new DatagramPacket(
- pkt.getData(), pkt.length(), server, port);
- DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
- // Packets may only be sent to or received from this server address
- udpSocket.connect(server, port);
- int pktTimeout = (timeout * (1 << retry));
- try {
- udpSocket.send(opkt);
+ synchronized (udpSocketLock) {
+ try (DatagramSocket udpSocket = getDatagramSocket()) {
+ DatagramPacket opkt = new DatagramPacket(
+ pkt.getData(), pkt.length(), server, port);
+ DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
+ // Packets may only be sent to or received from this server address
+ udpSocket.connect(server, port);
+ int pktTimeout = (timeout * (1 << retry));
+ try {
+ udpSocket.send(opkt);
- // timeout remaining after successive 'receive()'
- int timeoutLeft = pktTimeout;
- int cnt = 0;
- do {
- if (debug) {
- cnt++;
- dprint("Trying RECEIVE(" +
- cnt + ") retry(" + (retry + 1) +
- ") for:" + xid + " sock-timeout:" +
- timeoutLeft + " ms.");
- }
- udpSocket.setSoTimeout(timeoutLeft);
- long start = System.currentTimeMillis();
- udpSocket.receive(ipkt);
- long end = System.currentTimeMillis();
+ // timeout remaining after successive 'receive()'
+ int timeoutLeft = pktTimeout;
+ int cnt = 0;
+ do {
+ if (debug) {
+ cnt++;
+ dprint("Trying RECEIVE(" +
+ cnt + ") retry(" + (retry + 1) +
+ ") for:" + xid + " sock-timeout:" +
+ timeoutLeft + " ms.");
+ }
+ udpSocket.setSoTimeout(timeoutLeft);
+ long start = System.currentTimeMillis();
+ udpSocket.receive(ipkt);
+ long end = System.currentTimeMillis();
- byte[] data = ipkt.getData();
- if (isMatchResponse(data, xid)) {
- return data;
- }
- timeoutLeft = pktTimeout - ((int) (end - start));
- } while (timeoutLeft > minTimeout);
+ byte[] data = ipkt.getData();
+ if (isMatchResponse(data, xid)) {
+ return data;
+ }
+ timeoutLeft = pktTimeout - ((int) (end - start));
+ } while (timeoutLeft > minTimeout);
- } finally {
- udpSocket.disconnect();
+ } finally {
+ udpSocket.disconnect();
+ }
+ return null; // no matching packet received within the timeout
}
- return null; // no matching packet received within the timeout
}
}
--- a/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/ResourceRecord.java Tue Jul 04 01:52:53 2017 +0000
+++ b/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/ResourceRecord.java Sun Jul 23 10:33:13 2017 +0530
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -355,8 +355,19 @@
pos += typeAndLen;
} else if ((typeAndLen & 0xC0) == 0xC0) { // name compression
++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;
+ if (debug) {
+ dprint("decode: name compression at " + ppos
+ + " -> " + pos + " endPos=" + endPos);
+ assert endPos > 0;
+ assert pos < ppos;
+ assert pos >= Header.HEADER_SIZE;
+ }
} else
throw new IOException("Invalid label type: " + typeAndLen);
}
@@ -405,6 +416,11 @@
}
}
// 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];
System.arraycopy(msg, pos, rd, 0, rdlen);
return rd;
@@ -613,4 +629,15 @@
return sb.toString();
}
+
+ //-------------------------------------------------------------------------
+
+ private static final boolean debug = false;
+
+ private static void dprint(String mess) {
+ if (debug) {
+ System.err.println("DNS: " + mess);
+ }
+ }
+
}