AddressMask.java revision d70586b00b9530ab99ab4b8f003e9a54793e419f
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions copyright 2011-2013 ForgeRock AS
*/
package org.opends.server.types;
import static com.forgerock.opendj.ldap.ProtocolMessages.*;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.BitSet;
import java.util.Collection;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.server.ConfigException;
/**
* This class defines an address mask, which can be used to perform efficient
* comparisons against IP addresses to determine whether a particular IP address
* is in a given range.
*/
public final class AddressMask {
/**
* Types of rules we have. IPv4 - ipv4 rule IPv6 - ipv6 rule (begin with '['
* or contains an ':'). HOST - hostname match (foo.sun.com) HOSTPATTERN -
* host pattern match (begin with '.') ALLWILDCARD - *.*.*.* (first HOST is
* applied then ipv4)
*/
enum RuleType {
ALLWILDCARD, HOST, HOSTPATTERN, IPv4, IPv6
}
// IPv4 values for number of bytes and max CIDR prefix
private static final int IN4ADDRSZ = 4;
private static final int IPV4MAXPREFIX = 32;
// IPv6 values for number of bytes and max CIDR prefix
private static final int IN6ADDRSZ = 16;
private static final int IPV6MAXPREFIX = 128;
/**
* Decodes the provided string as an address mask.
*
* @param maskString
* The string to decode as an address mask.
* @return AddressMask The address mask decoded from the provided string.
* @throws ConfigException
* If the provided string cannot be decoded as an address mask.
*/
public static AddressMask decode(final String maskString) throws ConfigException {
return new AddressMask(maskString);
}
/**
* Indicates whether provided address matches one of the address masks in
* the provided collection.
*
* @param address
* The address to check.
* @param masks
* A collection of address masks to check.
* @return <CODE>true</CODE> if the provided address matches one of the
* given address masks, or <CODE>false</CODE> if it does not.
*/
public static boolean maskListContains(final InetAddress address, final Collection<AddressMask> masks) {
if (address != null) {
for (final AddressMask mask : masks) {
if (mask.match(address)) {
return true;
}
}
}
return false;
}
// Array that holds each component of a hostname.
private String[] hostName;
// Holds a hostname pattern (ie, rule that begins with '.');'
private String hostPattern;
// Holds binary representations of rule and mask respectively.
private byte[] ruleMask, prefixMask;
// Holds string passed into the constructor.
private final String ruleString;
// Type of rule determined
private RuleType ruleType;
// Bit array that holds wildcard info for above binary arrays.
private final BitSet wildCard = new BitSet();
/**
* Address mask constructor.
*
* @param rule
* The rule string to process.
* @throws ConfigException
* If the rule string is not valid.
*/
private AddressMask(final String rule) throws ConfigException {
determineRuleType(rule);
switch (ruleType) {
case IPv6:
processIPv6(rule);
break;
case IPv4:
processIpv4(rule);
break;
case HOST:
processHost(rule);
break;
case HOSTPATTERN:
processHostPattern(rule);
break;
case ALLWILDCARD:
processAllWilds(rule);
}
ruleString = rule;
}
/**
* Retrieves a string representation of this address mask.
*
* @return A string representation of this address mask.
*/
@Override
public String toString() {
return ruleString;
}
/**
* Try to determine what type of rule string this is. See RuleType above for
* valid types.
*
* @param ruleString
* The rule string to be examined.
* @throws ConfigException
* If the rule type cannot be determined from the rule string.
*/
private void determineRuleType(final String ruleString) throws ConfigException {
// Rule ending with '.' is invalid'
if (ruleString.endsWith(".")) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
} else if (ruleString.startsWith(".")) {
ruleType = RuleType.HOSTPATTERN;
} else if (ruleString.startsWith("[") || (ruleString.indexOf(':') != -1)) {
ruleType = RuleType.IPv6;
} else {
int wildCount = 0;
final String[] s = ruleString.split("\\.", -1);
/*
* Try to figure out how many wildcards and if the rule is hostname
* (can't begin with digit) or ipv4 address. Default to IPv4
* ruletype.
*/
ruleType = RuleType.HOST;
for (final String value : s) {
if (value.equals("*")) {
wildCount++;
continue;
}
// Looks like an ipv4 address
if (Character.isDigit(value.charAt(0))) {
ruleType = RuleType.IPv4;
break;
}
}
// All wildcards (*.*.*.*)
if (wildCount == s.length) {
ruleType = RuleType.ALLWILDCARD;
}
}
}
/**
* Main match function that determines which rule-type match function to
* use.
*
* @param address
* The address to check.
* @return <CODE>true</CODE>if one of the match functions found a match or
* <CODE>false</CODE>if not.
*/
private boolean match(final InetAddress address) {
boolean ret = false;
switch (ruleType) {
case IPv6:
case IPv4:
// this Address mask is an IPv4 rule
ret = matchAddress(address.getAddress());
break;
case HOST:
// HOST rule use hostname
ret = matchHostName(address.getHostName());
break;
case HOSTPATTERN:
// HOSTPATTERN rule
ret = matchPattern(address.getHostName());
break;
case ALLWILDCARD:
// first try ipv4 addr match, then hostname
ret = matchAddress(address.getAddress());
if (!ret) {
ret = matchHostName(address.getHostName());
}
break;
}
return ret;
}
/**
* Try to match remote client address using prefix mask and rule mask.
*
* @param remoteMask
* The byte array with remote client address.
* @return <CODE>true</CODE> if remote client address matches or
* <CODE>false</CODE>if not.
*/
private boolean matchAddress(final byte[] remoteMask) {
if (ruleType == RuleType.ALLWILDCARD) {
return true;
}
if (prefixMask == null) {
return false;
}
if (remoteMask.length != prefixMask.length) {
return false;
}
for (int i = 0; i < prefixMask.length; i++) {
if (!wildCard.get(i)) {
if ((ruleMask[i] & prefixMask[i]) != (remoteMask[i] & prefixMask[i])) {
return false;
}
}
}
return true;
}
/**
* Try to match remote client host name against rule host name.
*
* @param remoteHostName
* The remote host name string.
* @return <CODE>true</CODE>if the remote client host name matches
* <CODE>false</CODE> if it does not.
*/
private boolean matchHostName(final String remoteHostName) {
final String[] s = remoteHostName.split("\\.", -1);
if (s.length != hostName.length) {
return false;
}
if (ruleType == RuleType.ALLWILDCARD) {
return true;
}
for (int i = 0; i < s.length; i++) {
// skip if wildcard
if (!hostName[i].equals("*")) {
if (!s[i].equalsIgnoreCase(hostName[i])) {
return false;
}
}
}
return true;
}
/**
* Try to match remote host name string against the pattern rule.
*
* @param remoteHostName
* The remote client host name.
* @return <CODE>true</CODE>if the remote host name matches or
* <CODE>false</CODE>if not.
*/
private boolean matchPattern(final String remoteHostName) {
final int len = remoteHostName.length() - hostPattern.length();
return len > 0 && remoteHostName.regionMatches(true, len, hostPattern, 0, hostPattern.length());
}
/**
* Build the prefix mask of prefix len bits set in the array.
*
* @param prefix
* The len of the prefix to use.
*/
private void prefixMask(int prefix) {
int i;
for (i = 0; prefix > 8; i++) {
this.prefixMask[i] = (byte) 0xff;
prefix -= 8;
}
this.prefixMask[i] = (byte) ((0xff) << (8 - prefix));
}
/**
* The rule string is all wildcards. Set both address wildcard bitmask and
* hostname wildcard array.
*
* @param rule
* The rule string containing all wildcards.
*/
private void processAllWilds(final String rule) {
final String []s = rule.split("\\.", -1);
if (s.length == IN4ADDRSZ) {
for (int i = 0; i < IN4ADDRSZ; i++) {
wildCard.set(i);
}
}
hostName = rule.split("\\.", -1);
}
/**
* Examine rule string and build a hostname string array of its parts.
*
* @param rule
* The rule string.
* @throws ConfigException
* If the rule string is not a valid host name.
*/
private void processHost(final String rule) throws ConfigException {
// Note that '*' is valid in host rule
final String []s = rule.split("^[0-9a-zA-z-.*]+");
if (s.length > 0) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
hostName = rule.split("\\.", -1);
}
/**
* Examine the rule string of a host pattern and set the host pattern from
* the rule.
*
* @param rule
* The rule string to examine.
* @throws ConfigException
* If the rule string is not a valid host pattern rule.
*/
private void processHostPattern(final String rule) throws ConfigException {
// quick check for invalid chars like " "
final String []s = rule.split("^[0-9a-zA-z-.]+");
if (s.length > 0) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
hostPattern = rule;
}
/**
* The rule string is an IPv4 rule. Build both the prefix mask array and
* rule mask from the string.
*
* @param rule
* The rule string containing the IPv4 rule.
* @throws ConfigException
* If the rule string is not a valid IPv4 rule.
*/
private void processIpv4(final String rule) throws ConfigException {
final String[] s = rule.split("/", -1);
this.ruleMask = new byte[IN4ADDRSZ];
this.prefixMask = new byte[IN4ADDRSZ];
prefixMask(processPrefix(s, IPV4MAXPREFIX));
processIPv4Subnet((s.length == 0) ? rule : s[0]);
}
/**
* Examine the subnet part of a rule string and build a byte array
* representation of it.
*
* @param subnet
* The subnet string part of the rule.
* @throws ConfigException
* If the subnet string is not a valid IPv4 subnet string.
*/
private void processIPv4Subnet(final String subnet) throws ConfigException {
final String[] s = subnet.split("\\.", -1);
try {
// Make sure we have four parts
if (s.length != IN4ADDRSZ) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
for (int i = 0; i < IN4ADDRSZ; i++) {
final String quad = s[i].trim();
if (quad.equals("*")) {
wildCard.set(i); // see wildcard mark bitset
} else {
final long val = Integer.parseInt(quad);
// must be between 0-255
if ((val < 0) || (val > 0xff)) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
ruleMask[i] = (byte) (val & 0xff);
}
}
} catch (final NumberFormatException nfex) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
}
/**
* The rule string is an IPv6 rule. Build both the prefix mask array and
* rule mask from the string.
*
* @param rule
* The rule string containing the IPv6 rule.
* @throws ConfigException
* If the rule string is not a valid IPv6 rule.
*/
private void processIPv6(final String rule) throws ConfigException {
final String[] s = rule.split("/", -1);
InetAddress addr;
try {
addr = InetAddress.getByName(s[0]);
} catch (final UnknownHostException ex) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
if (addr instanceof Inet6Address) {
this.ruleType = RuleType.IPv6;
final Inet6Address addr6 = (Inet6Address) addr;
this.ruleMask = addr6.getAddress();
this.prefixMask = new byte[IN6ADDRSZ];
prefixMask(processPrefix(s, IPV6MAXPREFIX));
} else {
/*
* The address might be an IPv4-compat address. Throw an error if
* the rule has a prefix.
*/
if (s.length == 2) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
}
this.ruleMask = addr.getAddress();
this.ruleType = RuleType.IPv4;
this.prefixMask = new byte[IN4ADDRSZ];
prefixMask(processPrefix(s, IPV4MAXPREFIX));
}
}
/**
* Examine rule string for correct prefix usage.
*
* @param s
* The string array with rule string add and prefix strings.
* @param maxPrefix
* The max value the prefix can be.
* @return The prefix integer value.
* @throws ConfigException
* If the string array and prefix are not valid.
*/
private int processPrefix(final String[] s, final int maxPrefix) throws ConfigException {
int prefix = maxPrefix;
try {
// can only have one prefix value and a subnet string
if ((s.length < 1) || (s.length > 2)) {
final LocalizableMessage message = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(message);
} else if (s.length == 2) {
// can't have wildcard with a prefix
if (s[0].indexOf('*') > -1) {
final LocalizableMessage message = ERR_ADDRESSMASK_WILDCARD_DECODE_ERROR.get();
throw new ConfigException(message);
}
prefix = Integer.parseInt(s[1]);
}
// must be between 0-maxprefix
if ((prefix < 0) || (prefix > maxPrefix)) {
final LocalizableMessage message = ERR_ADDRESSMASK_PREFIX_DECODE_ERROR.get();
throw new ConfigException(message);
}
} catch (final NumberFormatException nfex) {
final LocalizableMessage msg = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
throw new ConfigException(msg);
}
return prefix;
}
}