/* * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program (see the file COPYING.LIB for more * details); if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.dcache.util; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.net.InetAddresses.forString; import static com.google.common.net.InetAddresses.getEmbeddedIPv4ClientAddress; import static com.google.common.primitives.Ints.fromByteArray; import static com.google.common.primitives.Longs.fromBytes; public class IPMatcher { private static final int IPv4_FULL_MASK = 32; private static final int IPv6_FULL_MASK = 128; private static final int IPv6_HALF_MASK = 64; public static int convertToCidrIfIsIPv4Mask(String maskString) { int mask; if (maskString.contains(".")) { mask = fromByteArray(forString(maskString).getAddress()); mask = IPv4_FULL_MASK - Integer.numberOfTrailingZeros(mask); } else { mask = Integer.parseInt(maskString); } return mask; } /** * Returns the subnet part of an InetAddress according to a mask in CIDR notation * * Example: inetAddress=123.123.45.67, mask=16 will return an InetAddress of 123.123.0.0 * * @param inetAddress base address * @param mask mask in CIDR notation * @return masked InetAddress * @throws UnknownHostException will be thrown if the resulting InetAddress is not valid. * This should not happen since the base address will always be valid. */ public static InetAddress maskInetAddress(InetAddress inetAddress, int mask) throws UnknownHostException { byte[] address = inetAddress.getAddress(); if (mask == 0) { return InetAddress.getByAddress(new byte[address.length]); } if (mask%8 != 0) { address[mask / 8] = (byte) (address[mask / 8] & (0xff << (8 - mask % 8))); } for (int i=mask/8+1; i<address.length; i++) { address[i] = 0; } return InetAddress.getByAddress(address); } /** * Matches an InetAddress with the CIDR notation of a subnet. * * @param cidrPattern CIDR notation of the subnet * @param inetAddress address to be matched * @return true if inetAddress matches cidrPattern, false otherwise */ public static boolean matchCidrPattern(InetAddress inetAddress, String cidrPattern) { return Subnet.create(cidrPattern).contains(inetAddress); } /** * @param ips array of ips to be matched with the subnet defined by subnet and mask * @param subnet subnet address * @param mask netmask in CIDR notation * @return true if any of the IPs in ips falls lies the subnet, otherwise false */ public static boolean matchAny(InetAddress[] ips, InetAddress subnet, int mask) { for (InetAddress inetAddress : ips) { if (match(inetAddress, subnet, mask)) { return true; } } return false; } /** * Checks matching ip in specified subnet. * * @param ip address to test * @param subnet subnet's base address * @param mask netmask in CIDR notation * @return true if ip matches subnet. */ public static boolean match(InetAddress ip, InetAddress subnet, int mask) { checkArgument(mask >= 0, "Netmask should be positive"); if(mask == 0) { return true; // match all } byte[] ipBytes = ip.getAddress(); byte[] netBytes = subnet.getAddress(); if (ipBytes.length != netBytes.length) { return false; } if (ipBytes.length == 4) { checkArgument(mask <= IPv4_FULL_MASK, "Netmask for IPv4 can't be bigger than" + IPv4_FULL_MASK); /* * IPv4 can be represented as a 32 bit ints. */ int ipAsInt = fromByteArray(ipBytes); int netAsBytes = fromByteArray(netBytes); return (ipAsInt ^ netAsBytes) >> (IPv4_FULL_MASK - mask) == 0; } checkArgument(mask <= IPv6_FULL_MASK, "Netmask for IPv6 can't be bigger than" + IPv6_FULL_MASK); /* * IPv6 can be represented as two 64 bit longs. * * We evaluate second long only if bitmask bigger than 64. The second * longs are created only if needed as it turned to be the slowest part. */ long ipAsLong0 = fromBytes(ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3], ipBytes[4], ipBytes[5], ipBytes[6], ipBytes[7]); long netAsLong0 = fromBytes(netBytes[0], netBytes[1], netBytes[2], netBytes[3], netBytes[4], netBytes[5], netBytes[6], netBytes[7]); if (mask > 64) { long ipAsLong1 = fromBytes(ipBytes[8], ipBytes[9], ipBytes[10], ipBytes[11], ipBytes[12], ipBytes[13], ipBytes[14], ipBytes[15]); long netAsLong1 = fromBytes(netBytes[8], netBytes[9], netBytes[10], netBytes[11], netBytes[12], netBytes[13], netBytes[14], netBytes[15]); return (ipAsLong0 == netAsLong0) & (ipAsLong1 ^ netAsLong1) >> (IPv6_FULL_MASK - mask) == 0; } return (ipAsLong0 ^ netAsLong0) >> (IPv6_HALF_MASK - mask) == 0; } public static InetAddress tryConvertToIPv4(InetAddress inetAddress) { if (!(inetAddress instanceof Inet6Address)) { return inetAddress; } try { return getEmbeddedIPv4ClientAddress((Inet6Address)inetAddress); } catch (IllegalArgumentException ignored) {} return inetAddress; } }