com.oracle.coherence.common.net.InetAddresses Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.oracle.coherence.common.net;
import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.base.Predicate;
import com.oracle.coherence.common.base.Timeout;
import com.oracle.coherence.common.collections.ConcurrentHashMap;
import com.oracle.coherence.common.internal.net.MultiplexedSocketProvider;
import com.oracle.coherence.common.util.Duration;
import com.oracle.coherence.common.util.SafeClock;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Helper class that encapsulates common InetAddress functionality.
*/
public abstract class InetAddresses
{
/**
* Obtain the "best" local host address which matches the supplied predicate.
*
* @param predicate the predicate to match
*
* @return the InetAddress
*
* @throws UnknownHostException if no match can be found
*/
public static InetAddress getLocalAddress(Predicate predicate)
throws UnknownHostException
{
InetAddress addrLocal = InetAddress.getLocalHost();
InetAddress addrBest = null;
int nMTUBest = 0;
int nMTULocal = 0;
Map mapAddr = getAllLocalMTUs();
for (Iterator iter = mapAddr.entrySet().iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry) iter.next();
InetAddress addr = (InetAddress) entry.getKey();
if (!predicate.evaluate(addr))
{
continue;
}
// addr is acceptable, prefer a higher MTU. In the case of equal
// MTUs we compare addresses for order to ensure that we will
// return a consistent result between invocations.
Integer oMTU = (Integer) entry.getValue();
int nMTU = oMTU == null ? 0 : oMTU.intValue();
if (addr.equals(addrLocal))
{
nMTULocal = nMTU;
}
if (nMTU > nMTUBest ||
(nMTU == nMTUBest && compare(addr, addrBest) < 0))
{
addrBest = addr;
nMTUBest = nMTU;
}
}
if (addrBest == null)
{
throw new UnknownHostException("No local address matching " + predicate);
}
// prefer the OS defined localhost assuming all else is equal
return nMTUBest == nMTULocal && predicate.evaluate(addrLocal)
? addrLocal : addrBest;
}
/**
* Return the local InetAddress represented by the specified string.
*
* Note: if an explicit address is supplied and it is non-local then it will be assumed to
* be an external NAT address.
*
*
* @param sAddr the string address, either hostname, literal ip, or subnet and mask
*
* @return the InetAddress
*
* @throws UnknownHostException if the string cannot be resolved
*/
public static InetAddress getLocalAddress(String sAddr)
throws UnknownHostException
{
if (sAddr == null || sAddr.isEmpty() || sAddr.equals("localhost"))
{
return getLocalHost();
}
else if (sAddr.equals("0.0.0.0") || sAddr.equals("::0") || sAddr.equals("::")) // wildcard
{
return ADDR_ANY;
}
else if (sAddr.indexOf('/') != -1) // CIDR
{
return getLocalAddress(new IsSubnetMask(sAddr));
}
else
{
return InetAddress.getByName(sAddr);
}
}
/**
* Obtain the local host address. If at all possible, ensure that the
* returned address is not a loopback, wildcard or a link local address.
*
* @return the InetAddress that is the best fit for the local host address
*
* @throws UnknownHostException if no match can be found
*
* @see
* Sun's Bug Parade
*/
public static InetAddress getLocalHost()
throws UnknownHostException
{
InetAddress addrLocal = s_addrLocalhost;
if (addrLocal != null &&
isLocalAddress(addrLocal)) // avoid returning dropped DHCP
{
return addrLocal;
}
boolean fNonRoutable = false;
Set setExclude = null;
for (;;)
{
try
{
final boolean fNonRoutablePass = fNonRoutable;
final Set setExcludePass = setExclude;
InetAddress addr = getLocalAddress(new Predicate()
{
@Override
public boolean evaluate(InetAddress addr)
{
return (fNonRoutablePass || IsRoutable.INSTANCE.evaluate(addr)) && // avoids loopback initially
(setExcludePass == null || !setExcludePass.contains(addr)); // avoid previous rejections
}
});
if (!isLocalReachableAddress(addr, 300)) // avoids temporary IPv6 addresses, and VPN blocked addresses
{
if (setExclude == null)
{
setExclude = new HashSet<>();
}
setExclude.add(addr);
continue;
}
return s_addrLocalhost = addr; // cache and return
}
catch (UnknownHostException e)
{
if (fNonRoutable)
{
return InetAddress.getLocalHost();
}
fNonRoutable = true; // include non-routable on next pass, i.e. allow loopback
}
}
}
/**
* The IsRoutable predicate evaluates to true for any InetAddress which is
* externally routable.
*/
public static class IsRoutable
implements Predicate
{
/**
* {@inheritDoc}
*/
@Override
public boolean evaluate(InetAddress addr)
{
return !addr.isLoopbackAddress() &&
!addr.isAnyLocalAddress() &&
!addr.isLinkLocalAddress();
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "is routable";
}
/**
* Singleton instance.
*/
public static final IsRoutable INSTANCE = new IsRoutable();
}
/**
* IsSubnetMask predicate evaluates to true for any address with matches the
* pattern for the masked bits
*/
public static class IsSubnetMask
implements Predicate
{
/**
* Construct a predicate for the given pattern and mask.
*
* @param addrPattern the pattern to match
* @param addrMask the mask identifying the portion of the pattern to match
*/
public IsSubnetMask(InetAddress addrPattern, InetAddress addrMask)
{
m_abPattern = addrPattern.getAddress();
m_abMask = addrMask.getAddress();
m_sDescription = addrPattern + "/" + addrMask;
if (m_abPattern.length != m_abMask.length)
{
throw new IllegalArgumentException(
"pattern and mask must be of the same byte length");
}
}
/**
* Construct a predicate for the given pattern and mask bit count.
*
* @param addrPattern the pattern to match
* @param cMaskBits the number of mask bits
*/
public IsSubnetMask(InetAddress addrPattern, int cMaskBits)
{
m_abPattern = addrPattern.getAddress();
m_abMask = new byte[m_abPattern.length];
m_sDescription = addrPattern + "/" + cMaskBits;
setSubnetMask(m_abMask, cMaskBits);
}
/**
* Construct a predicate for the given pattern and slash mask.
*
* @see
* CIDR Notation
*
* @param sAddr the pattern and mask
*/
public IsSubnetMask(String sAddr)
{
try
{
m_sDescription = sAddr;
int ofSubnetMask = sAddr.indexOf('/');
InetAddress addr = ofSubnetMask == -1
? InetAddress.getByName(sAddr)
: InetAddress.getByName(sAddr.substring(0, ofSubnetMask));
byte[] abPattern = m_abPattern = addr.getAddress();
if (ofSubnetMask == -1 || sAddr.indexOf('.', ofSubnetMask) == -1)
{
byte[] abMask = m_abMask = new byte[abPattern.length];
setSubnetMask(abMask, ofSubnetMask == -1
? abPattern.length * 8 // no slash == full mask
: Integer.valueOf(sAddr.substring(ofSubnetMask + 1)));
}
else
{
m_abMask = InetAddress.getByName(sAddr.substring
(ofSubnetMask + 1)).getAddress();
}
}
catch (UnknownHostException e)
{
throw new IllegalArgumentException("dns names are not supported");
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean evaluate(InetAddress addr)
{
byte[] ab = addr.getAddress();
byte[] abPat = m_abPattern;
byte[] abMask = m_abMask;
if (ab.length != abPat.length)
{
return false;
}
for (int i = ab.length - 1; i >= 0; --i)
{
byte bMask = abMask[i];
if ((ab[i] & bMask) != (abPat[i] & bMask))
{
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "IsSubnetMask(" + m_sDescription + ")";
}
protected String m_sDescription;
protected byte[] m_abPattern;
protected byte[] m_abMask;
}
/**
* Compare two InetAddresses for ordering purposes.
*
* @param addrA the first address to compare
* @param addrB the second address to compare
*
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second
*/
public static int compare(InetAddress addrA, InetAddress addrB)
{
return InetAddressComparator.INSTANCE.compare(addrA, addrB);
}
/**
* Return the MTU for the specified local address.
*
* @param addr the local address
*
* @return the MTU of the specified address, or 0 if the MTU can not be
* identified
*/
public static int getLocalMTU(InetAddress addr)
{
try
{
NetworkInterface ni = NetworkInterface.getByInetAddress(addr);
if (ni == null)
{
throw new IllegalArgumentException("The specified address \"" +
addr + "\" is not a local address.");
}
return getLocalMTU(ni);
}
catch (SocketException e) {}
return 0;
}
/**
* Return the MTU for the specified NetworkInterface.
*
* @param ni the network interface
*
* @return the MTU of the specified address, or 0 if the MTU can not be
* identified
*/
public static int getLocalMTU(NetworkInterface ni)
{
try
{
int nMTU = ni.getMTU();
// Windows 7 + JRE 1.6 reports -1 for loopback, apparently
// converting a unsigned 32b int max value into a signed 32b int
return nMTU < 0 ? Integer.MAX_VALUE : nMTU;
}
catch (Exception e) {}
return 0;
}
/**
* Return this machines MTU.
*
* @return the machine's MTU
*/
public static int getLocalMTU()
{
// we couldn't find it, either because of the above described error, or because the socket is bound to a
// NAT address; just return the box's minimum MTU
int nMtu = 65535; // start with largest allowed by IP
try
{
for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces();
enmrNI != null && enmrNI.hasMoreElements();)
{
int nMtuNic = InetAddresses.getLocalMTU((NetworkInterface) enmrNI.nextElement());
if (nMtuNic > 0)
{
nMtu = Math.min(nMtu, nMtuNic);
}
}
}
catch (SocketException e) {}
return nMtu;
}
/**
* Return a local address which is in the same subnet as the specified address.
*
* @param addr the address to find a local peer of
*
* @return a local address in the same subnet as the specified address or null
if none is found
*/
public static InetAddress getLocalPeer(InetAddress addr)
{
for (InetAddress addrLocal : getAllLocalAddresses())
{
if (isInSubnet(addr, /*addrSubnet*/ addrLocal, getLocalSubnetLength(addrLocal)))
{
return addrLocal;
}
}
return null;
}
/**
* Return true if the specified address is part of the specified subnet
*
* @param addr the address to test
* @param addrSubnet the subnet
* @param cBitSubnet the number of valid bits in the subnet address
*
* @return return true if the specified address is in the specified subnet
*/
public static boolean isInSubnet(InetAddress addr, InetAddress addrSubnet, int cBitSubnet)
{
byte[] ab = addr.getAddress();
byte[] abPat = addrSubnet.getAddress();
if (ab.length != abPat.length)
{
return false;
}
for (int i = 0; i < ab.length && cBitSubnet > 0; ++i)
{
byte bMask;
if (cBitSubnet < 8)
{
bMask = 0;
for (int j = 0; j < cBitSubnet; ++j)
{
bMask |= (1 << cBitSubnet - j);
}
}
else
{
bMask = (byte) 0x0FF;
}
if ((ab[i] & bMask) != (abPat[i] & bMask))
{
return false;
}
cBitSubnet -= 8;
}
return true;
}
/**
* Return the InetAddress representing the subnet for the specified local address.
*
* @param addr the local address
*
* @return the subnet address
*/
public static InetAddress getLocalSubnetAddress(InetAddress addr)
{
int cBits = getLocalSubnetLength(addr);
byte[] abAddr = addr.getAddress();
for (int i = 0, c = abAddr.length; i < c; ++i)
{
if (cBits == 0)
{
// zero out entire byte
abAddr[i] = 0;
}
else if (cBits < 8)
{
// partial byte is part of subnet, zero out remainder
byte cZero = (byte) (8 - cBits);
abAddr[i] = (byte) ((abAddr[i] >> cZero) << cZero);
cBits = 0;
}
else
{
// full byte is part of subnet
cBits -= 8;
}
}
try
{
return InetAddress.getByAddress(abAddr);
}
catch (UnknownHostException e)
{
throw new IllegalStateException(e);
}
}
/**
* Return the bit length for the subnet of the specified local address.
*
* @param addr the local address
*
* @return the subnet address
*/
public static short getLocalSubnetLength(InetAddress addr)
{
try
{
NetworkInterface ni = NetworkInterface.getByInetAddress(addr);
if (ni == null)
{
throw new IllegalArgumentException("The specified address \"" +
addr + "\" is not a local address.");
}
for (InterfaceAddress addrIf : ni.getInterfaceAddresses())
{
if (addrIf.getAddress().equals(addr))
{
short cBits = addrIf.getNetworkPrefixLength();
// for some reason 0 is sometimes returned for ipv4s, there is no correct
// answer here, but anything is better then 0, note in such cases the
// broadcast address is also null so we can't even try to derive it
return cBits == 0 ? 8 : cBits;
}
}
}
catch (IOException e)
{
throw new IllegalStateException(e);
}
throw new IllegalStateException();
}
/**
* Return a list of all InetAddress objects bound to all the network
* interfaces on this machine.
*
* @return a list of InetAddress objects
*/
public static List getAllLocalAddresses()
{
return getLocalAddresses(o -> true);
}
/**
* Return a list of InetAddresses bound to all the network
* interfaces on this machine matching the specified predicate.
*
* @param predicate the predicate to match
*
* @return a list of InetAddress objects
*/
public static List getLocalAddresses(Predicate predicate)
{
List listAddr = new ArrayList<>();
try
{
for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces();
enmrNI != null && enmrNI.hasMoreElements();)
{
NetworkInterface ni = (NetworkInterface) enmrNI.nextElement();
for (Enumeration enmrAddr = ni.getInetAddresses();
enmrAddr.hasMoreElements();)
{
InetAddress addr = enmrAddr.nextElement();
if (predicate.evaluate(addr))
{
listAddr.add(addr);
}
}
}
}
catch (SocketException e) {}
return listAddr;
}
/**
* Returns a collection of all local bindable addresses.
*
* Note: this collection will also include NAT address which have been previously
* {@link #isNatLocalAddress(InetAddress, int, int, int) identified}. I.e. external addresses which can be
* bound to via {@link TcpSocketProvider#MULTIPLEXED multiplexed socket provider}.
*
* @return a list of all local bindable addresses
*
* @since 12.2.1
*/
public static Collection getLocalBindableAddresses()
{
if (!s_fDequeAddrBindablePopulated)
{
synchronized (s_dequeAddrBindable)
{
if (!s_fDequeAddrBindablePopulated)
{
s_dequeAddrBindable.addAll(getLocalAddresses(
new Predicate()
{
public boolean evaluate(InetAddress addr)
{
if (addr.isAnyLocalAddress())
{
return false;
}
else if (addr instanceof Inet4Address)
{
return true;
}
else
{
// only return Inet6Addresses which are bindable
try (ServerSocket socket = new ServerSocket(0, 0, addr))
{
return true;
}
catch (IOException e)
{
}
return false;
}
}
}));
s_fDequeAddrBindablePopulated = true;
}
}
}
return Collections.unmodifiableCollection(s_dequeAddrBindable);
}
/**
* Return a map of all InetAddress and MTUs bound to all the network
* interfaces on this machine. If the MTU cannot be obtained a null
* value will be used.
*
* @return a map of Integer MTU values keyed by the corresponding InetAddress
*/
public static Map getAllLocalMTUs()
{
Map mapAddr = new HashMap();
try
{
for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces();
enmrNI != null && enmrNI.hasMoreElements();)
{
NetworkInterface ni = (NetworkInterface) enmrNI.nextElement();
int nMTU = getLocalMTU(ni);
Object oMTU = nMTU == 0 ? null : Integer.valueOf(nMTU);
for (Enumeration enmrAddr = ni.getInetAddresses();
enmrAddr.hasMoreElements();)
{
mapAddr.put(enmrAddr.nextElement(), oMTU);
}
}
}
catch (SocketException e) {}
return mapAddr;
}
/**
* Clean an IPv6 address by removing the brackets.
*
* @param sAddr an address
*
* @return an IPv6 address without brackets or original string if not IPv6
* literal
*/
private static String unbracketAddressString(String sAddr)
{
if (sAddr.charAt(0) == '[')
{
int ofBracket = sAddr.indexOf(']', 1);
if (ofBracket < 0)
{
throw new IllegalArgumentException("invalid IPv6 address");
}
sAddr = sAddr.substring(1, ofBracket);
}
return sAddr;
}
/**
* Determine if a host string is a hostname.
*
* @param sHost the host string
*
* @return true if host string is a hostname
*/
public static boolean isHostName(String sHost)
{
return sHost.length() != 0 && sHost.indexOf(":") < 0
&& !sHost.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$");
}
/**
* Return true if the specified address string represents the wildcard address
*
* @param sAddr the address to test
*
* @return true if the specified address is the wildcard address
*/
public static boolean isAnyLocalAddress(String sAddr)
{
return sAddr != null && (sAddr.equals("::0") || sAddr.equals("0.0.0.0"));
}
/**
* Return true if the supplied port is believed to be in the ephemeral port range.
*
* Note that while a port of 0 can used to request an ephemeral port it is not itself
* considered to be ephemeral.
*
* @param nPort the port to query
*
* @return true if the port is believed to be ephemeral
*/
public static boolean isEphemeral(int nPort)
{
if (nPort < 0 || nPort > 65535) // decode multiplexed ports
{
nPort = MultiplexedSocketProvider.getBasePort(nPort);
}
int nLow;
int nHigh;
String sOS = System.getProperty("os.name").toLowerCase().trim();
if ("linux".equals(sOS))
{
// we have custom linux code because it doesn't use the IANA suggested range and at least RedHat uses a
// stupid lower bound of 1024 so we're likely to run into random bind failures on RedHat and presumably its
// derivatives.
// read /proc to get actual range and reservations, these aren't actual files on disk but rather virtual
// files served up by the kernel and access should be immediate so we don't try to cache the results
// see https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt or details on the formats of these
// files note despite the names these files are for both ipv4 and ipv6 as well as both UDP and TCP
// try to check if it reserved, if so then it can't be ephemeral
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/sys/net/ipv4/ip_local_reserved_ports"))))
{
StringTokenizer sTok = new StringTokenizer(in.readLine(), ",");
while (sTok.hasMoreElements())
{
String sReserved = sTok.nextToken().trim();
int ofRange = sReserved.indexOf('-');
if (ofRange == -1)
{
if (Integer.parseInt(sReserved) == nPort)
{
return false;
}
}
else
{
int nLowRes = Integer.parseInt(sReserved.substring(0, ofRange).trim());
int nHighRes = Integer.parseInt(sReserved.substring(ofRange + 1).trim());
if (nPort >= nLowRes && nPort <= nHighRes)
{
return false;
}
}
}
}
catch (Throwable t) {}
// try to read configured ephemeral range
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/sys/net/ipv4/ip_local_port_range"))))
{
StringTokenizer sTok = new StringTokenizer(in.readLine());
nLow = Integer.parseInt(sTok.nextToken().trim());
nHigh = Integer.parseInt(sTok.nextToken().trim());
}
catch (Throwable t)
{
// use standard Linux defaults
nLow = 32768;
nHigh = 61000;
}
}
else // assume IANA suggested defaults which is quite typical other then for Linux
{
nLow = 49152;
nHigh = 65535;
}
return nPort >= nLow && nPort <= nHigh;
}
/**
* Return the InetSocketAddress represented by the specified string.
*
* @param sAddr a legal address string
* @param nPort a default port to use if sAddr does not contain one
*
* @return a non-null InetSocketAddress object
*
* @throws UnknownHostException if no match can be found
*/
public static InetSocketAddress getSocketAddress(String sAddr, int nPort)
throws UnknownHostException
{
if (sAddr == null)
{
throw new IllegalArgumentException("address cannot be null");
}
String sHost = unbracketAddressString(sAddr);
int ofPort;
if (sAddr.equals(sHost))
{
// ipv4 address
// still need to parse for both host and port
ofPort = sAddr.lastIndexOf(':');
int ofEnd = ofPort != -1 ? ofPort : sAddr.length();
sHost = sAddr.substring(0, ofEnd);
}
else
{
// ipv6 address
// we already have the host, just grab the port if it exists
ofPort = sAddr.indexOf(':', sAddr.indexOf(']'));
}
if (ofPort != -1)
{
// pull port off of address
nPort = Integer.parseInt(sAddr.substring(ofPort + 1));
}
if (sHost.equals("*"))
{
return new InetSocketAddress(nPort);
}
if (sHost.equals("localhost") || sAddr.length() == 0)
{
return new InetSocketAddress(InetAddresses.getLocalHost(), nPort);
}
InetSocketAddress addr = new InetSocketAddress(sHost, nPort);
if (addr.getAddress() == null)
{
throw new UnknownHostException("could not resolve address \"" + sAddr + "\"");
}
return addr;
}
/**
* Obtain the {@link InetAddress} from a given {@link SocketAddress}.
*
* Throws an {@link IllegalArgumentException} if sockAddr is not a
* {@link InetSocketAddress} or {@link InetSocketAddress32}.
*
* @param sockAddr the {@link SocketAddress}
*
* @return the {@link InetAddress}
*
* @since 12.2.1
*/
public static InetAddress getAddress(SocketAddress sockAddr)
{
if (sockAddr instanceof InetSocketAddress)
{
return ((InetSocketAddress) sockAddr).getAddress();
}
if (sockAddr instanceof InetSocketAddress32)
{
return ((InetSocketAddress32) sockAddr).getAddress();
}
throw new IllegalArgumentException("Cannot obtain an address from class " + sockAddr.getClass());
}
/**
* Obtain the port from a given {@link SocketAddress}.
*
* Throws an {@link IllegalArgumentException} if sockAddr is not a
* {@link InetSocketAddress} or {@link InetSocketAddress32}.
*
* @param sockAddr the {@link SocketAddress}
*
* @return the port
*
* @since 12.2.1
*/
public static int getPort(SocketAddress sockAddr)
{
if (sockAddr instanceof InetSocketAddress)
{
return ((InetSocketAddress) sockAddr).getPort();
}
if (sockAddr instanceof InetSocketAddress32)
{
return ((InetSocketAddress32) sockAddr).getPort();
}
throw new IllegalArgumentException("Cannot obtain a port from class " + sockAddr.getClass());
}
/**
* Return a new SocketAddress of the same type but with the specified address
*
* Throws an {@link IllegalArgumentException} if sockAddr is not a
* {@link InetSocketAddress} or {@link InetSocketAddress32}.
*
* @param sockAddr the {@link SocketAddress}
* @param addr the new address
*
* @return the new SocketAddress
*
* @since 12.2.3
*/
public static SocketAddress setAddress(SocketAddress sockAddr, InetAddress addr)
{
if (sockAddr instanceof InetSocketAddress)
{
return new InetSocketAddress(addr, ((InetSocketAddress) sockAddr).getPort());
}
if (sockAddr instanceof InetSocketAddress32)
{
return new InetSocketAddress32(addr, ((InetSocketAddress32) sockAddr).getPort());
}
throw new IllegalArgumentException("Cannot set address for class " + sockAddr.getClass());
}
/**
* Return a new SocketAddress of the same type but with the specified port.
*
* Throws an {@link IllegalArgumentException} if sockAddr is not a
* {@link InetSocketAddress} or {@link InetSocketAddress32}.
*
* @param sockAddr the {@link SocketAddress}
* @param nPort the new port
*
* @return the new SocketAddress
*
* @since 12.2.3
*/
public static SocketAddress setPort(SocketAddress sockAddr, int nPort)
{
if (sockAddr instanceof InetSocketAddress)
{
return new InetSocketAddress(((InetSocketAddress) sockAddr).getAddress(), nPort);
}
if (sockAddr instanceof InetSocketAddress32)
{
return new InetSocketAddress32(((InetSocketAddress32) sockAddr).getAddress(), nPort);
}
throw new IllegalArgumentException("Cannot set port for class " + sockAddr.getClass());
}
/**
* Return true if the specified address is a local address.
*
* @param addr the address to check
*
* @return true if the address is a local address
*/
public static boolean isLocalAddress(InetAddress addr)
{
try
{
return addr.isLoopbackAddress() || addr.isAnyLocalAddress() || checkLocalAddress(addr);
}
catch (SocketException e)
{
return false;
}
}
/**
* Return true iff the specified address is local and bindable.
*
* @param addr the address to test
*
* @return true iff the specified address is local and bindable
*/
public static boolean isLocalBindableAddress(InetAddress addr)
{
try (ServerSocket socket = new ServerSocket(0, 0, addr))
{
return true;
}
catch (IOException e)
{
return false;
}
}
/**
* Return true iff the specified address is local and reachable.
*
* @param addr the address to test
* @param cMillis the timeout value in milliseconds
*
* @return true iff the specified address is local and reachable
*/
public static boolean isLocalReachableAddress(InetAddress addr, int cMillis)
{
try (ServerSocket socket = new ServerSocket(0, 0, addr))
{
try (Socket client = new Socket())
{
Blocking.connect(client, socket.getLocalSocketAddress(), cMillis);
return true;
}
}
catch (IOException e)
{
return false;
}
}
/**
* Return true if any {@link #isNatLocalAddress(InetAddress, int) local NAT} addresses have been identified.
*
* @return true if any local NAT addresses have been identified
*/
public static boolean hasNatLocalAddress()
{
return s_refSetNAT.get() != null;
}
/**
* Return true iff the specified non-local address is NAT'd to a local address.
*
* @param addr the socket address to test
*
* @return true iff the specified non-local address routes to a local address
*/
public static boolean isNatLocalAddress(SocketAddress addr)
{
return isNatLocalAddress(getAddress(addr), getPort(addr));
}
/**
* Return true iff the specified non-local address is NAT'd to a local address.
*
* @param addr the address to test
* @param nPort the port to test
*
* @return true iff the specified non-local address routes to a local address
*/
public static boolean isNatLocalAddress(InetAddress addr, int nPort)
{
return isNatLocalAddress(addr, nPort, nPort);
}
/**
* Return true iff the specified non-local address is NAT'd to a local address.
*
* This method takes a range of ports to test through, though once there is a single positive match
* no further testing will be preformed. Specification of a port range is only necessary if a firewall
* may block the NAT'd traffic, or if NAT'ing is only available on specific ports.
*
* @param addr the address to test
* @param nPortMin the lower bound on the range of ports to test
* @param nPortMax the upper bound on the range of ports to test
*
* @return true iff the specified non-local address routes to a local address
*/
public static boolean isNatLocalAddress(InetAddress addr, int nPortMin, int nPortMax)
{
return isNatLocalAddress(addr, nPortMin, nPortMax, (int) NAT_CHECK_TIMEOUT);
}
/**
* Return true iff the specified non-local address is NAT'd to a local address.
*
* This method takes a range of ports to test through, though once there is a single positive match
* no further testing will be preformed. Specification of a port range is only necessary if a firewall
* may block the NAT'd traffic, or if NAT'ing is only available on specific ports.
*
* @param addr the address to test
* @param nPortMin the lower bound on the range of ports to test
* @param nPortMax the upper bound on the range of ports to test
* @param cMillis the timeout value in milliseconds
*
* @return true iff the specified non-local address routes to a local address
*/
public static boolean isNatLocalAddress(InetAddress addr, int nPortMin, int nPortMax, int cMillis)
{
if (cMillis <= 0L || isLocalAddress(addr))
{
return false;
}
Set setNAT = s_refSetNAT.get();
if (setNAT == null)
{
setNAT = Collections.newSetFromMap(new ConcurrentHashMap<>());
if (!s_refSetNAT.compareAndSet(null, setNAT))
{
setNAT = s_refSetNAT.get();
}
}
// It's possible we've already bound to this port and are retesting because of some other multiplexed
// socket. Here we use multiplexed sockets with ephemeral sub-ports, this ensures we aren't blocked
// by and that we don't block other concurrent users of the same port. Also we allow the specified
// ports to be multiplexed, we just strip of the sub port since ultimately we only need to check if the
// base is NAT'd.
nPortMin = MultiplexedSocketProvider.getBasePort(nPortMin);
nPortMax = Math.max(nPortMin, MultiplexedSocketProvider.getBasePort(nPortMax));
InetSocketAddress addrSockBase = new InetSocketAddress(addr, nPortMin);
if (setNAT.contains(addrSockBase) || // test if someone has already verified this range (from the base)
setNAT.contains(new InetSocketAddress(addr, 0))) // or has verified that all ports appear to be mapped
{
return true;
}
try (Timeout t = Timeout.after(cMillis))
{
try (ServerSocket server = TcpSocketProvider.MULTIPLEXED.openServerSocket())
{
for (int nPort = nPortMin; nPort <= nPortMax && !server.isBound(); ++nPort)
{
try
{
// bind to ephemeral address, and specified port
server.bind(new InetSocketAddress32(MultiplexedSocketProvider.getPort(nPort, 0)));
}
catch (IOException e)
{
if (nPort == nPortMax)
{
// we couldn't find any free ports on which to conduct the test
return false;
}
// else; try next port
}
}
// test if we can connect to the NAT address and port and receive the connection on our binding
try (Socket clientOut = TcpSocketProvider.MULTIPLEXED.openSocket())
{
Blocking.connect(clientOut, new InetSocketAddress32(addr, server.getLocalPort()));
final byte[] MAGIC = generateMagic();
try (OutputStream out = clientOut.getOutputStream())
{
out.write(MAGIC);
}
// This timeout would seem to be allowed to be set much shorter, we've already established the
// connection above so it should be ready to come out without any delay. We don't want to assume
// too much about how the underlying sockets work, and it is possible that the inbound connection
// isn't available immediately. In fact since we use multiplexed sockets this is quite true as we
// have to wait for the header, and connect finishing only ensures that the header has been sent,
// it may not have yet been received, thus the full timeout is quite relevant.
server.setSoTimeout((int) Timeout.remainingTimeoutMillis());
try (Socket clientIn = server.accept())
{
byte[] abIn = new byte[MAGIC.length];
try (InputStream is = clientIn.getInputStream())
{
// either 8 bytes were read or the connection was closed; regardless check what was read
// and compare to what is expected
is.read(abIn);
}
// verify that the connection is truly from us by ensuring the generated 'magic' payload
// is what we receive; we can not assume any information about source ip / port as when
// routed through a NAT that NAT can rewrite the source IP to appear like it is the NAT
// host in addition to using a different port
if (Arrays.equals(abIn, MAGIC))
{
// cache the addr (with base port) to avoid all this mess on future checks
// we don't cache failures as most failures would be timeouts which could be transient
setNAT.add(addrSockBase);
// we add NAT addresses to the start of the cached bindable address list to give them
// priority when used as a source in #getRoutes
synchronized (s_dequeAddrBindable)
{
InetAddress addrIP = addrSockBase.getAddress();
if (!s_dequeAddrBindable.contains(addrIP))
{
s_dequeAddrBindable.addFirst(addrIP);
}
}
return true;
}
return false;
}
}
}
}
catch (IOException | InterruptedException e)
{
return false;
}
}
/**
* Return an InetAddress object given the raw IP address.
*
* @param abAddr the raw IP address in network byte order
*
* @return the InetAddress object
*
* @throws UnknownHostException if no match can be found
*/
public static InetAddress getByAddress(byte[] abAddr)
throws UnknownHostException
{
if (abAddr == null)
{
return null;
}
return InetAddress.getByAddress(abAddr);
}
/**
* Converts an IPv4 compatible address to a long value.
*
* @param addr an instance of InetAddress to convert to a long
*
* @return a long value holding the IPv4 address
*/
public static long toLong(InetAddress addr)
{
byte[] ab = addr.getAddress();
int of = ab.length == 4 ? 0 : 12;
return ((((long) ab[of + 0]) & 0xFFL) << 24)
| ((((long) ab[of + 1]) & 0xFFL) << 16)
| ((((long) ab[of + 2]) & 0xFFL) << 8)
| ((((long) ab[of + 3]) & 0xFFL) );
}
/**
* Select the appropriate source addresses for connecting to the specified destination addresses.
*
* @param collSource the source addresses
* @param collDest the destination addresses
*
* @return the source addresses which are believed to have routes to the destinations
*/
public static Collection getRoutes(Iterable collSource, Iterable collDest)
{
// find source address with the largest IP overlap with a destination address, i.e. most likely on the same subnet
List listAddr = new ArrayList<>();
int cbBest = 0;
for (InetAddress addrDest : collDest)
{
byte[] abDest = addrDest.getAddress();
for (InetAddress addrSrc : collSource)
{
byte[] abSrc = addrSrc.getAddress();
int ofSrc = 0;
int ofDest = 0;
if (abDest.length != abSrc.length)
{
// comparing ipv6 vs ipv4, but it could be an ipv6 encoded ipv4, skip over leading zeros
for (; ofDest < abDest.length && abDest[ofDest] == 0; ++ofDest);
for (; ofSrc < abSrc.length && abSrc [ofSrc] == 0; ++ofSrc);
if (abDest.length - ofDest != abSrc.length - ofSrc)
{
continue; // not comparable; move onto next source
}
}
// Note: the following comparison is byte based, technically it should be bit based though subnets
// are in practice never really split that way
int cbEqual = 0;
for (int cb = abSrc.length - ofSrc; cbEqual < cb && abSrc[ofSrc + cbEqual] == abDest[ofDest + cbEqual]; ++cbEqual);
if (cbEqual >= cbBest)
{
// addrSrc is at least as good as anything we've found so far
// if it better then toss all others
if (cbEqual > cbBest)
{
cbBest = cbEqual;
listAddr.clear(); // discard all worse matches
}
listAddr.add(addrSrc);
}
}
}
return listAddr;
}
// ----- helper methods -------------------------------------------------
/**
* Set the specified number of bits of a byte array representing a subnet
* mask.
*
* @param ab the array to fill
* @param cBits the number of bits to set
*/
protected static void setSubnetMask(byte[] ab, int cBits)
{
if (ab.length * 8 < cBits)
{
throw new IllegalArgumentException("subnet mask of " + cBits +
" exceeds address length of " + ab.length * 8);
}
int cBytes = cBits / 8;
for (int i = 0; i < cBytes; ++i)
{
ab[i] = -1;
}
for (int i = 0, c = cBits % 8; i < c; ++i)
{
ab[cBytes] |= 1 << (7 - i);
}
}
/**
* Return a unique byte array.
*
* @return a unique byte array
*/
protected static byte[] generateMagic()
{
int nRnd = ThreadLocalRandom.current().nextInt();
return new byte[]
{
0x52, 0x41, 0x4A, 0x41,
(byte) ((nRnd >> 24) & 0xFF),
(byte) ((nRnd >> 16) & 0xFF),
(byte) ((nRnd >> 8) & 0xFF),
(byte) ((nRnd) & 0xFF)
};
}
/**
* Return true if the given address is a local address.
*
* @param addr the InetAddress to check
*
* @return true if the given address is a local address
*
* @throws SocketException
*/
protected static boolean checkLocalAddress(InetAddress addr)
throws SocketException
{
long cTimeout = INETADDRESS_TIMEOUT.get();
if (SafeClock.INSTANCE.getSafeTimeMillis() > cTimeout)
{
try
{
Set setAddresses = new HashSet<>();
for (Enumeration enmr = NetworkInterface.getNetworkInterfaces(); enmr.hasMoreElements(); )
{
NetworkInterface iface = enmr.nextElement();
for (Enumeration enmrAddr = iface.getInetAddresses(); enmrAddr.hasMoreElements(); )
{
setAddresses.add(enmrAddr.nextElement());
}
}
if (INETADDRESS_TIMEOUT.compareAndSet(cTimeout, SafeClock.INSTANCE.getSafeTimeMillis() + INETADDRESS_REFRESH))
{
LOCAL_ADDRESSES.clear();
LOCAL_ADDRESSES.addAll(setAddresses);
}
}
catch (SocketException e)
{ // denigrates to NI.getByInetAddress
}
}
// check our cache of addresses and if not present delegate to the
// more expensive check
return LOCAL_ADDRESSES.contains(addr) ||
NetworkInterface.getByInetAddress(addr) != null;
}
// ----- data fields and constants --------------------------------------
/**
* The value of system property "java.net.preferIPv4Stack".
*
* @see
* Networking IPv6 User Guide
*/
public static final boolean PreferIPv4Stack = Boolean.getBoolean("java.net.preferIPv4Stack");
/**
* The value of system property "java.net.preferIPv6Addresses".
*
* @see
* Networking IPv6 User Guide
*/
public static final boolean PreferIPv6Addresses = Boolean.getBoolean("java.net.preferIPv6Addresses");
/**
* The wildcard address.
*/
public static final InetAddress ADDR_ANY = new InetSocketAddress((InetAddress) null, 0).getAddress();
/**
* The absolute time that the cache of InetAddresses should be considered
* valid. After this point they are stale and should be refreshed to be
* aligned with what the OS reports.
*/
private static final AtomicLong INETADDRESS_TIMEOUT = new AtomicLong();
/**
* The data structure used to hold the InetAddresses.
*/
private static final Set LOCAL_ADDRESSES = new CopyOnWriteArraySet<>();
/**
* Cached localhost address.
*/
private static InetAddress s_addrLocalhost;
/**
* Cached local NAT addresses.
*/
private static final AtomicReference> s_refSetNAT = new AtomicReference<>();
/**
* Cached local (and NAT) bindable addresses.
*/
private static final Deque s_dequeAddrBindable = new ConcurrentLinkedDeque<>();
/**
* True once s_dequeAddrBindable has been populated with local dddresses.
*/
private static volatile boolean s_fDequeAddrBindablePopulated;
/**
* The default timeout for testing if an address is NAT'd.
*
* We use a rather high default value here. The reason is that it is assumed that the isNatLocalAddress will
* normally pass as the caller must have had some indication which would suggest that it is in fact a local NAT
* address. The test will involve going out through the NAT, and as such could have packet loss so we want to
* allow TCP enough time to retry a connect if needed. So successes should be fast, but we allow them to be slow
* in case of packet loss. A failure will also normally be fast. If the NAT address references another machine
* and that machine has no listener on the port then we'll get a reject and be done quickly. So we're only slow
* on a failure if there we manage to connect to someone else, or if our connect attempt has no accept or reject.
*/
protected static final long NAT_CHECK_TIMEOUT = new Duration(
System.getProperty(InetAddresses.class.getName() + ".natCheckTimeout", "10s"))
.as(Duration.Magnitude.MILLI);
/**
* The default time the cache of local addresses is assumed to be correct.
* Once this threshold is reached the local state is brought back in sync
* with all local inet addresses.
*
* The primary benefit of this is to avoid the horrendously expensive call
* to {@link NetworkInterface#getByInetAddress(InetAddress)} on windows which
* should have no ill effects on linux.
*/
protected static final long INETADDRESS_REFRESH = new Duration(
System.getProperty(InetAddresses.class.getName() + ".localAddressCacheTimeout", "1h"))
.as(Duration.Magnitude.MILLI);
}