blob: cda087730a991922827e0b12a82bbecbad890963 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Random;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.internal.net.SocketCreator;
/**
* This class determines whether or not a given port is available and can also provide a randomly
* selected available port.
*/
public class AvailablePort {
/** Is the port available for a Socket (TCP) connection? */
public static final int SOCKET = 0;
public static final int AVAILABLE_PORTS_LOWER_BOUND = 20001;// 20000/udp is securid
public static final int AVAILABLE_PORTS_UPPER_BOUND = 29999;// 30000/tcp is spoolfax
/** Is the port available for a JGroups (UDP) multicast connection */
public static final int MULTICAST = 1;
/////////////////////// Static Methods ///////////////////////
/**
* see if there is a gemfire system property that establishes a default address for the given
* protocol, and return it
*/
public static InetAddress getAddress(int protocol) {
String name = null;
try {
if (protocol == SOCKET) {
name = System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "bind-address");
} else if (protocol == MULTICAST) {
name = System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "mcast-address");
}
if (name != null) {
return InetAddress.getByName(name);
}
} catch (UnknownHostException e) {
throw new RuntimeException("Unable to resolve address " + name);
}
return null;
}
/**
* Returns whether or not the given port on the local host is available (that is, unused).
*
* @param port The port to check
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static boolean isPortAvailable(final int port, int protocol) {
return isPortAvailable(port, protocol, getAddress(protocol));
}
/**
* Returns whether or not the given port on the local host is available (that is, unused).
*
* @param port The port to check
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
* @param addr the bind address (or mcast address) to use
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static boolean isPortAvailable(final int port, int protocol, InetAddress addr) {
if (protocol == SOCKET) {
// Try to create a ServerSocket
if (addr == null) {
return testAllInterfaces(port);
} else {
return testOneInterface(addr, port);
}
}
else if (protocol == MULTICAST) {
MulticastSocket socket = null;
try {
socket = new MulticastSocket();
InetAddress localHost = SocketCreator.getLocalHost();
socket.setInterface(localHost);
socket.setSoTimeout(Integer.getInteger("AvailablePort.timeout", 2000).intValue());
socket.setReuseAddress(true);
byte[] buffer = new byte[4];
buffer[0] = (byte) 'p';
buffer[1] = (byte) 'i';
buffer[2] = (byte) 'n';
buffer[3] = (byte) 'g';
InetAddress mcid = addr == null ? DistributionConfig.DEFAULT_MCAST_ADDRESS : addr;
SocketAddress mcaddr = new InetSocketAddress(mcid, port);
socket.joinGroup(mcid);
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length, mcaddr);
socket.send(packet);
try {
socket.receive(packet);
packet.getData(); // make sure there's data, but no need to process it
return false;
} catch (SocketTimeoutException ste) {
// System.out.println("socket read timed out");
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} catch (java.io.IOException ioe) {
if (ioe.getMessage().equals("Network is unreachable")) {
throw new RuntimeException(
"Network is unreachable", ioe);
}
ioe.printStackTrace();
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
else {
throw new IllegalArgumentException(String.format("Unknown protocol: %s",
Integer.valueOf(protocol)));
}
}
public static Keeper isPortKeepable(final int port, int protocol, InetAddress addr) {
if (protocol == SOCKET) {
// Try to create a ServerSocket
if (addr == null) {
return keepAllInterfaces(port);
} else {
return keepOneInterface(addr, port);
}
} else if (protocol == MULTICAST) {
throw new IllegalArgumentException("You can not keep the JGROUPS protocol");
} else {
throw new IllegalArgumentException(String.format("Unknown protocol: %s",
Integer.valueOf(protocol)));
}
}
private static boolean testOneInterface(InetAddress addr, int port) {
Keeper k = keepOneInterface(addr, port);
if (k != null) {
k.release();
return true;
} else {
return false;
}
}
private static Keeper keepOneInterface(InetAddress addr, int port) {
ServerSocket server = null;
try {
// (new Exception("Opening server socket on " + port)).printStackTrace();
server = new ServerSocket();
server.setReuseAddress(true);
if (addr != null) {
server.bind(new InetSocketAddress(addr, port));
} else {
server.bind(new InetSocketAddress(port));
}
Keeper result = new Keeper(server, port);
server = null;
return result;
} catch (java.io.IOException ioe) {
if (ioe.getMessage().equals("Network is unreachable")) {
throw new RuntimeException("Network is unreachable");
}
// ioe.printStackTrace();
if (addr instanceof Inet6Address) {
byte[] addrBytes = addr.getAddress();
if ((addrBytes[0] == (byte) 0xfe) && (addrBytes[1] == (byte) 0x80)) {
// Hack, early Sun 1.5 versions (like Hitachi's JVM) cannot handle IPv6
// link local addresses. Cannot trust InetAddress.isLinkLocalAddress()
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6558853
// By returning true we ignore these interfaces and potentially say a
// port is not in use when it really is.
Keeper result = new Keeper(server, port);
server = null;
return result;
}
}
return null;
} catch (Exception ex) {
return null;
} finally {
if (server != null)
try {
server.close();
} catch (Exception ex) {
}
}
}
/**
* Test to see if a given port is available port on all interfaces on this host.
*
* @return true of if the port is free on all interfaces
*/
private static boolean testAllInterfaces(int port) {
Keeper k = keepAllInterfaces(port);
if (k != null) {
k.release();
return true;
} else {
return false;
}
}
private static Keeper keepAllInterfaces(int port) {
// First check to see if we can bind to the wildcard address.
if (!testOneInterface(null, port)) {
return null;
}
// Now check all of the addresses for all of the addresses
// on this system. On some systems (solaris, aix) binding
// to the wildcard address will successfully bind to only some
// of the interfaces if other interfaces are in use. We want to
// make sure this port is completely free.
//
// Note that we still need the check of the wildcard address, above,
// because on some systems (aix) we can still bind to specific addresses
// if someone else has bound to the wildcard address.
Enumeration en;
try {
en = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
throw new RuntimeException(e);
}
while (en.hasMoreElements()) {
NetworkInterface next = (NetworkInterface) en.nextElement();
Enumeration en2 = next.getInetAddresses();
while (en2.hasMoreElements()) {
InetAddress addr = (InetAddress) en2.nextElement();
boolean available = testOneInterface(addr, port);
if (!available) {
return null;
}
}
}
// Now do it one more time but reserve the wildcard address
return keepOneInterface(null, port);
}
/**
* Returns a randomly selected available port in the range 5001 to 32767.
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getRandomAvailablePort(int protocol) {
return getRandomAvailablePort(protocol, getAddress(protocol));
}
public static Keeper getRandomAvailablePortKeeper(int protocol) {
return getRandomAvailablePortKeeper(protocol, getAddress(protocol));
}
/**
* Returns a randomly selected available port in the provided range.
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getAvailablePortInRange(int rangeBase, int rangeTop, int protocol) {
return getAvailablePortInRange(protocol, getAddress(protocol), rangeBase, rangeTop);
}
/**
* Returns a randomly selected available port in the range 5001 to 32767 that satisfies a modulus
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getRandomAvailablePortWithMod(int protocol, int mod) {
return getRandomAvailablePortWithMod(protocol, getAddress(protocol), mod);
}
/**
* Returns a randomly selected available port in the range 5001 to 32767.
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
* @param addr the bind-address or mcast address to use
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getRandomAvailablePort(int protocol, InetAddress addr) {
return getRandomAvailablePort(protocol, addr, false);
}
/**
* Returns a randomly selected available port in the range 5001 to 32767.
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
* @param addr the bind-address or mcast address to use
* @param useMembershipPortRange use true if the port will be used for membership
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getRandomAvailablePort(int protocol, InetAddress addr,
boolean useMembershipPortRange) {
while (true) {
int port = getRandomWildcardBindPortNumber(useMembershipPortRange);
if (isPortAvailable(port, protocol, addr)) {
// don't return the products default multicast port
if (!(protocol == MULTICAST && port == DistributionConfig.DEFAULT_MCAST_PORT)) {
return port;
}
}
}
}
public static Keeper getRandomAvailablePortKeeper(int protocol, InetAddress addr) {
return getRandomAvailablePortKeeper(protocol, addr, false);
}
public static Keeper getRandomAvailablePortKeeper(int protocol, InetAddress addr,
boolean useMembershipPortRange) {
while (true) {
int port = getRandomWildcardBindPortNumber(useMembershipPortRange);
Keeper result = isPortKeepable(port, protocol, addr);
if (result != null) {
return result;
}
}
}
/**
* Returns a randomly selected available port in the provided range.
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
* @param addr the bind-address or mcast address to use
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getAvailablePortInRange(int protocol, InetAddress addr, int rangeBase,
int rangeTop) {
for (int port = rangeBase; port <= rangeTop; port++) {
if (isPortAvailable(port, protocol, addr)) {
return port;
}
}
return -1;
}
/**
* Returns a randomly selected available port in the range 5001 to 32767 that satisfies a modulus
* and the provided protocol
*
* @param protocol The protocol to check (either {@link #SOCKET} or {@link #MULTICAST}).
* @param addr the bind-address or mcast address to use
*
* @throws IllegalArgumentException <code>protocol</code> is unknown
*/
public static int getRandomAvailablePortWithMod(int protocol, InetAddress addr, int mod) {
while (true) {
int port = getRandomWildcardBindPortNumber();
if (isPortAvailable(port, protocol, addr) && (port % mod) == 0) {
return port;
}
}
}
@Immutable
public static final Random rand;
static {
boolean fast = Boolean.getBoolean("AvailablePort.fastRandom");
if (fast)
rand = new Random();
else
rand = new java.security.SecureRandom();
}
private static int getRandomWildcardBindPortNumber() {
return getRandomWildcardBindPortNumber(false);
}
private static int getRandomWildcardBindPortNumber(boolean useMembershipPortRange) {
int rangeBase;
int rangeTop;
if (!useMembershipPortRange) {
rangeBase = AVAILABLE_PORTS_LOWER_BOUND; // 20000/udp is securid
rangeTop = AVAILABLE_PORTS_UPPER_BOUND; // 30000/tcp is spoolfax
} else {
rangeBase = DistributionConfig.DEFAULT_MEMBERSHIP_PORT_RANGE[0];
rangeTop = DistributionConfig.DEFAULT_MEMBERSHIP_PORT_RANGE[1];
}
return rand.nextInt(rangeTop - rangeBase) + rangeBase;
}
private static int getRandomPortNumberInRange(int rangeBase, int rangeTop) {
return rand.nextInt(rangeTop - rangeBase) + rangeBase;
}
public static int getRandomAvailablePortInRange(int rangeBase, int rangeTop, int protocol) {
int numberOfPorts = rangeTop - rangeBase;
// do "5 times the numberOfPorts" iterations to select a port number. This will ensure that
// each of the ports from given port range get a chance at least once
int numberOfRetrys = numberOfPorts * 5;
for (int i = 0; i < numberOfRetrys; i++) {
int port = rand.nextInt(numberOfPorts + 1) + rangeBase;// add 1 to numberOfPorts so that
// rangeTop also gets included
if (isPortAvailable(port, protocol, getAddress(protocol))) {
return port;
}
}
return -1;
}
/**
* This class will keep an allocated port allocated until it is used. This makes the window
* smaller that can cause bug 46690
*
*/
public static class Keeper implements Serializable {
private final transient ServerSocket ss;
private final int port;
public Keeper(ServerSocket ss, int port) {
this.ss = ss;
this.port = port;
}
public Keeper(ServerSocket ss, Integer port) {
this.ss = ss;
this.port = port != null ? port : 0;
}
public int getPort() {
return this.port;
}
/**
* Once you call this the socket will be freed and can then be reallocated by someone else.
*/
public void release() {
try {
if (this.ss != null) {
this.ss.close();
}
} catch (IOException ignore) {
}
}
}
/////////////////////// Main Program ///////////////////////
@Immutable
private static final PrintStream out = System.out;
@Immutable
private static final PrintStream err = System.err;
private static void usage(String s) {
err.println("\n** " + s + "\n");
err.println("usage: java AvailablePort socket|jgroups [\"addr\" network-address] [port]");
err.println("");
err.println(
"This program either prints whether or not a port is available for a given protocol, or it prints out an available port for a given protocol.");
err.println("");
ExitCode.FATAL.doSystemExit();
}
public static void main(String[] args) {
String protocolString = null;
String addrString = null;
String portString = null;
for (int i = 0; i < args.length; i++) {
if (protocolString == null) {
protocolString = args[i];
} else if (args[i].equals("addr")) {
addrString = args[++i];
} else if (portString == null) {
portString = args[i];
} else {
usage("Spurious command line: " + args[i]);
}
}
int protocol;
if (protocolString == null) {
usage("Missing protocol");
return;
} else if (protocolString.equalsIgnoreCase("socket")) {
protocol = SOCKET;
} else if (protocolString.equalsIgnoreCase("javagroups")
|| protocolString.equalsIgnoreCase("jgroups")) {
protocol = MULTICAST;
} else {
usage("Unknown protocol: " + protocolString);
return;
}
InetAddress addr = null;
if (addrString != null) {
try {
addr = InetAddress.getByName(addrString);
} catch (Exception e) {
e.printStackTrace();
ExitCode.FATAL.doSystemExit();
}
}
if (portString != null) {
int port;
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException ex) {
usage("Malformed port: " + portString);
return;
}
out.println("\nPort " + port + " is " + (isPortAvailable(port, protocol, addr) ? "" : "not ")
+ "available for a " + protocolString + " connection\n");
} else {
out.println("\nRandomly selected " + protocolString + " port: "
+ getRandomAvailablePort(protocol, addr) + "\n");
}
}
}