/*
* 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.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* <P>A simple socket address holding the host name and port number. The reason
* it does not extend {@link InetSocketAddress} is twofold:</P>
* <OL>
* <LI><P>
* 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
* </P></LI>
*
* <LI><P>
* If empty host name is provided we replace it with the <I>any</I>
* address of 0.0.0.0
* </P></LI>
* </OL>
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SshdSocketAddress extends SocketAddress {
public static final String LOCALHOST_NAME = "localhost";
public static final String LOCALHOST_IP = "127.0.0.1";
public static final String IP_ANYADDR = "0.0.0.0";
// 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";
/**
* A dummy placeholder that can be used instead of {@code null}s
*/
public static final SshdSocketAddress LOCALHOST_ADDRESS = new SshdSocketAddress(LOCALHOST_IP, 0);
/**
* Compares {@link InetAddress}-es according to their {@link InetAddress#getHostAddress()}
* value case <U>insensitive</U>
*/
public static final Comparator<InetAddress> 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);
};
private static final long serialVersionUID = 6461645947151952729L;
private final String hostName;
private final int port;
public SshdSocketAddress(int port) {
this(IP_ANYADDR, port);
}
public SshdSocketAddress(String hostName, int port) {
Objects.requireNonNull(hostName, "Host name may not be null");
this.hostName = GenericUtils.isEmpty(hostName) ? IP_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())
&& Objects.equals(this.getHostName(), that.getHostName());
}
}
@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 Objects.hashCode(getHostName()) + 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<InetAddress> getExternalNetwork4Addresses() {
List<InetAddress> addresses = new ArrayList<>();
try {
for (Enumeration<NetworkInterface> 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<InetAddress> 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 <P><code>true</code> if the address is:</P></BR>
* <UL>
* <LI>Not <code>null</code></LI>
* <LI>An {@link Inet4Address}</LI>
* <LI>Not link local</LI>
* <LI>Not a multicast</LI>
* <LI>Not a loopback</LI>
* </UL>
* @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;
}
return !isLoopback(addr);
}
/**
* @param addr The {@link InetAddress} to be considered
* @return <code>true</code> if the address is a loopback one.
* <B>Note:</B> if {@link InetAddress#isLoopbackAddress()}
* returns <code>false</code> the address <U>string</U> 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 <code>true</code> 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) || LOCALHOST_IP.equals(ip)) {
return true;
}
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 String toAddressString(SocketAddress addr) {
if (addr == null) {
return null;
}
if (addr instanceof InetSocketAddress) {
return ((InetSocketAddress) addr).getHostString();
}
if (addr instanceof SshdSocketAddress) {
return ((SshdSocketAddress) addr).getHostName();
}
return addr.toString();
}
/**
* <P>Converts a {@code SocketAddress} into an {@link InetSocketAddress} if possible:</P></BR>
* <UL>
* <LI>If already an {@link InetSocketAddress} then cast it as such</LI>
* <LI>If an {@code SshdSocketAddress} then invoke {@link #toInetSocketAddress()}</LI>
* <LI>Otherwise, throw an exception</LI>
* </UL>
*
* @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) {
if (GenericUtils.isEmpty(addr)) {
return false;
}
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. <B>Note:</B> 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 <A HREF="http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces">Wiki page</A>
*/
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 <A HREF="http://tools.ietf.org/html/rfc6598">RFC6598</A>
*/
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);
}
/**
* <P>Checks if the provided argument is a valid IPv4 address component:</P></BR>
* <UL>
* <LI>Not {@code null}/empty</LI>
* <LI>Has at most 3 <U>digits</U></LI>
* <LI>Its value is ≤ 255</LI>
* </UL>
* @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);
}
}