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
@@ -196,9 +221,7 @@ private DatagramSocket openDefault() throws SocketException {
196221 } catch (SocketException x ) {
197222 throw x ;
198223 } catch (IOException x ) {
199- SocketException e = new SocketException (x .getMessage ());
200- e .initCause (x );
201- throw e ;
224+ throw new SocketException (x .getMessage (), x );
202225 }
203226 }
204227 return new DatagramSocket ();
@@ -218,24 +241,48 @@ synchronized boolean isUndecided() {
218241 && !isUsingNativePortRandomization ();
219242 }
220243
244+ private boolean farEnough (int port ) {
245+ return Integer .bitCount (port ^ lastport ) > BIT_DEVIATION
246+ && Math .abs (port - lastport ) > deviation ;
247+ }
248+
221249 private DatagramSocket openRandom () {
222250 int maxtries = MAX_RANDOM_TRIES ;
223251 while (maxtries -- > 0 ) {
224- int port = EphemeralPortRange .LOWER
225- + random .nextInt (EphemeralPortRange .RANGE );
252+ int port ;
253+ boolean suitable ;
254+ boolean recycled ;
255+ int maxrandom = MAX_RANDOM_TRIES ;
256+ do {
257+ port = EphemeralPortRange .LOWER
258+ + random .nextInt (EphemeralPortRange .RANGE );
259+ recycled = history .contains (port );
260+ suitable = lastport == 0 || (farEnough (port ) && !recycled );
261+ } while (maxrandom -- > 0 && !suitable );
262+
263+ // if no suitable port was found, try again
264+ // this means we might call random MAX_RANDOM_TRIES x MAX_RANDOM_TRIES
265+ // times - but that should be OK with MAX_RANDOM_TRIES = 5.
266+ if (!suitable ) continue ;
267+
226268 try {
227269 if (family != null ) {
228270 DatagramChannel c = DatagramChannel .open (family );
229271 try {
230272 DatagramSocket s = c .socket ();
231273 s .bind (new InetSocketAddress (port ));
274+ lastport = s .getLocalPort ();
275+ if (!recycled ) history .add (port );
232276 return s ;
233277 } catch (Throwable x ) {
234278 c .close ();
235279 throw x ;
236280 }
237281 }
238- return new DatagramSocket (port );
282+ DatagramSocket s = new DatagramSocket (port );
283+ lastport = s .getLocalPort ();
284+ if (!recycled ) history .add (port );
285+ return s ;
239286 } catch (IOException x ) {
240287 // try again until maxtries == 0;
241288 }
0 commit comments