/* * 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 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; } }