-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.kiwiproject.net; | ||
|
||
import com.google.common.net.InetAddresses; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import java.math.BigInteger; | ||
import java.net.InetAddress; | ||
import java.nio.ByteBuffer; | ||
|
||
/** | ||
* Small utility to analyze CIDR information. Supports both IPv4 and IPv6. | ||
* | ||
* This utility was copied and enhanced from https://github.com/edazdarevic/CIDRUtils which has not been updated since | ||
* 2019 and seems to be unmaintained. | ||
*/ | ||
@SuppressWarnings("UnstableApiUsage") | ||
@Slf4j | ||
public class KiwiCidrs { | ||
|
||
private final InetAddress inetAddress; | ||
private final int prefixLength; | ||
|
||
private InetAddress startAddress; | ||
private InetAddress endAddress; | ||
private BigInteger startAddressBigInt; | ||
private BigInteger endAddressBigInt; | ||
|
||
/** | ||
* Creates a new instance of KiwiCidrs parsing the given CIDR. | ||
* | ||
* @param cidr the CIDR to use for this instance. | ||
*/ | ||
public KiwiCidrs(String cidr) { | ||
|
||
/* split CIDR to address and prefix part */ | ||
if (cidr.contains("/")) { | ||
var index = cidr.indexOf("/"); | ||
var addressPart = cidr.substring(0, index); | ||
var networkPart = cidr.substring(index + 1); | ||
|
||
inetAddress = InetAddresses.forString(addressPart); | ||
prefixLength = Integer.parseInt(networkPart); | ||
|
||
calculate(); | ||
} else { | ||
throw new IllegalArgumentException("not a valid CIDR format!"); | ||
} | ||
} | ||
|
||
private void calculate() { | ||
|
||
ByteBuffer maskBuffer; | ||
int targetSize; | ||
if (inetAddress.getAddress().length == 4) { | ||
maskBuffer = ByteBuffer.allocate(4) | ||
.putInt(-1); | ||
|
||
targetSize = 4; | ||
} else { | ||
maskBuffer = ByteBuffer.allocate(16) | ||
.putLong(-1L) | ||
.putLong(-1L); | ||
targetSize = 16; | ||
} | ||
|
||
var mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength); | ||
|
||
var buffer = ByteBuffer.wrap(inetAddress.getAddress()); | ||
var ipVal = new BigInteger(1, buffer.array()); | ||
|
||
var startIp = ipVal.and(mask); | ||
var endIp = startIp.add(mask.not()); | ||
|
||
this.startAddress = targetSize == 4 ? InetAddresses.fromIPv4BigInteger(startIp) : InetAddresses.fromIPv6BigInteger(startIp); | ||
this.startAddressBigInt = new BigInteger(1, this.startAddress.getAddress()); | ||
|
||
this.endAddress = targetSize == 4 ? InetAddresses.fromIPv4BigInteger(endIp) : InetAddresses.fromIPv6BigInteger(endIp); | ||
this.endAddressBigInt = new BigInteger(1, this.endAddress.getAddress()); | ||
|
||
} | ||
|
||
/** | ||
* Returns the network address for the CIDR. For example: 192.168.100.15/24 will return 192.168.100.0. | ||
* | ||
* @return The network address for the CIDR | ||
*/ | ||
public String getNetworkAddress() { | ||
return this.startAddress.getHostAddress(); | ||
} | ||
|
||
/** | ||
* Returns the broadcast address for the CIDR. For example: 192.168.100.15/24 will return 192.168.100.255. | ||
* | ||
* @return The broadcast address for the CIDR | ||
*/ | ||
public String getBroadcastAddress() { | ||
return this.endAddress.getHostAddress(); | ||
} | ||
|
||
/** | ||
* Checks if a given IP address (as a string) is in the CIDR range. | ||
* | ||
* @param ipAddress the IP address to check | ||
* @return true if the IP address is in range, false otherwise | ||
*/ | ||
public boolean isInRange(String ipAddress) { | ||
var address = InetAddresses.forString(ipAddress); | ||
return isInRange(address); | ||
} | ||
|
||
/** | ||
* Checks if a given IP address (as an {@link InetAddress}) is in the CIDR range. | ||
* | ||
* @param address the IP address to check | ||
* @return true if the IP address is in range, false otherwise | ||
*/ | ||
public boolean isInRange(InetAddress address) { | ||
var target = new BigInteger(1, address.getAddress()); | ||
|
||
if (startAddressBigInt.compareTo(target) > 0){ | ||
return false; //start is higher than address -> is not in range | ||
} | ||
|
||
return endAddressBigInt.compareTo(target) >= 0; // end is higher or equal -> is in range | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package org.kiwiproject.net; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
|
||
@DisplayName("KiwiCidrs") | ||
class KiwiCidrsTest { | ||
|
||
@Test | ||
void shouldThrowIllegalArgumentExceptionIfNotValidCidr() { | ||
assertThatThrownBy(() -> new KiwiCidrs("1.2.3.4")) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("not a valid CIDR format!"); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"192.168.200.5/32, 192.168.200.5, 192.168.200.5", | ||
"192.168.200.5/24, 192.168.200.0, 192.168.200.255", | ||
"192.168.200.5/16, 192.168.0.0, 192.168.255.255", | ||
"192.168.200.5/8, 192.0.0.0, 192.255.255.255", | ||
"192.168.200.5/0, 0.0.0.0, 255.255.255.255", | ||
"0.0.0.0/0, 0.0.0.0, 255.255.255.255", | ||
"::/0, 0:0:0:0:0:0:0:0, ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", | ||
"2000::/16, 2000:0:0:0:0:0:0:0, 2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff" | ||
}) | ||
void shouldParseCidr(String cidr, String networkAddress, String broadcastAddress) { | ||
var cidrs = new KiwiCidrs(cidr); | ||
|
||
assertThat(cidrs.getNetworkAddress()).isEqualTo(networkAddress); | ||
assertThat(cidrs.getBroadcastAddress()).isEqualTo(broadcastAddress); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"192.168.200.5/32, 192.168.200.5", | ||
"192.168.200.5/24, 192.168.200.25", | ||
"192.168.200.5/16, 192.168.150.50", | ||
"192.168.200.5/8, 192.100.150.5", | ||
"192.168.200.5/0, 10.10.1.1", | ||
"0.0.0.0/0, 10.10.1.1", | ||
"::/0, 2001:db8:0000:0000:0000:0000:0000:003f", | ||
"2000::/16, 2000:db8:0000:0000:0000:0000:0000:003f" | ||
}) | ||
void shouldReturnTrueWhenAddressIsInRangeOfCidr(String cidr, String address) { | ||
var cidrs = new KiwiCidrs(cidr); | ||
|
||
assertThat(cidrs.isInRange(address)).isTrue(); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"192.168.200.5/32, 192.168.200.10", | ||
"192.168.200.5/24, 192.168.201.25", | ||
"192.168.200.5/16, 192.200.150.50", | ||
"192.168.200.5/8, 10.100.150.5", | ||
"2000::/16, 2001:db8:0000:0000:0000:0000:0000:003f" | ||
}) | ||
void shouldReturnFalseWhenAddressIsNotInRangeOfCidr(String cidr, String address) { | ||
var cidrs = new KiwiCidrs(cidr); | ||
|
||
assertThat(cidrs.isInRange(address)).isFalse(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters