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

com.googlecode.ipv6.IPv6AddressPool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Jan Van Besien
 *
 * 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 com.googlecode.ipv6;


import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Immutable representation of an IPv6 address pool.
 * 

* An IPv6 address pool is like an IPv6 address range in which some addresses are "free" and some are "allocated". Think "dhcp server". * Addresses are allocated in whole subnet blocks at once. These subnet blocks have a predefined prefix length for the whole allocatable * range. * * @author Jan Van Besien */ public final class IPv6AddressPool implements Serializable { private final IPv6AddressRange underlyingRange; private final SortedSet freeRanges; private final IPv6NetworkMask allocationSubnetSize; private final IPv6Network lastAllocated; /** * Create a pool of the given range (boundaries inclusive) which is completely free. The given subnet size is the network mask (thus * size) of the allocated subnets in this range. This constructor verifies that the whole range is "aligned" with subnets of this size * (i.e. there should not be a waste of space in the beginning or end which is smaller than one subnet of the given subnet size). * * @param range range from within to allocate * @param allocationSubnetSize size of the subnets that will be allocated * @return ipv6 address pool */ public static IPv6AddressPool fromRangeAndSubnet(final IPv6AddressRange range, final IPv6NetworkMask allocationSubnetSize) { // in the beginning, all is free return new IPv6AddressPool(range, allocationSubnetSize, new TreeSet(Arrays.asList(range)), null); } /** * Private constructor to construct a pool with a given set of free ranges and a network which was just allocated. * * @param range range from within to allocate * @param allocationSubnetSize size of the subnets that will be allocated * @param freeRanges free ranges in the allocatable IP address range */ private IPv6AddressPool(final IPv6AddressRange range, final IPv6NetworkMask allocationSubnetSize, final SortedSet freeRanges, final IPv6Network lastAllocated) { this.underlyingRange = range; this.allocationSubnetSize = allocationSubnetSize; this.freeRanges = Collections.unmodifiableSortedSet(freeRanges); this.lastAllocated = lastAllocated; validateFreeRanges(underlyingRange, freeRanges); validateRangeIsMultipleOfSubnetsOfGivenSize(underlyingRange, allocationSubnetSize); } private void validateFreeRanges(IPv6AddressRange range, SortedSet toValidate) { if (!toValidate.isEmpty() && !checkWithinBounds(range, toValidate)) throw new IllegalArgumentException("invalid free ranges: not all within bounds of overall range"); // TODO: some more validations would be useful. For example the free ranges should be defragmented and non overlapping etc } private boolean checkWithinBounds(IPv6AddressRange range, SortedSet toValidate) { return (toValidate.first().getFirst().compareTo(range.getFirst()) >= 0 && toValidate.last().getLast().compareTo(range.getLast()) <= 0); } private void validateRangeIsMultipleOfSubnetsOfGivenSize(IPv6AddressRange range, IPv6NetworkMask allocationSubnetSize) { final int allocatableBits = 128 - allocationSubnetSize.asPrefixLength(); if (range.getFirst().numberOfTrailingZeroes() < allocatableBits) throw new IllegalArgumentException( "range [" + this + "] is not aligned with prefix length [" + allocationSubnetSize.asPrefixLength() + "], " + "first address should end with " + allocatableBits + " zero bits"); if (range.getLast().numberOfTrailingOnes() < allocatableBits) throw new IllegalArgumentException( "range [" + this + "] is not aligned with prefix length [" + allocationSubnetSize.asPrefixLength() + "], last address should end with " + allocatableBits + " one bits"); } /** * @return the last IPv6Network which was allocated or null if none was allocated yet */ public IPv6Network getLastAllocated() { return lastAllocated; } /** * Allocate the first available subnet from the pool. * * @return resulting pool */ public IPv6AddressPool allocate() { if (!isExhausted()) { // get the first range of free subnets, and take the first subnet of that range final IPv6AddressRange firstFreeRange = freeRanges.first(); final IPv6Network allocated = IPv6Network.fromAddressAndMask(firstFreeRange.getFirst(), allocationSubnetSize); return doAllocate(allocated, firstFreeRange); } else { // exhausted return null; } } /** * Allocate the given subnet from the pool. * * @param toAllocate subnet to allocate from the pool * @return resulting pool */ public IPv6AddressPool allocate(IPv6Network toAllocate) { if (!contains(toAllocate)) throw new IllegalArgumentException( "can not allocate network which is not contained in the pool to allocate from [" + toAllocate + "]"); if (!this.allocationSubnetSize.equals(toAllocate.getNetmask())) throw new IllegalArgumentException( "can not allocate network with prefix length /" + toAllocate.getNetmask().asPrefixLength() + " from a pool configured to hand out subnets with prefix length /" + allocationSubnetSize); // go find the range that contains the requested subnet final IPv6AddressRange rangeToAllocateFrom = findFreeRangeContaining(toAllocate); if (rangeToAllocateFrom != null) { // found a range in which this subnet is free, allocate it return doAllocate(toAllocate, rangeToAllocateFrom); } else { // requested subnet not free return null; } } private IPv6AddressRange findFreeRangeContaining(IPv6Network toAllocate) { // split around the subnet to allocate final SortedSet head = freeRanges.headSet(toAllocate); final SortedSet tail = freeRanges.tailSet(toAllocate); // the range containing the network to allocate is either the first of the tail, or the last of the head, or it doesn't exist if (!head.isEmpty() && head.last().contains(toAllocate)) { return head.last(); } else if (!tail.isEmpty() && tail.first().contains(toAllocate)) { return tail.first(); } else { return null; } } /** * Private helper method to perform the allocation of a subnet within one of the free ranges. * * @param toAllocate subnet to allocate * @param rangeToAllocateFrom free range to allocate from * @return resulting pool */ private IPv6AddressPool doAllocate(final IPv6Network toAllocate, final IPv6AddressRange rangeToAllocateFrom) { assert freeRanges.contains(rangeToAllocateFrom); assert rangeToAllocateFrom.contains(toAllocate); final TreeSet newFreeRanges = new TreeSet(this.freeRanges); // remove range from free ranges newFreeRanges.remove(rangeToAllocateFrom); // from the range, remove the allocated subnet final List newRanges = rangeToAllocateFrom.remove(toAllocate); // and add the resulting ranges as new free ranges newFreeRanges.addAll(newRanges); return new IPv6AddressPool(underlyingRange, allocationSubnetSize, newFreeRanges, toAllocate); } /** * Give a network back to the pool (de-allocate). * * @param toDeAllocate network to de-allocate */ public IPv6AddressPool deAllocate(final IPv6Network toDeAllocate) { if (!contains(toDeAllocate)) { throw new IllegalArgumentException( "Network to de-allocate[" + toDeAllocate + "] is not contained in this allocatable range [" + this + "]"); } // find ranges just in front or after the network to deallocate. These are the ranges to merge with to prevent fragmentation. final IPv6AddressRange freeRangeBeforeNetwork = findFreeRangeBefore(toDeAllocate); final IPv6AddressRange freeRangeAfterNetwork = findFreeRangeAfter(toDeAllocate); final TreeSet newFreeRanges = new TreeSet(this.freeRanges); if ((freeRangeBeforeNetwork == null) && (freeRangeAfterNetwork == null)) { // nothing to "defragment" newFreeRanges.add(toDeAllocate); } else { if ((freeRangeBeforeNetwork != null) && (freeRangeAfterNetwork != null)) { // merge two existing ranges newFreeRanges.remove(freeRangeBeforeNetwork); newFreeRanges.remove(freeRangeAfterNetwork); newFreeRanges.add(IPv6AddressRange .fromFirstAndLast(freeRangeBeforeNetwork.getFirst(), freeRangeAfterNetwork.getLast())); } else if (freeRangeBeforeNetwork != null) { // append newFreeRanges.remove(freeRangeBeforeNetwork); newFreeRanges.add(IPv6AddressRange.fromFirstAndLast(freeRangeBeforeNetwork.getFirst(), toDeAllocate.getLast())); } else /*if (freeRangeAfterNetwork != null)*/ { // prepend newFreeRanges.remove(freeRangeAfterNetwork); newFreeRanges.add(IPv6AddressRange.fromFirstAndLast(toDeAllocate.getFirst(), freeRangeAfterNetwork.getLast())); } } return new IPv6AddressPool(underlyingRange, allocationSubnetSize, newFreeRanges, getLastAllocated()); } /** * Private helper method to find the free range just before the given network. */ private IPv6AddressRange findFreeRangeBefore(IPv6Network network) { for (IPv6AddressRange freeRange : freeRanges) { if (freeRange.getLast().add(1).equals(network.getFirst())) { return freeRange; } } // not found return null; } /** * Private helper method to find the free range just after the given address. */ private IPv6AddressRange findFreeRangeAfter(IPv6Network network) { for (IPv6AddressRange freeRange : freeRanges) { if (freeRange.getFirst().subtract(1).equals(network.getLast())) { return freeRange; } } // not found return null; } /** * @return true if no subnets are free in this pool, false otherwize */ public boolean isExhausted() { return freeRanges.isEmpty(); } public boolean isFree(final IPv6Network network) { if (network == null) throw new IllegalArgumentException("network invalid [null]"); if (!this.allocationSubnetSize.equals(network.getNetmask())) throw new IllegalArgumentException( "network of prefix length [" + network.getNetmask().asPrefixLength() + "] can not be free in a pool which uses prefix length [" + allocationSubnetSize + "]"); // find a free range that contains the network for (IPv6AddressRange freeRange : freeRanges) { if (freeRange.contains(network)) { return true; } } // nothing found return false; } /** * @return all networks (all with the same fixed prefix length) which are free in this pool */ public Iterable freeNetworks() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { /* * Iteration is implemented by allocating from a separate pool. */ private IPv6AddressPool poolInstanceUsedForIteration = IPv6AddressPool.this; @Override public boolean hasNext() { return !poolInstanceUsedForIteration.isExhausted(); } @Override public IPv6Network next() { if (hasNext()) { poolInstanceUsedForIteration = poolInstanceUsedForIteration.allocate(); return poolInstanceUsedForIteration.lastAllocated; } else { throw new NoSuchElementException(); } } @Override public void remove() { throw new UnsupportedOperationException("remove not supported"); } }; } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IPv6AddressPool that = (IPv6AddressPool) o; if (allocationSubnetSize != null ? !allocationSubnetSize.equals(that.allocationSubnetSize) : that.allocationSubnetSize != null) return false; if (freeRanges != null ? !freeRanges.equals(that.freeRanges) : that.freeRanges != null) return false; if (lastAllocated != null ? !lastAllocated.equals(that.lastAllocated) : that.lastAllocated != null) return false; if (underlyingRange != null ? !underlyingRange.equals(that.underlyingRange) : that.underlyingRange != null) return false; return true; } @Override public int hashCode() { int result = underlyingRange != null ? underlyingRange.hashCode() : 0; result = 31 * result + (freeRanges != null ? freeRanges.hashCode() : 0); result = 31 * result + (allocationSubnetSize != null ? allocationSubnetSize.hashCode() : 0); result = 31 * result + (lastAllocated != null ? lastAllocated.hashCode() : 0); return result; } // delegation methods public boolean contains(IPv6Address address) { return underlyingRange.contains(address); } public boolean contains(IPv6AddressRange range) { return underlyingRange.contains(range); } public boolean overlaps(IPv6AddressRange range) { return underlyingRange.overlaps(range); } public IPv6Address getFirst() { return underlyingRange.getFirst(); } public IPv6Address getLast() { return underlyingRange.getLast(); } @Override public String toString() { return underlyingRange.toString(); } /** * @return like toString but without using shorthand notations for addresses */ public String toLongString() { return underlyingRange.toLongString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy