org.wildfly.common.net.CidrAddress 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).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.wildfly.common.net;
import static java.lang.Integer.signum;
import static java.lang.Math.min;
import java.io.Serializable;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import org.wildfly.common.Assert;
import org.wildfly.common._private.CommonMessages;
import org.wildfly.common.math.HashMath;
/**
* A Classless Inter-Domain Routing address. This is the combination of an IP address and a netmask.
*
* @author David M. Lloyd
*/
public final class CidrAddress implements Serializable, Comparable {
private static final long serialVersionUID = - 6548529324373774149L;
/**
* The CIDR address representing all IPv4 addresses.
*/
public static final CidrAddress INET4_ANY_CIDR = new CidrAddress(Inet.INET4_ANY, 0);
/**
* The CIDR address representing all IPv6 addresses.
*/
public static final CidrAddress INET6_ANY_CIDR = new CidrAddress(Inet.INET6_ANY, 0);
private final InetAddress networkAddress;
private final byte[] cachedBytes;
private final int netmaskBits;
private Inet4Address broadcast;
private String toString;
private int hashCode;
private CidrAddress(final InetAddress networkAddress, final int netmaskBits) {
this.networkAddress = networkAddress;
cachedBytes = networkAddress.getAddress();
this.netmaskBits = netmaskBits;
}
/**
* Create a new CIDR address.
*
* @param networkAddress the network address (must not be {@code null})
* @param netmaskBits the netmask bits (0-32 for IPv4, or 0-128 for IPv6)
* @return the CIDR address (not {@code null})
*/
public static CidrAddress create(InetAddress networkAddress, int netmaskBits) {
Assert.checkNotNullParam("networkAddress", networkAddress);
Assert.checkMinimumParameter("netmaskBits", 0, netmaskBits);
int scopeId = Inet.getScopeId(networkAddress);
if (networkAddress instanceof Inet4Address) {
Assert.checkMaximumParameter("netmaskBits", 32, netmaskBits);
if (netmaskBits == 0) {
return INET4_ANY_CIDR;
}
} else if (networkAddress instanceof Inet6Address) {
Assert.checkMaximumParameter("netmaskBits", 128, netmaskBits);
if (netmaskBits == 0 && scopeId == 0) {
return INET6_ANY_CIDR;
}
} else {
throw Assert.unreachableCode();
}
final byte[] bytes = networkAddress.getAddress();
maskBits0(bytes, netmaskBits);
String name = Inet.toOptimalString(bytes);
try {
if (bytes.length == 4) {
return new CidrAddress(InetAddress.getByAddress(name, bytes), netmaskBits);
} else {
return new CidrAddress(Inet6Address.getByAddress(name, bytes, scopeId), netmaskBits);
}
} catch (UnknownHostException e) {
throw Assert.unreachableCode();
}
}
/**
* Create a new CIDR address.
*
* @param addressBytes the network address bytes (must not be {@code null}, must be 4 bytes for IPv4 or 16 bytes for IPv6)
* @param netmaskBits the netmask bits (0-32 for IPv4, or 0-128 for IPv6)
* @return the CIDR address (not {@code null})
*/
public static CidrAddress create(byte[] addressBytes, int netmaskBits) {
return create(addressBytes, netmaskBits, true);
}
static CidrAddress create(byte[] addressBytes, int netmaskBits, boolean clone) {
Assert.checkNotNullParam("networkAddress", addressBytes);
Assert.checkMinimumParameter("netmaskBits", 0, netmaskBits);
final int length = addressBytes.length;
if (length == 4) {
Assert.checkMaximumParameter("netmaskBits", 32, netmaskBits);
if (netmaskBits == 0) {
return INET4_ANY_CIDR;
}
} else if (length == 16) {
Assert.checkMaximumParameter("netmaskBits", 128, netmaskBits);
if (netmaskBits == 0) {
return INET6_ANY_CIDR;
}
} else {
throw CommonMessages.msg.invalidAddressBytes(length);
}
final byte[] bytes = clone ? addressBytes.clone() : addressBytes;
maskBits0(bytes, netmaskBits);
String name = Inet.toOptimalString(bytes);
try {
return new CidrAddress(InetAddress.getByAddress(name, bytes), netmaskBits);
} catch (UnknownHostException e) {
throw Assert.unreachableCode();
}
}
/**
* Determine if this CIDR address matches the given address.
*
* @param address the address to test
* @return {@code true} if the address matches, {@code false} otherwise
*/
public boolean matches(InetAddress address) {
Assert.checkNotNullParam("address", address);
if (address instanceof Inet4Address) {
return matches((Inet4Address) address);
} else if (address instanceof Inet6Address) {
return matches((Inet6Address) address);
} else {
throw Assert.unreachableCode();
}
}
/**
* Determine if this CIDR address matches the given address bytes.
*
* @param bytes the address bytes to test
* @return {@code true} if the address bytes match, {@code false} otherwise
*/
public boolean matches(byte[] bytes) {
return matches(bytes, 0);
}
/**
* Determine if this CIDR address matches the given address bytes.
*
* @param bytes the address bytes to test
* @param scopeId the scope ID, or 0 to match no scope
* @return {@code true} if the address bytes match, {@code false} otherwise
*/
public boolean matches(byte[] bytes, int scopeId) {
Assert.checkNotNullParam("bytes", bytes);
return cachedBytes.length == bytes.length && (getScopeId() == 0 || getScopeId() == scopeId) && bitsMatch(cachedBytes, bytes, netmaskBits);
}
/**
* Determine if this CIDR address matches the given address.
*
* @param address the address to test
* @return {@code true} if the address matches, {@code false} otherwise
*/
public boolean matches(Inet4Address address) {
Assert.checkNotNullParam("address", address);
return networkAddress instanceof Inet4Address && bitsMatch(cachedBytes, address.getAddress(), netmaskBits);
}
/**
* Determine if this CIDR address matches the given address.
*
* @param address the address to test
* @return {@code true} if the address matches, {@code false} otherwise
*/
public boolean matches(Inet6Address address) {
Assert.checkNotNullParam("address", address);
return networkAddress instanceof Inet6Address && bitsMatch(cachedBytes, address.getAddress(), netmaskBits)
&& (getScopeId() == 0 || getScopeId() == address.getScopeId());
}
/**
* Determine if this CIDR address matches the given CIDR address. This will be true only when the given CIDR
* block is wholly enclosed by this one.
*
* @param address the address to test
* @return {@code true} if the given block is enclosed by this one, {@code false} otherwise
*/
public boolean matches(CidrAddress address) {
Assert.checkNotNullParam("address", address);
return netmaskBits <= address.netmaskBits && matches(address.cachedBytes)
&& (getScopeId() == 0 || getScopeId() == address.getScopeId());
}
/**
* Get the network address. The returned address has a resolved name consisting of the most compact valid string
* representation of the network of this CIDR address.
*
* @return the network address (not {@code null})
*/
public InetAddress getNetworkAddress() {
return networkAddress;
}
/**
* Get the broadcast address for this CIDR block. If the block has no broadcast address (either because it is IPv6
* or it is too small) then {@code null} is returned.
*
* @return the broadcast address for this CIDR block, or {@code null} if there is none
*/
public Inet4Address getBroadcastAddress() {
final Inet4Address broadcast = this.broadcast;
if (broadcast == null) {
final int netmaskBits = this.netmaskBits;
if (netmaskBits >= 31) {
// definitely IPv6 or too small
return null;
}
// still maybe IPv6
final byte[] cachedBytes = this.cachedBytes;
if (cachedBytes.length == 4) {
// calculate
if (netmaskBits == 0) {
return this.broadcast = Inet.INET4_BROADCAST;
} else {
final byte[] bytes = maskBits1(cachedBytes.clone(), netmaskBits);
try {
return this.broadcast = (Inet4Address) InetAddress.getByAddress(Inet.toOptimalString(bytes), bytes);
} catch (UnknownHostException e) {
throw Assert.unreachableCode();
}
}
}
return null;
}
return broadcast;
}
/**
* Get the netmask bits. This will be in the range 0-32 for IPv4 addresses, and 0-128 for IPv6 addresses.
*
* @return the netmask bits
*/
public int getNetmaskBits() {
return netmaskBits;
}
/**
* Get the match address scope ID (if it is an IPv6 address).
*
* @return the scope ID, or 0 if there is none or the address is an IPv4 address
*/
public int getScopeId() {
return Inet.getScopeId(getNetworkAddress());
}
public int compareTo(final CidrAddress other) {
Assert.checkNotNullParam("other", other);
if (this == other) return 0;
return compareAddressBytesTo(other.cachedBytes, other.netmaskBits, other.getScopeId());
}
public int compareAddressBytesTo(final byte[] otherBytes, final int otherNetmaskBits, final int scopeId) {
Assert.checkNotNullParam("bytes", otherBytes);
final int otherLength = otherBytes.length;
if (otherLength != 4 && otherLength != 16) {
throw CommonMessages.msg.invalidAddressBytes(otherLength);
}
// IPv4 before IPv6
final byte[] cachedBytes = this.cachedBytes;
int res = signum(cachedBytes.length - otherLength);
if (res != 0) return res;
res = signum(scopeId - getScopeId());
if (res != 0) return res;
// sorted numerically with long matches coming later
final int netmaskBits = this.netmaskBits;
int commonPrefix = min(netmaskBits, otherNetmaskBits);
// compare byte-wise as far as we can
int i = 0;
while (commonPrefix >= 8) {
res = signum((cachedBytes[i] & 0xff) - (otherBytes[i] & 0xff));
if (res != 0) return res;
i++;
commonPrefix -= 8;
}
while (commonPrefix > 0) {
final int bit = 1 << commonPrefix;
res = signum((cachedBytes[i] & bit) - (otherBytes[i] & bit));
if (res != 0) return res;
commonPrefix--;
}
// common prefix is a match; now the shortest mask wins
return signum(netmaskBits - otherNetmaskBits);
}
public boolean equals(final Object obj) {
return obj instanceof CidrAddress && equals((CidrAddress) obj);
}
public boolean equals(final CidrAddress obj) {
return obj == this || obj != null && netmaskBits == obj.netmaskBits && Arrays.equals(cachedBytes, obj.cachedBytes);
}
public int hashCode() {
int hashCode = this.hashCode;
if (hashCode == 0) {
hashCode = HashMath.multiHashOrdered(netmaskBits, Arrays.hashCode(cachedBytes));
if (hashCode == 0) {
hashCode = 1;
}
this.hashCode = hashCode;
}
return hashCode;
}
public String toString() {
final String toString = this.toString;
if (toString == null) {
final int scopeId = getScopeId();
if (scopeId == 0) {
return this.toString = String.format("%s/%d", Inet.toOptimalString(cachedBytes), Integer.valueOf(netmaskBits));
} else {
return this.toString = String.format("%s%%%d/%d", Inet.toOptimalString(cachedBytes), Integer.valueOf(scopeId), Integer.valueOf(netmaskBits));
}
}
return toString;
}
Object writeReplace() {
return new Ser(cachedBytes, netmaskBits);
}
static final class Ser implements Serializable {
private static final long serialVersionUID = 6367919693596329038L;
final byte[] b;
final int m;
Ser(final byte[] b, final int m) {
this.b = b;
this.m = m;
}
Object readResolve() {
return create(b, m, false);
}
}
private static boolean bitsMatch(byte[] address, byte[] test, int bits) {
final int length = address.length;
assert length == test.length;
// bytes are in big-endian form.
int i = 0;
while (bits >= 8 && i < length) {
if (address[i] != test[i]) {
return false;
}
i ++;
bits -= 8;
}
if (bits > 0) {
assert bits < 8;
int mask = 0xff << 8 - bits;
if ((address[i] & 0xff & mask) != (test[i] & 0xff & mask)) {
return false;
}
}
return true;
}
private static byte[] maskBits0(byte[] address, int bits) {
final int length = address.length;
// bytes are in big-endian form.
int i = 0;
while (bits >= 8 && i < length) {
i ++;
bits -= 8;
}
if (bits > 0) {
assert bits < 8;
int mask = 0xff << 8 - bits;
address[i++] &= mask;
}
while (i < length) {
address[i++] = 0;
}
return address;
}
private static byte[] maskBits1(byte[] address, int bits) {
final int length = address.length;
// bytes are in big-endian form.
int i = 0;
while (bits >= 8 && i < length) {
i ++;
bits -= 8;
}
if (bits > 0) {
assert bits < 8;
int mask = 0xff >>> 8 - bits;
address[i++] |= mask;
}
while (i < length) {
address[i++] = (byte) 0xff;
}
return address;
}
}