11/*
2- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
3030import java .net .SocketException ;
3131import java .net .InetSocketAddress ;
3232import java .nio .channels .DatagramChannel ;
33+ import java .security .AccessController ;
34+ import java .security .PrivilegedExceptionAction ;
3335import java .util .Objects ;
3436import java .util .Random ;
3537
@@ -50,6 +52,21 @@ private EphemeralPortRange() {}
5052 static final int RANGE = UPPER - LOWER + 1 ;
5153 }
5254
55+ private static int findFirstFreePort () {
56+ PrivilegedExceptionAction <DatagramSocket > action = () -> new DatagramSocket (0 );
57+ int port ;
58+ try {
59+ @ SuppressWarnings ({"deprecated" , "removal" })
60+ DatagramSocket ds = AccessController .doPrivileged (action );
61+ try (DatagramSocket ds1 = ds ) {
62+ port = ds1 .getLocalPort ();
63+ }
64+ } catch (Exception x ) {
65+ port = 0 ;
66+ }
67+ return port ;
68+ }
69+
5370 // Records a subset of max {@code capacity} previously used ports
5471 static final class PortHistory {
5572 final int capacity ;
@@ -74,7 +91,10 @@ public boolean contains(int port) {
7491 public boolean add (int port ) {
7592 if (ports [index ] != 0 ) { // at max capacity
7693 // remove one port at random and store the new port there
77- ports [random .nextInt (capacity )] = port ;
94+ // don't remove the last port
95+ int remove = random .nextInt (capacity );
96+ if ((remove +1 ) % capacity == index ) remove = index ;
97+ ports [index = remove ] = port ;
7898 } else { // there's a free slot
7999 ports [index ] = port ;
80100 }
@@ -90,7 +110,8 @@ public boolean offer(int port) {
90110 }
91111 }
92112
93- int lastport = 0 ;
113+ int lastport = findFirstFreePort ();
114+ int lastSystemAllocated = lastport ;
94115 int suitablePortCount ;
95116 int unsuitablePortCount ;
96117 final ProtocolFamily family ; // null (default) means dual stack
@@ -147,13 +168,16 @@ public synchronized DatagramSocket open() throws SocketException {
147168 s = openDefault ();
148169 lastport = s .getLocalPort ();
149170 if (lastseen == 0 ) {
171+ lastSystemAllocated = lastport ;
150172 history .offer (lastport );
151173 return s ;
152174 }
153175
154176 thresholdCrossed = suitablePortCount > thresholdCount ;
155- boolean farEnough = Integer .bitCount (lastseen ^ lastport ) > BIT_DEVIATION
156- && Math .abs (lastport - lastseen ) > deviation ;
177+ boolean farEnough = farEnough (lastseen );
178+ if (farEnough && lastSystemAllocated > 0 ) {
179+ farEnough = farEnough (lastSystemAllocated );
180+ }
157181 boolean recycled = history .contains (lastport );
158182 boolean suitable = (thresholdCrossed || farEnough && !recycled );
159183 if (suitable && !recycled ) history .add (lastport );
@@ -168,6 +192,7 @@ public synchronized DatagramSocket open() throws SocketException {
168192 // Either the underlying stack supports random UDP port allocation,
169193 // or the new port is sufficiently distant from last port to make
170194 // it look like it is. Let's use it.
195+ lastSystemAllocated = lastport ;
171196 return s ;
172197 }
173198
@@ -218,24 +243,48 @@ synchronized boolean isUndecided() {
218243 && !isUsingNativePortRandomization ();
219244 }
220245
246+ private boolean farEnough (int port ) {
247+ return Integer .bitCount (port ^ lastport ) > BIT_DEVIATION
248+ && Math .abs (port - lastport ) > deviation ;
249+ }
250+
221251 private DatagramSocket openRandom () {
222252 int maxtries = MAX_RANDOM_TRIES ;
223253 while (maxtries -- > 0 ) {
224- int port = EphemeralPortRange .LOWER
225- + random .nextInt (EphemeralPortRange .RANGE );
254+ int port ;
255+ boolean suitable ;
256+ boolean recycled ;
257+ int maxrandom = MAX_RANDOM_TRIES ;
258+ do {
259+ port = EphemeralPortRange .LOWER
260+ + random .nextInt (EphemeralPortRange .RANGE );
261+ recycled = history .contains (port );
262+ suitable = lastport == 0 || (farEnough (port ) && !recycled );
263+ } while (maxrandom -- > 0 && !suitable );
264+
265+ // if no suitable port was found, try again
266+ // this means we might call random MAX_RANDOM_TRIES x MAX_RANDOM_TRIES
267+ // times - but that should be OK with MAX_RANDOM_TRIES = 5.
268+ if (!suitable ) continue ;
269+
226270 try {
227271 if (family != null ) {
228272 DatagramChannel c = DatagramChannel .open (family );
229273 try {
230274 DatagramSocket s = c .socket ();
231275 s .bind (new InetSocketAddress (port ));
276+ lastport = s .getLocalPort ();
277+ if (!recycled ) history .add (port );
232278 return s ;
233279 } catch (Throwable x ) {
234280 c .close ();
235281 throw x ;
236282 }
237283 }
238- return new DatagramSocket (port );
284+ DatagramSocket s = new DatagramSocket (port );
285+ lastport = s .getLocalPort ();
286+ if (!recycled ) history .add (port );
287+ return s ;
239288 } catch (IOException x ) {
240289 // try again until maxtries == 0;
241290 }
0 commit comments