org.apache.sshd.common.util.net.SshdSocketAddress Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
/*
* 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.sshd.common.util.net;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
*
* A simple socket address holding the host name and port number. The reason it does not extend
* {@link InetSocketAddress} is twofold:
*
*
* -
*
* The {@link InetSocketAddress} performs a DNS resolution on the provided host name - which we don't want do use until
* we want to create a connection using this address (thus the {@link #toInetSocketAddress()} call which executes this
* query
*
*
*
* -
*
* If empty host name is provided we replace it with the any address of 0.0.0.0
*
*
*
*
* @author Apache MINA SSHD Project
*/
public class SshdSocketAddress extends SocketAddress {
public static final String LOCALHOST_NAME = "localhost";
public static final String LOCALHOST_IPV4 = "127.0.0.1";
public static final String IPV4_ANYADDR = "0.0.0.0";
public static final Set WELL_KNOWN_IPV4_ADDRESSES = Collections.unmodifiableSet(
new LinkedHashSet<>(
Arrays.asList(LOCALHOST_IPV4, IPV4_ANYADDR)));
// 10.0.0.0 - 10.255.255.255
public static final String PRIVATE_CLASS_A_PREFIX = "10.";
// 172.16.0.0 - 172.31.255.255
public static final String PRIVATE_CLASS_B_PREFIX = "172.";
// 192.168.0.0 - 192.168.255.255
public static final String PRIVATE_CLASS_C_PREFIX = "192.168.";
// 100.64.0.0 - 100.127.255.255
public static final String CARRIER_GRADE_NAT_PREFIX = "100.";
// The IPv4 broadcast address
public static final String BROADCAST_ADDRESS = "255.255.255.255";
/** Max. number of hex groups (separated by ":") in an IPV6 address */
public static final int IPV6_MAX_HEX_GROUPS = 8;
/** Max. hex digits in each IPv6 group */
public static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
public static final String IPV6_LONG_ANY_ADDRESS = "0:0:0:0:0:0:0:0";
public static final String IPV6_SHORT_ANY_ADDRESS = "::";
public static final String IPV6_LONG_LOCALHOST = "0:0:0:0:0:0:0:1";
public static final String IPV6_SHORT_LOCALHOST = "::1";
public static final Set WELL_KNOWN_IPV6_ADDRESSES = Collections.unmodifiableSet(
new LinkedHashSet<>(
Arrays.asList(
IPV6_LONG_LOCALHOST, IPV6_SHORT_LOCALHOST,
IPV6_LONG_ANY_ADDRESS, IPV6_SHORT_ANY_ADDRESS)));
/**
* A dummy placeholder that can be used instead of {@code null}s
*/
public static final SshdSocketAddress LOCALHOST_ADDRESS = new SshdSocketAddress(LOCALHOST_IPV4, 0);
/**
* Compares {@link InetAddress}-es according to their {@link InetAddress#getHostAddress()} value case
* insensitive
*
* @see #toAddressString(InetAddress)
*/
public static final Comparator BY_HOST_ADDRESS = (a1, a2) -> {
String n1 = GenericUtils.trimToEmpty(toAddressString(a1));
String n2 = GenericUtils.trimToEmpty(toAddressString(a2));
return String.CASE_INSENSITIVE_ORDER.compare(n1, n2);
};
/**
* Compares {@link SocketAddress}-es according to their host case insensitive and if equals, then according
* to their port value (if any)
*
* @see #toAddressString(SocketAddress)
* @see #toAddressPort(SocketAddress)
*/
public static final Comparator BY_HOST_AND_PORT = (a1, a2) -> {
String n1 = GenericUtils.trimToEmpty(toAddressString(a1));
String n2 = GenericUtils.trimToEmpty(toAddressString(a2));
int nRes = String.CASE_INSENSITIVE_ORDER.compare(n1, n2);
if (nRes != 0) {
return nRes;
}
int p1 = toAddressPort(a1);
int p2 = toAddressPort(a2);
nRes = Integer.compare(p1, p2);
if (nRes != 0) {
return nRes;
}
return 0;
};
private static final long serialVersionUID = 6461645947151952729L;
private final String hostName;
private final int port;
public SshdSocketAddress(int port) {
this(IPV4_ANYADDR, port);
}
public SshdSocketAddress(InetSocketAddress addr) {
Objects.requireNonNull(addr, "No address provided");
String host = addr.getHostString();
hostName = GenericUtils.isEmpty(host) ? IPV4_ANYADDR : host;
port = addr.getPort();
ValidateUtils.checkTrue(port >= 0, "Port must be >= 0: %d", port);
}
public SshdSocketAddress(String hostName, int port) {
Objects.requireNonNull(hostName, "Host name may not be null");
this.hostName = GenericUtils.isEmpty(hostName) ? IPV4_ANYADDR : hostName;
ValidateUtils.checkTrue(port >= 0, "Port must be >= 0: %d", port);
this.port = port;
}
public String getHostName() {
return hostName;
}
public int getPort() {
return port;
}
public InetSocketAddress toInetSocketAddress() {
return new InetSocketAddress(getHostName(), getPort());
}
@Override
public String toString() {
return getHostName() + ":" + getPort();
}
protected boolean isEquivalent(SshdSocketAddress that) {
if (that == null) {
return false;
} else if (that == this) {
return true;
} else {
return (this.getPort() == that.getPort())
&& isEquivalentHostName(this.getHostName(), that.getHostName(), false);
}
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
return isEquivalent((SshdSocketAddress) o);
}
@Override
public int hashCode() {
return GenericUtils.hashCode(getHostName(), Boolean.FALSE) + 31 * Integer.hashCode(getPort());
}
/**
* Returns the first external network address assigned to this machine or null if one is not found.
*
* @return Inet4Address associated with an external interface DevNote: We actually return InetAddress here, as
* Inet4Addresses are final and cannot be mocked.
*/
public static InetAddress getFirstExternalNetwork4Address() {
List extends InetAddress> addresses = getExternalNetwork4Addresses();
return (GenericUtils.size(addresses) > 0) ? addresses.get(0) : null;
}
/**
* @return a {@link List} of local network addresses which are not multicast or localhost sorted according to
* {@link #BY_HOST_ADDRESS}
*/
public static List getExternalNetwork4Addresses() {
List addresses = new ArrayList<>();
try {
for (Enumeration nets = NetworkInterface.getNetworkInterfaces();
(nets != null) && nets.hasMoreElements();) {
NetworkInterface netint = nets.nextElement();
/*
* TODO - uncomment when 1.5 compatibility no longer required if (!netint.isUp()) { continue; // ignore
* non-running interfaces }
*/
for (Enumeration inetAddresses = netint.getInetAddresses();
(inetAddresses != null) && inetAddresses.hasMoreElements();) {
InetAddress inetAddress = inetAddresses.nextElement();
if (isValidHostAddress(inetAddress)) {
addresses.add(inetAddress);
}
}
}
} catch (SocketException e) {
// swallow
}
if (GenericUtils.size(addresses) > 1) {
Collections.sort(addresses, BY_HOST_ADDRESS);
}
return addresses;
}
/**
* @param addr The {@link InetAddress} to be verified
* @return
*
* true
if the address is:
*
*
*
* - Not {@code null}
* - An {@link Inet4Address}
* - Not link local
* - Not a multicast
* - Not a loopback
*
* @see InetAddress#isLinkLocalAddress()
* @see InetAddress#isMulticastAddress()
* @see InetAddress#isMulticastAddress()
*/
public static boolean isValidHostAddress(InetAddress addr) {
if (addr == null) {
return false;
}
if (addr.isLinkLocalAddress()) {
return false;
}
if (addr.isMulticastAddress()) {
return false;
}
if (!(addr instanceof Inet4Address)) {
return false; // TODO add support for IPv6 - see SSHD-746
}
return !isLoopback(addr);
}
/**
* @param addr The {@link InetAddress} to be considered
* @return true
if the address is a loopback one. Note: if
* {@link InetAddress#isLoopbackAddress()} returns false
the address string is
* checked
* @see #toAddressString(InetAddress)
* @see #isLoopback(String)
*/
public static boolean isLoopback(InetAddress addr) {
if (addr == null) {
return false;
}
if (addr.isLoopbackAddress()) {
return true;
}
String ip = toAddressString(addr);
return isLoopback(ip);
}
/**
* @param ip IP value to be tested
* @return true
if the IP is "localhost" or "127.x.x.x".
*/
public static boolean isLoopback(String ip) {
if (GenericUtils.isEmpty(ip)) {
return false;
}
if (LOCALHOST_NAME.equals(ip)) {
return true;
}
return isIPv4LoopbackAddress(ip) || isIPv6LoopbackAddress(ip);
}
public static boolean isIPv4LoopbackAddress(String ip) {
if (GenericUtils.isEmpty(ip)) {
return false;
}
if (LOCALHOST_IPV4.equals(ip)) {
return true; // most used
}
String[] values = GenericUtils.split(ip, '.');
if (GenericUtils.length(values) != 4) {
return false;
}
for (int index = 0; index < values.length; index++) {
String val = values[index];
if (!isValidIPv4AddressComponent(val)) {
return false;
}
if (index == 0) {
int number = Integer.parseInt(val);
if (number != 127) {
return false;
}
}
}
return true;
}
public static boolean isIPv6LoopbackAddress(String ip) {
// TODO add more patterns - e.g., https://tools.ietf.org/id/draft-smith-v6ops-larger-ipv6-loopback-prefix-04.html
return IPV6_LONG_LOCALHOST.equals(ip) || IPV6_SHORT_LOCALHOST.equals(ip);
}
public static boolean isEquivalentHostName(String h1, String h2, boolean allowWildcard) {
if (GenericUtils.safeCompare(h1, h2, false) == 0) {
return true;
}
if (allowWildcard) {
return isWildcardAddress(h1) || isWildcardAddress(h2);
}
return false;
}
public static boolean isLoopbackAlias(String h1, String h2) {
return (LOCALHOST_NAME.equals(h1) && isLoopback(h2))
|| (LOCALHOST_NAME.equals(h2) && isLoopback(h1));
}
public static boolean isWildcardAddress(String addr) {
return IPV4_ANYADDR.equalsIgnoreCase(addr)
|| IPV6_LONG_ANY_ADDRESS.equalsIgnoreCase(addr)
|| IPV6_SHORT_ANY_ADDRESS.equalsIgnoreCase(addr);
}
public static SshdSocketAddress toSshdSocketAddress(SocketAddress addr) {
if (addr == null) {
return null;
} else if (addr instanceof SshdSocketAddress) {
return (SshdSocketAddress) addr;
} else if (addr instanceof InetSocketAddress) {
InetSocketAddress isockAddress = (InetSocketAddress) addr;
return new SshdSocketAddress(isockAddress.getHostString(), isockAddress.getPort());
} else {
throw new UnsupportedOperationException("Cannot convert " + addr.getClass().getSimpleName()
+ "=" + addr + " to " + SshdSocketAddress.class.getSimpleName());
}
}
public static String toAddressString(SocketAddress addr) {
if (addr == null) {
return null;
} else if (addr instanceof InetSocketAddress) {
return ((InetSocketAddress) addr).getHostString();
} else if (addr instanceof SshdSocketAddress) {
return ((SshdSocketAddress) addr).getHostName();
} else {
return addr.toString();
}
}
/**
* Attempts to resolve the port value
*
* @param addr The {@link SocketAddress} to examine
* @return The associated port value - negative if failed to resolve
*/
public static int toAddressPort(SocketAddress addr) {
if (addr instanceof InetSocketAddress) {
return ((InetSocketAddress) addr).getPort();
} else if (addr instanceof SshdSocketAddress) {
return ((SshdSocketAddress) addr).getPort();
} else {
return -1;
}
}
/**
*
* Converts a {@code SocketAddress} into an {@link InetSocketAddress} if possible:
*
*
*
* - If already an {@link InetSocketAddress} then cast it as such
* - If an {@code SshdSocketAddress} then invoke {@link #toInetSocketAddress()}
* - Otherwise, throw an exception
*
*
* @param remoteAddress The {@link SocketAddress} - ignored if {@code null}
* @return The {@link InetSocketAddress} instance
* @throws ClassCastException if argument is not already an {@code InetSocketAddress} or a {@code SshdSocketAddress}
*/
public static InetSocketAddress toInetSocketAddress(SocketAddress remoteAddress) {
if (remoteAddress == null) {
return null;
} else if (remoteAddress instanceof InetSocketAddress) {
return (InetSocketAddress) remoteAddress;
} else if (remoteAddress instanceof SshdSocketAddress) {
return ((SshdSocketAddress) remoteAddress).toInetSocketAddress();
} else {
throw new ClassCastException("Unknown remote address type: " + remoteAddress);
}
}
public static String toAddressString(InetAddress addr) {
String ip = (addr == null) ? null : addr.toString();
if (GenericUtils.isEmpty(ip)) {
return null;
} else {
return ip.replaceAll(".*/", "");
}
}
public static boolean isIPv4Address(String addr) {
addr = GenericUtils.trimToEmpty(addr);
if (GenericUtils.isEmpty(addr)) {
return false;
}
if (WELL_KNOWN_IPV4_ADDRESSES.contains(addr)) {
return true;
}
String[] comps = GenericUtils.split(addr, '.');
if (GenericUtils.length(comps) != 4) {
return false;
}
for (String c : comps) {
if (!isValidIPv4AddressComponent(c)) {
return false;
}
}
return true;
}
/**
* Checks if the address is one of the allocated private blocks
*
* @param addr The address string
* @return {@code true} if this is one of the allocated private blocks. Note: it assumes that the
* address string is indeed an IPv4 address
* @see #isIPv4Address(String)
* @see #PRIVATE_CLASS_A_PREFIX
* @see #PRIVATE_CLASS_B_PREFIX
* @see #PRIVATE_CLASS_C_PREFIX
* @see Wiki page
*/
public static boolean isPrivateIPv4Address(String addr) {
if (GenericUtils.isEmpty(addr)) {
return false;
}
if (addr.startsWith(PRIVATE_CLASS_A_PREFIX) || addr.startsWith(PRIVATE_CLASS_C_PREFIX)) {
return true;
}
// for 172.x.x.x we need further checks
if (!addr.startsWith(PRIVATE_CLASS_B_PREFIX)) {
return false;
}
int nextCompPos = addr.indexOf('.', PRIVATE_CLASS_B_PREFIX.length());
if (nextCompPos <= PRIVATE_CLASS_B_PREFIX.length()) {
return false;
}
String value = addr.substring(PRIVATE_CLASS_B_PREFIX.length(), nextCompPos);
if (!isValidIPv4AddressComponent(value)) {
return false;
}
int v = Integer.parseInt(value);
return (v >= 16) && (v <= 31);
}
/**
* @param addr The address to be checked
* @return {@code true} if the address is in the 100.64.0.0/10 range
* @see RFC6598
*/
public static boolean isCarrierGradeNatIPv4Address(String addr) {
if (GenericUtils.isEmpty(addr)) {
return false;
}
if (!addr.startsWith(CARRIER_GRADE_NAT_PREFIX)) {
return false;
}
int nextCompPos = addr.indexOf('.', CARRIER_GRADE_NAT_PREFIX.length());
if (nextCompPos <= CARRIER_GRADE_NAT_PREFIX.length()) {
return false;
}
String value = addr.substring(CARRIER_GRADE_NAT_PREFIX.length(), nextCompPos);
if (!isValidIPv4AddressComponent(value)) {
return false;
}
int v = Integer.parseInt(value);
return (v >= 64) && (v <= 127);
}
/**
*
* Checks if the provided argument is a valid IPv4 address component:
*
*
*
* - Not {@code null}/empty
* - Has at most 3 digits
* - Its value is ≤ 255
*
*
* @param c The {@link CharSequence} to be validate
* @return {@code true} if valid IPv4 address component
*/
public static boolean isValidIPv4AddressComponent(CharSequence c) {
if (GenericUtils.isEmpty(c) || (c.length() > 3)) {
return false;
}
char ch = c.charAt(0);
if ((ch < '0') || (ch > '9')) {
return false;
}
if (!NumberUtils.isIntegerNumber(c)) {
return false;
}
int v = Integer.parseInt(c.toString());
return (v >= 0) && (v <= 255);
}
// Based on org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address
public static boolean isIPv6Address(String address) {
address = GenericUtils.trimToEmpty(address);
if (GenericUtils.isEmpty(address)) {
return false;
}
if (WELL_KNOWN_IPV6_ADDRESSES.contains(address)) {
return true;
}
boolean containsCompressedZeroes = address.contains("::");
if (containsCompressedZeroes && (address.indexOf("::") != address.lastIndexOf("::"))) {
return false;
}
if (((address.indexOf(':') == 0) && (!address.startsWith("::")))
|| (address.endsWith(":") && (!address.endsWith("::")))) {
return false;
}
String[] splitOctets = GenericUtils.split(address, ':');
List octetList = new ArrayList<>(Arrays.asList(splitOctets));
if (containsCompressedZeroes) {
if (address.endsWith("::")) {
// String.split() drops ending empty segments
octetList.add("");
} else if (address.startsWith("::") && (!octetList.isEmpty())) {
octetList.remove(0);
}
}
int numOctests = octetList.size();
if (numOctests > IPV6_MAX_HEX_GROUPS) {
return false;
}
int validOctets = 0;
int emptyOctets = 0; // consecutive empty chunks
for (int index = 0; index < numOctests; index++) {
String octet = octetList.get(index);
int pos = octet.indexOf('%'); // is it a zone index
if (pos >= 0) {
// zone index must come last
if (index != (numOctests - 1)) {
return false;
}
octet = (pos > 0) ? octet.substring(0, pos) : "";
}
int octetLength = octet.length();
if (octetLength == 0) {
emptyOctets++;
if (emptyOctets > 1) {
return false;
}
validOctets++;
continue;
}
emptyOctets = 0;
// Is last chunk an IPv4 address?
if ((index == (numOctests - 1)) && (octet.indexOf('.') > 0)) {
if (!isIPv4Address(octet)) {
return false;
}
validOctets += 2;
continue;
}
if (octetLength > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
return false;
}
int octetInt = 0;
try {
octetInt = Integer.parseInt(octet, 16);
} catch (NumberFormatException e) {
return false;
}
if ((octetInt < 0) || (octetInt > 0x000ffff)) {
return false;
}
validOctets++;
}
if ((validOctets > IPV6_MAX_HEX_GROUPS)
|| ((validOctets < IPV6_MAX_HEX_GROUPS) && (!containsCompressedZeroes))) {
return false;
}
return true;
}
public static V findByOptionalWildcardAddress(Map map, SshdSocketAddress address) {
Map.Entry entry = findMatchingOptionalWildcardEntry(map, address);
return (entry == null) ? null : entry.getValue();
}
public static V removeByOptionalWildcardAddress(Map map, SshdSocketAddress address) {
Map.Entry entry = findMatchingOptionalWildcardEntry(map, address);
return (entry == null) ? null : map.remove(entry.getKey());
}
public static Map.Entry findMatchingOptionalWildcardEntry(
Map map, SshdSocketAddress address) {
if (MapEntryUtils.isEmpty(map) || (address == null)) {
return null;
}
String hostName = address.getHostName();
Map.Entry candidate = null;
for (Map.Entry e : map.entrySet()) {
SshdSocketAddress a = e.getKey();
if (a.getPort() != address.getPort()) {
continue;
}
String candidateName = a.getHostName();
if (hostName.equalsIgnoreCase(candidateName)) {
return e; // If found exact match then use it
}
if (isEquivalentHostName(hostName, candidateName, true)) {
if (candidate != null) {
throw new IllegalStateException("Multiple candidate matches for " + address + ": " + candidate + ", " + e);
}
candidate = e;
}
}
return candidate;
}
}