All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.shibboleth.utilities.java.support.net.IPRange Maven / Gradle / Ivy

There is a newer version: 8.0.0
Show newest version
/*
 * Licensed to the University Corporation for Advanced Internet Development,
 * Inc. (UCAID) under one or more contributor license agreements.  See the
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID 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 net.shibboleth.utilities.java.support.net;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.BitSet;

import net.shibboleth.utilities.java.support.primitive.StringSupport;

/** Represents a range of IP addresses. */
public class IPRange {

    /** Number of bits within the address.  32 bits for IPv4 address, 128 bits for IPv6 addresses. */
    private final int addressLength;

    /** The IP network address for the range. */
    private final BitSet network;
    
    /** The IP host address, if a host address rather than a network address was specified. */
    private final BitSet host;

    /** The netmask for the range. */
    private BitSet mask;

    /**
     * Constructor.
     * 
     * @param address address to base the range on; may be the network address or the
     *                address of a host within the network
     * @param maskSize the number of bits in the netmask
     */
    public IPRange(final InetAddress address, final int maskSize) {
        this(address.getAddress(), maskSize);
    }

    /**
     * Constructor.
     * 
     * @param address address to base the range on; may be the network address or the
     *                address of a host within the network
     * @param maskSize the number of bits in the netmask
     */
    public IPRange(final byte[] address, final int maskSize) {
        addressLength = address.length * 8;
        if (addressLength != 32 && addressLength != 128) {
            throw new IllegalArgumentException("address was neither an IPv4 or IPv6 address");
        }
        
        if (maskSize < 0 || maskSize > addressLength) {
            throw new IllegalArgumentException("prefix length must be in range 0 to " + addressLength);
        }

        mask = new BitSet(addressLength);
        mask.set(addressLength - maskSize, addressLength, true);

        final BitSet hostAddress = toBitSet(address);

        network = (BitSet)hostAddress.clone();
        network.and(mask);
        
        if (hostAddress.equals(network)) {
            host = null;
        } else {
            host = hostAddress;
        }
    }

    /**
     * Returns the network address corresponding to this range as an {@link InetAddress}.
     * 
     * @return network address as an {@link InetAddress}
     */
    public InetAddress getNetworkAddress() {
        return toInetAddress(network);
    }
    
    /**
     * Returns the host address originally specified for this range, if it was a
     * host address rather than a network address.  Returns null if the address
     * specified was a network address.
     * 
     * @return host address as an {@link InetAddress}, or null
     */
    public InetAddress getHostAddress() {
        return toInetAddress(host);
    }
    
    /**
     * Validate an IPv4 address for use as the base of a CIDR block.
     * 
     * Throws IllegalArgumentException if validation fails.
     * 
     * @param address the address to validate
     */
    private static void validateV4Address(final String address) {
        final String[] components = address.split("\\.");
        if (components.length != 4) {
            throw new IllegalArgumentException("IPv4 address should have four components");
        }
        for (final String component : components) {
            final int value = Integer.parseInt(component, 10);
            if (value < 0 || (value > 255)) {
                throw new IllegalArgumentException("IPv4 component range error: " + component);
            }
        }
    }
    
    /**
     * Validate an IPv6 address for use as the base of a CIDR block.
     * 
     * Just check that any non-empty components are valid hexadecimal integers
     * in the right range; leave most of the hard work to the {@link InetAddress} parser. 
     * 
     * Throws IllegalArgumentException if validation fails.
     * 
     * @param address the address to validate
     */
    private static void validateV6Address(final String address) {
        final String[] components = address.split(":");
        for (final String component : components) {
            if (component.length() != 0) {
                final int value = Integer.parseInt(component, 16);
                if (value < 0 || (value > 0xFFFF)) {
                    throw new IllegalArgumentException("IPv6 component range error: " + component);
                }
            }
        }
    }
    
    /**
     * Validate an IP address for use as the base of a CIDR block.
     * 
     * Throws IllegalArgumentException if validation fails.
     * 
     * @param address the address to validate
     */
    private static void validateIPAddress(final String address) {
        // any colons mean a V6 address, otherwise V4
        if (address.indexOf(':') >= 0) {
            validateV6Address(address);
        } else {
            validateV4Address(address);
        }
    }
    
    /**
     * Parses a CIDR block definition in to an IP range.
     * 
     * @param cidrBlock the CIDR block definition
     * 
     * @return the resultant IP range
     */
    public static IPRange parseCIDRBlock(final String cidrBlock) {
        final String block = StringSupport.trimOrNull(cidrBlock);
        if (block == null) {
            throw new IllegalArgumentException("CIDR block definition can not be null or empty");
        }

        final String[] blockParts = block.split("/");
        if (blockParts.length != 2) {
            throw new IllegalArgumentException("CIDR block definition is invalid, check for missing or extra slash");
        }
        try {
            validateIPAddress(blockParts[0]);
            final InetAddress address = InetAddress.getByName(blockParts[0]);
            final int maskSize = Integer.parseInt(blockParts[1]);
            return new IPRange(address, maskSize);
        } catch (final UnknownHostException e) {
            throw new IllegalArgumentException("Invalid IP address");
        } catch (final NumberFormatException e) {
            throw new IllegalArgumentException("Invalid netmask size");
        }
    }

    /**
     * Determines whether the given address is contained in the IP range.
     * 
     * @param address the address to check
     * 
     * @return true if the address is in the range, false it not
     */
    public boolean contains(final InetAddress address) {
        return contains(address.getAddress());
    }

    /**
     * Determines whether the given address is contained in the IP range.
     * 
     * @param address the address to check
     * 
     * @return true if the address is in the range, false it not
     */
    public boolean contains(final byte[] address) {
        if (address.length * 8 != addressLength) {
            return false;
        }

        final BitSet addrNetwork = toBitSet(address);
        addrNetwork.and(mask);

        return addrNetwork.equals(network);
    }

    /**
     * Converts a byte array to a BitSet.
     * 
     * The supplied byte array is assumed to have the most significant bit in element 0.
     * 
     * @param bytes the byte array with most significant bit in element 0.
     * 
     * @return the BitSet
     */
    protected BitSet toBitSet(final byte[] bytes) {
        final BitSet bits = new BitSet(bytes.length * 8);

        for (int i = 0; i < bytes.length * 8; i++) {
            if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
                bits.set(i);
            }
        }

        return bits;
    }

    /**
     * Convert a {@link BitSet} representing an address into an
     * equivalent array of bytes, sized according to the address
     * length of this {@link IPRange}.
     * 
     * @param bits {@link BitSet} representing an address
     * @return array of bytes representing the same address
     */
    private byte[] toByteArray(final BitSet bits) {
        final byte[] bytes = new byte[addressLength / 8];
        for (int i = 0; i < addressLength; i++) {
            if (bits.get(i)) {
                bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
            }
        }
        return bytes;
    }
    
    /**
     * Convert a {@link BitSet} representing an address into an
     * equivalent {@link InetAddress}.
     * 
     * Returns null for either a null {@link BitSet} or for any
     * problems encountered by {@link InetAddress}.
     * 
     * @param bits {@link BitSet} representing an address
     * @return {@link InetAddress} representing the same address
     */
    private InetAddress toInetAddress(final BitSet bits) {
        if (bits == null) {
            return null;
        }
        try {
            return InetAddress.getByAddress(toByteArray(bits));
        } catch (final UnknownHostException e) {
            // only supposed to happen if the address length is invalid
            return null;
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy