Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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
*
* https://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 org.apache.commons.net.examples.cidr;

import java.nio.charset.Charset;
import java.util.Scanner;

import org.apache.commons.net.util.SubnetUtils6;
import org.apache.commons.net.util.SubnetUtils6.SubnetInfo;

/**
* Example class that shows how to use the {@link SubnetUtils6} class.
*/
public class SubnetUtils6Example {

public static void main(final String[] args) {
final String subnet = "2001:db8:85a3::8a2e:370:7334/64";
final SubnetUtils6 utils = new SubnetUtils6(subnet);
final SubnetInfo info = utils.getInfo();

System.out.printf("Subnet Information for %s:%n", subnet);
System.out.println("--------------------------------------");
System.out.printf("IP Address:\t\t\t%s%n", info.getAddress());
System.out.printf("Prefix Length:\t\t\t%d%n", info.getPrefixLength());
System.out.printf("CIDR Representation:\t\t%s%n%n", info.getCidrSignature());

System.out.printf("Network Address:\t\t%s%n", info.getNetworkAddress());
System.out.printf("Low Address:\t\t\t%s%n", info.getLowAddress());
System.out.printf("High Address:\t\t\t%s%n", info.getHighAddress());

System.out.printf("Total addresses in subnet:\t%s%n%n", info.getAddressCount());

final String prompt = "Enter an IPv6 address (e.g., 2001:db8:85a3::1):";
System.out.println(prompt);
try (Scanner scanner = new Scanner(System.in, Charset.defaultCharset().name())) {
while (scanner.hasNextLine()) {
final String address = scanner.nextLine();
System.out.println("The IP address [" + address + "] is " + (info.isInRange(address) ? "" : "not ") + "within the subnet [" + subnet + "]");
System.out.println(prompt);
}
}
}

}
316 changes: 316 additions & 0 deletions src/main/java/org/apache/commons/net/util/SubnetUtils6.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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
*
* https://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 org.apache.commons.net.util;

import java.math.BigInteger;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* Performs subnet calculations given an IPv6 network address and a prefix length.
* <p>
* This is the IPv6 equivalent of {@link SubnetUtils}.
* </p>
*
* @see SubnetUtils
* @since 3.13
*/
public class SubnetUtils6 {

/**
* Contains IPv6 subnet summary information.
*/
public final class SubnetInfo {

private SubnetInfo() { }

/**
* Gets the address used to initialize this subnet.
*
* @return the address as a string in standard IPv6 format.
*/
public String getAddress() {
return format(address);
}

/**
* Gets the count of available addresses in this subnet.
* <p>
* For IPv6, this can be astronomically large. A /64 subnet has 2^64 addresses.
* </p>
*
* @return the count of addresses as a BigInteger.
*/
public BigInteger getAddressCount() {
// 2^(128 - prefixLength)
return TWO.pow(NBITS - prefixLength);
}

/**
* Gets the CIDR notation for this subnet.
*
* @return the CIDR signature (e.g., "2001:db8::1/64").
*/
public String getCidrSignature() {
return format(address) + "/" + prefixLength;
}

/**
* Gets the highest address in this subnet.
*
* @return the high address as a string in standard IPv6 format.
*/
public String getHighAddress() {
return format(high);
}

/**
* Gets the lowest address in this subnet (the network address).
*
* @return the low address as a string in standard IPv6 format.
*/
public String getLowAddress() {
return format(network);
}

/**
* Gets the network address for this subnet.
*
* @return the network address as a string in standard IPv6 format.
*/
public String getNetworkAddress() {
return format(network);
}

/**
* Gets the prefix length for this subnet.
*
* @return the prefix length (0-128).
*/
public int getPrefixLength() {
return prefixLength;
}

/**
* Tests if the given address is within this subnet range.
*
* @param addr the IPv6 address to test (as a BigInteger).
* @return true if the address is in range.
*/
public boolean isInRange(final BigInteger addr) {
if (addr == null) {
return false;
}
return addr.compareTo(network) >= 0 && addr.compareTo(high) <= 0;
}

/**
* Tests if the given address is within this subnet range.
*
* @param addr the IPv6 address to test as a byte array (16 bytes).
* @return true if the address is in range.
*/
public boolean isInRange(final byte[] addr) {
if (addr == null || addr.length != 16) {
return false;
}
return isInRange(new BigInteger(1, addr));
}

/**
* Tests if the given address is within this subnet range.
*
* @param addr the IPv6 address to test.
* @return true if the address is in range.
*/
public boolean isInRange(final Inet6Address addr) {
if (addr == null) {
return false;
}
return isInRange(addr.getAddress());
}

/**
* Tests if the given address is within this subnet range.
*
* @param addr the IPv6 address to test as a string.
* @return true if the address is in range.
* @throws IllegalArgumentException if the address cannot be parsed.
*/
public boolean isInRange(final String addr) {
return isInRange(toBytes(addr));
}

@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n")
.append(" Network: [").append(getNetworkAddress()).append("]\n")
.append(" First address: [").append(getLowAddress()).append("]\n")
.append(" Last address: [").append(getHighAddress()).append("]\n")
.append(" Address Count: [").append(getAddressCount()).append("]\n");
return buf.toString();
}
}

private static final int NBITS = 128;
private static final String PARSE_FAIL = "Could not parse [%s]";
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final BigInteger MAX_VALUE = TWO.pow(NBITS).subtract(BigInteger.ONE);

/**
* Formats a BigInteger as an IPv6 address string.
* <p>
* Uses the canonical format with :: compression for the longest run of zeros.
* </p>
*
* @param addr the address as a BigInteger.
* @return the formatted IPv6 address string.
*/
private static String format(final BigInteger addr) {
final byte[] bytes = toByteArray16(addr);
try {
return InetAddress.getByAddress(bytes).getHostAddress();
} catch (final UnknownHostException e) {
// Should never happen with a valid 16-byte array
throw new IllegalStateException("Unexpected error formatting IPv6 address", e);
}
}

/**
* Converts a BigInteger to a 16-byte array, padding with leading zeros if necessary.
*
* @param value the BigInteger to convert.
* @return a 16-byte array.
*/
private static byte[] toByteArray16(final BigInteger value) {
final byte[] raw = value.toByteArray();
if (raw.length == 16) {
return raw;
}
final byte[] result = new byte[16];
if (raw.length > 16) {
// BigInteger may have a leading sign byte; skip it
System.arraycopy(raw, raw.length - 16, result, 0, 16);
} else {
// Pad with leading zeros
System.arraycopy(raw, 0, result, 16 - raw.length, raw.length);
}
return result;
}

/**
* Parses an IPv6 address string to a byte array.
*
* @param address the IPv6 address string.
* @return the 16-byte representation.
* @throws IllegalArgumentException if the address cannot be parsed.
*/
private static byte[] toBytes(final String address) {
try {
final InetAddress inetAddr = InetAddress.getByName(address);
if (inetAddr instanceof Inet6Address) {
return inetAddr.getAddress();
}
throw new IllegalArgumentException(String.format(PARSE_FAIL, address) + " - not an IPv6 address");
} catch (final UnknownHostException e) {
throw new IllegalArgumentException(String.format(PARSE_FAIL, address), e);
}
}

private final BigInteger address;
private final BigInteger high;
private final BigInteger network;
private final int prefixLength;

/**
* Constructs an instance from a CIDR-notation string, e.g., "2001:db8::1/64".
*
* @param cidrNotation a CIDR-notation string, e.g., "2001:db8::1/64".
* @throws IllegalArgumentException if the parameter is invalid.
*/
public SubnetUtils6(final String cidrNotation) {
if (cidrNotation == null) {
throw new IllegalArgumentException(String.format(PARSE_FAIL, "null") + " - null input");
}

final int slashIndex = cidrNotation.indexOf('/');
if (slashIndex < 0) {
throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) + " - missing prefix length");
}

final String addressPart = cidrNotation.substring(0, slashIndex);
final String prefixPart = cidrNotation.substring(slashIndex + 1);

// Parse and validate prefix length
try {
this.prefixLength = Integer.parseInt(prefixPart);
} catch (final NumberFormatException e) {
throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) + " - invalid prefix length", e);
}

if (this.prefixLength < 0 || this.prefixLength > NBITS) {
throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) +
" - prefix length must be between 0 and " + NBITS);
}

// Parse and validate IPv6 address
final byte[] addressBytes = toBytes(addressPart);
this.address = new BigInteger(1, addressBytes);

// Create netmask: prefixLength 1-bits followed by (128 - prefixLength) 0-bits
final BigInteger netmask;
if (this.prefixLength == 0) {
netmask = BigInteger.ZERO;
} else {
netmask = MAX_VALUE.shiftLeft(NBITS - this.prefixLength).and(MAX_VALUE);
}

// Calculate network address
this.network = this.address.and(netmask);

// Calculate the highest address in the range
final BigInteger hostmask = MAX_VALUE.xor(netmask);
this.high = this.network.or(hostmask);
}

/**
* Constructs an instance from an IPv6 address and prefix length.
*
* @param address an IPv6 address, e.g., "2001:db8::1".
* @param prefixLength the prefix length (0-128).
* @throws IllegalArgumentException if the parameters are invalid.
*/
public SubnetUtils6(final String address, final int prefixLength) {
this(address + "/" + prefixLength);
}

/**
* Gets a {@link SubnetInfo} instance that contains subnet-specific statistics.
*
* @return a new SubnetInfo instance.
*/
public SubnetInfo getInfo() {
return new SubnetInfo();
}

@Override
public String toString() {
return getInfo().toString();
}
}
Loading