src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2019, 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 jdk.dns.client.internal;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProtocolFamily;
+import java.net.SocketException;
+import java.nio.channels.DatagramChannel;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
+
+class DnsDatagramChannelFactory {
+ 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 = PortConfig.getLower();
+ static final int UPPER = 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;
+ final ReentrantLock factoryLock = new ReentrantLock();
+
+ DnsDatagramChannelFactory() {
+ this(new Random());
+ }
+
+ DnsDatagramChannelFactory(Random random) {
+ this(Objects.requireNonNull(random), null, DEVIATION, THRESHOLD);
+ }
+
+ DnsDatagramChannelFactory(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 DatagramChannel bound to a random port.
+ * @throws SocketException if the socket cannot be created.
+ */
+ public DatagramChannel open() throws SocketException {
+ factoryLock.lock();
+ try {
+ int lastseen = lastport;
+ DatagramChannel dc;
+
+ 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
+ dc = openRandom();
+ if (dc != null) return dc;
+
+ // couldn't allocate a random port: reset all counters and fall
+ // through.
+ unsuitablePortCount = 0;
+ suitablePortCount = 0;
+ lastseen = 0;
+ }
+
+ // Allocate an ephemeral port (port 0)
+ dc = openDefault();
+ lastport = dc.socket().getLocalPort();
+ if (lastseen == 0) {
+ history.offer(lastport);
+ return dc;
+ }
+
+ 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 dc;
+ }
+
+ // Undecided... the new port was too close. Let's allocate a random
+ // port using our own algorithm
+ assert !thresholdCrossed;
+ try {
+ dc.close();
+ } catch (IOException ioe) {
+ throw new SocketException(ioe.getMessage());
+ }
+ dc = openRandom();
+ unsuitablePortCount++;
+ return dc;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ private DatagramChannel openDefault() throws SocketException {
+ if (family != null) {
+ try {
+ DatagramChannel dc = DatagramChannel.open(family);
+ try {
+ dc.bind(null);
+ return dc;
+ } catch (Throwable x) {
+ dc.close();
+ throw x;
+ }
+ } catch (SocketException x) {
+ throw x;
+ } catch (IOException x) {
+ SocketException e = new SocketException(x.getMessage());
+ e.initCause(x);
+ throw e;
+ }
+ }
+ try {
+ return DatagramChannel.open();
+ } catch (IOException ioe) {
+ throw new SocketException(ioe.getMessage());
+ }
+ }
+
+ boolean isUsingNativePortRandomization() {
+ factoryLock.lock();
+ try {
+ return unsuitablePortCount <= thresholdCount
+ && suitablePortCount > thresholdCount;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ boolean isUsingJavaPortRandomization() {
+ factoryLock.lock();
+ try {
+ return unsuitablePortCount > thresholdCount;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ boolean isUndecided() {
+ factoryLock.lock();
+ try {
+ return !isUsingJavaPortRandomization()
+ && !isUsingNativePortRandomization();
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ private DatagramChannel openRandom() {
+ int maxtries = MAX_RANDOM_TRIES;
+ while (maxtries-- > 0) {
+ int port = EphemeralPortRange.LOWER
+ + random.nextInt(EphemeralPortRange.RANGE);
+ try {
+ if (family != null) {
+ DatagramChannel dc = DatagramChannel.open(family);
+ try {
+ dc.bind(new InetSocketAddress(port));
+ return dc;
+ } catch (Throwable x) {
+ dc.close();
+ throw x;
+ }
+ } else {
+
+ }
+ DatagramChannel dc = DatagramChannel.open(family);
+ return dc.bind(new InetSocketAddress(port));
+ } catch (IOException x) {
+ // try again until maxtries == 0;
+ }
+ }
+ return null;
+ }
+
+}