package org.dcache.util; import static com.google.common.base.Preconditions.checkArgument; import java.io.Serializable; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.regex.Pattern; import static com.google.common.net.InetAddresses.forString; public class Subnet implements Serializable { private static final long serialVersionUID = 9210530422244320383L; private static final Pattern MAPPED_ADDRESS = Pattern.compile("(0{1,4}:){0,12}:?:ffff(((.[0-9]{1,3}){4})|(:([0-9a-fA-F]{1,4})){2})"); private static final Pattern DOCUMENTATION_ADDRESS = Pattern.compile("2001:0?db8:[0-9a-fA-F:]*"); private static final Pattern TOREDO_ADDRESS = Pattern.compile("2001:0{0,4}:[0-9a-fA-F:]*"); private static final String ALL_SUBNET = "all"; private final InetAddress _subnetAddress; private final int _mask; protected Subnet() { _subnetAddress = null; _mask = 0; } protected Subnet(InetAddress subnetAddress, int mask) { int hostBits = (subnetAddress instanceof Inet4Address ? 32 : 128) - mask; BigInteger maskedAddress = new BigInteger(subnetAddress.getAddress()) .shiftRight(hostBits) .shiftLeft(hostBits); InetAddress address; try { address = InetAddress.getByAddress(maskedAddress.toByteArray()); } catch (UnknownHostException uhe) { address = subnetAddress; } _subnetAddress = address; _mask = mask; } @Override public boolean equals(Object other) { if ((other == null) || !(other instanceof Subnet)) { return false; } return other.toString().equals(this.toString()); } @Override public int hashCode() { int hash = 5; hash = 53 * hash + (this._subnetAddress != null ? this._subnetAddress.hashCode() : 0); hash = 53 * hash + this._mask; return hash; } public InetAddress getSubnetAddress() { return _subnetAddress; } public int getMask() { return _mask; } /** * Creates an instance of a subnet from an InetAddress and a mask in CIDR notation. * * @param subnetAddress subnet base address * @param mask mask in CIDR notation (number of topmost relevant bits) * @return an instance of Subnet created from address and mask */ public static Subnet create(InetAddress subnetAddress, int mask) { return new Subnet(subnetAddress, mask); } /** * Creates an instance of a subnet from its netmask notation. * * Examples: "123.45.0.0/24"; "1234:5678:9ABC:DEF0::/64"; 1.2.3.4/255.0.0.0 * IPv6 compatible addresses starting with 2002: and addresses * of the form ::a.b.c.d are supported and will be converted to ipv4 * addresses together with their mask (i.e., the lower 4 bytes are used * as mask). Mapped addresses (::ffff.a.b.c.d) * are converted to ipv4 addresses and should (if at all) be passed * with corresponding matching ipv4 mask. Teredo addresses (addresses * starting with 2001:0000) and special documentation addresses (2001:db8) * cannot be used to create instances of Subnet and will result in an * IllegalArgumentException. * * @param netmaskPattern CIDR notation of the subnet * @return an instance of Subnet created from the netmask pattern * * param netmaskPattern CIDR notation of the subnet * return an instance of Subnet created from the netmask pattern */ public static Subnet create(String netmaskPattern) { String[] netmaskParts = netmaskPattern.split("/"); String hostname = netmaskParts[0]; InetAddress originalAddress = forString(hostname); boolean isIpV6Mask = originalAddress instanceof Inet6Address; // special handling of mapped addresses. Needed because guava's // forString converts these automatically to IPv4. if (MAPPED_ADDRESS.matcher(hostname).matches()) { isIpV6Mask = true; } String originalHostAddress = originalAddress.getHostAddress(); checkArgument(!DOCUMENTATION_ADDRESS.matcher(originalHostAddress).matches(), "Special documentation address '%s' cannot be used to create a Subnet", hostname); checkArgument(!TOREDO_ADDRESS.matcher(originalHostAddress).matches(), "Toredo address '%s' cannot be used to create a Subnet", hostname); InetAddress subnetAddress = IPMatcher.tryConvertToIPv4(originalAddress); int maskBitLength = subnetAddress instanceof Inet4Address ? 32 : 128; if (netmaskParts.length > 1) { String maskbits = netmaskParts[1]; int cidrMask = IPMatcher.convertToCidrIfIsIPv4Mask(maskbits); // if a conversion from IPv6 to IPv4 actually happened if (isIpV6Mask && (maskBitLength == 32)) { cidrMask = cidrMask==128? 32 : cidrMask & 0x1f; } checkArgument(cidrMask <= maskBitLength, "Network mask '%s' in netmask pattern '%s' is too big for IP address '%s'", cidrMask, netmaskPattern, originalAddress); return create(subnetAddress, cidrMask); } else { // if pattern only contains a hostname return create(subnetAddress, maskBitLength); } } /** * @return an instance of Subnet that matches all IPs */ public static Subnet create() { return new Subnet() { private static final long serialVersionUID = 97750694361406752L; @Override public boolean contains(InetAddress inetAddress) { return true; } }; } /** * @param hostname hostname to be checked agains the subnet * @return true if hostname can be evaluated to one or more IPs that are contained in the subnet represented by this instance, otherwise false * @throws UnknownHostException */ public boolean containsHost(String hostname) throws UnknownHostException { return containsAny(InetAddress.getAllByName(hostname)); } /** * @param inetAddresses addresses to be checked against the subnet * @return true if any of the inetAddresses is contained in the subnet represented by this instance, otherwise false */ public boolean containsAny(InetAddress[] inetAddresses) { for (InetAddress inetAddress : inetAddresses) { if (contains(inetAddress)) { return true; } } return false; } /** * @param inetAddress address to be checked against the subnet * @return true if inetAddress is contained in the subnet represented by this instance, otherwise false */ public boolean contains(InetAddress inetAddress) { return IPMatcher.match(inetAddress, getSubnetAddress(), getMask() ); } @Override public String toString() { if (_subnetAddress == null) { return ALL_SUBNET; } if (_subnetAddress instanceof Inet6Address) { return _subnetAddress.getHostAddress().replaceFirst("(^|:)(0(:|$)){2,}", "::") + '/' + _mask; } else { return _subnetAddress.getHostAddress() + '/' + _mask; } } }