1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.jndi.dns; 26 27 import java.io.IOException; 28 import java.net.DatagramSocket; 29 import java.net.ProtocolFamily; 30 import java.net.SocketException; 31 import java.net.InetSocketAddress; 32 import java.nio.channels.DatagramChannel; 33 import java.util.Objects; 34 import java.util.Random; 35 36 class DNSDatagramSocketFactory { 37 static final int DEVIATION = 3; 38 static final int THRESHOLD = 6; 39 static final int BIT_DEVIATION = 2; 40 static final int HISTORY = 32; 41 static final int MAX_RANDOM_TRIES = 5; 42 /** 43 * The dynamic allocation port range (aka ephemeral ports), as configured 44 * on the system. Use nested class for lazy evaluation. 45 */ 46 static final class EphemeralPortRange { EphemeralPortRange()47 private EphemeralPortRange() {} 48 static final int LOWER = sun.net.PortConfig.getLower(); 49 static final int UPPER = sun.net.PortConfig.getUpper(); 50 static final int RANGE = UPPER - LOWER + 1; 51 } 52 53 // Records a subset of max {@code capacity} previously used ports 54 static final class PortHistory { 55 final int capacity; 56 final int[] ports; 57 final Random random; 58 int index; PortHistory(int capacity, Random random)59 PortHistory(int capacity, Random random) { 60 this.random = random; 61 this.capacity = capacity; 62 this.ports = new int[capacity]; 63 } 64 // returns true if the history contains the specified port. contains(int port)65 public boolean contains(int port) { 66 int p = 0; 67 for (int i=0; i<capacity; i++) { 68 if ((p = ports[i]) == 0 || p == port) break; 69 } 70 return p == port; 71 } 72 // Adds the port to the history - doesn't check whether the port 73 // is already present. Always adds the port and always return true. add(int port)74 public boolean add(int port) { 75 if (ports[index] != 0) { // at max capacity 76 // remove one port at random and store the new port there 77 ports[random.nextInt(capacity)] = port; 78 } else { // there's a free slot 79 ports[index] = port; 80 } 81 if (++index == capacity) index = 0; 82 return true; 83 } 84 // Adds the port to the history if not already present. 85 // Return true if the port was added, false if the port was already 86 // present. offer(int port)87 public boolean offer(int port) { 88 if (contains(port)) return false; 89 else return add(port); 90 } 91 } 92 93 int lastport = 0; 94 int suitablePortCount; 95 int unsuitablePortCount; 96 final ProtocolFamily family; // null (default) means dual stack 97 final int thresholdCount; // decision point 98 final int deviation; 99 final Random random; 100 final PortHistory history; 101 DNSDatagramSocketFactory()102 DNSDatagramSocketFactory() { 103 this(new Random()); 104 } 105 DNSDatagramSocketFactory(Random random)106 DNSDatagramSocketFactory(Random random) { 107 this(Objects.requireNonNull(random), null, DEVIATION, THRESHOLD); 108 } DNSDatagramSocketFactory(Random random, ProtocolFamily family, int deviation, int threshold)109 DNSDatagramSocketFactory(Random random, 110 ProtocolFamily family, 111 int deviation, 112 int threshold) { 113 this.random = Objects.requireNonNull(random); 114 this.history = new PortHistory(HISTORY, random); 115 this.family = family; 116 this.deviation = Math.max(1, deviation); 117 this.thresholdCount = Math.max(2, threshold); 118 } 119 120 /** 121 * Opens a datagram socket listening to the wildcard address on a 122 * random port. If the underlying OS supports UDP port randomization 123 * out of the box (if binding a socket to port 0 binds it to a random 124 * port) then the underlying OS implementation is used. Otherwise, this 125 * method will allocate and bind a socket on a randomly selected ephemeral 126 * port in the dynamic range. 127 * @return A new DatagramSocket bound to a random port. 128 * @throws SocketException if the socket cannot be created. 129 */ open()130 public synchronized DatagramSocket open() throws SocketException { 131 int lastseen = lastport; 132 DatagramSocket s; 133 134 boolean thresholdCrossed = unsuitablePortCount > thresholdCount; 135 if (thresholdCrossed) { 136 // Underlying stack does not support random UDP port out of the box. 137 // Use our own algorithm to allocate a random UDP port 138 s = openRandom(); 139 if (s != null) return s; 140 141 // couldn't allocate a random port: reset all counters and fall 142 // through. 143 unsuitablePortCount = 0; suitablePortCount = 0; lastseen = 0; 144 } 145 146 // Allocate an ephemeral port (port 0) 147 s = openDefault(); 148 lastport = s.getLocalPort(); 149 if (lastseen == 0) { 150 history.offer(lastport); 151 return s; 152 } 153 154 thresholdCrossed = suitablePortCount > thresholdCount; 155 boolean farEnough = Integer.bitCount(lastseen ^ lastport) > BIT_DEVIATION 156 && Math.abs(lastport - lastseen) > deviation; 157 boolean recycled = history.contains(lastport); 158 boolean suitable = (thresholdCrossed || farEnough && !recycled); 159 if (suitable && !recycled) history.add(lastport); 160 161 if (suitable) { 162 if (!thresholdCrossed) { 163 suitablePortCount++; 164 } else if (!farEnough || recycled) { 165 unsuitablePortCount = 1; 166 suitablePortCount = thresholdCount/2; 167 } 168 // Either the underlying stack supports random UDP port allocation, 169 // or the new port is sufficiently distant from last port to make 170 // it look like it is. Let's use it. 171 return s; 172 } 173 174 // Undecided... the new port was too close. Let's allocate a random 175 // port using our own algorithm 176 assert !thresholdCrossed; 177 DatagramSocket ss = openRandom(); 178 if (ss == null) return s; 179 unsuitablePortCount++; 180 s.close(); 181 return ss; 182 } 183 openDefault()184 private DatagramSocket openDefault() throws SocketException { 185 if (family != null) { 186 try { 187 DatagramChannel c = DatagramChannel.open(family); 188 try { 189 DatagramSocket s = c.socket(); 190 s.bind(null); 191 return s; 192 } catch (Throwable x) { 193 c.close(); 194 throw x; 195 } 196 } catch (SocketException x) { 197 throw x; 198 } catch (IOException x) { 199 SocketException e = new SocketException(x.getMessage()); 200 e.initCause(x); 201 throw e; 202 } 203 } 204 return new DatagramSocket(); 205 } 206 isUsingNativePortRandomization()207 synchronized boolean isUsingNativePortRandomization() { 208 return unsuitablePortCount <= thresholdCount 209 && suitablePortCount > thresholdCount; 210 } 211 isUsingJavaPortRandomization()212 synchronized boolean isUsingJavaPortRandomization() { 213 return unsuitablePortCount > thresholdCount ; 214 } 215 isUndecided()216 synchronized boolean isUndecided() { 217 return !isUsingJavaPortRandomization() 218 && !isUsingNativePortRandomization(); 219 } 220 openRandom()221 private DatagramSocket openRandom() { 222 int maxtries = MAX_RANDOM_TRIES; 223 while (maxtries-- > 0) { 224 int port = EphemeralPortRange.LOWER 225 + random.nextInt(EphemeralPortRange.RANGE); 226 try { 227 if (family != null) { 228 DatagramChannel c = DatagramChannel.open(family); 229 try { 230 DatagramSocket s = c.socket(); 231 s.bind(new InetSocketAddress(port)); 232 return s; 233 } catch (Throwable x) { 234 c.close(); 235 throw x; 236 } 237 } 238 return new DatagramSocket(port); 239 } catch (IOException x) { 240 // try again until maxtries == 0; 241 } 242 } 243 return null; 244 } 245 246 } 247