Skip to content

Commit 4698204

Browse files
author
alexsa
committed
Implement Subnet class and drop optional ipaddress dependency
1 parent 85d5931 commit 4698204

File tree

9 files changed

+364
-101
lines changed

9 files changed

+364
-101
lines changed

core/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,6 @@
116116
<groupId>org.reactivestreams</groupId>
117117
<artifactId>reactive-streams</artifactId>
118118
</dependency>
119-
<dependency>
120-
<groupId>com.github.seancfoley</groupId>
121-
<artifactId>ipaddress</artifactId>
122-
<optional>true</optional>
123-
</dependency>
124119
<dependency>
125120
<groupId>com.github.stephenc.jcip</groupId>
126121
<artifactId>jcip-annotations</artifactId>
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package com.datastax.oss.driver.internal.core.addresstranslation;
2+
3+
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
4+
5+
import java.net.InetAddress;
6+
import java.net.UnknownHostException;
7+
import java.util.Arrays;
8+
9+
class Subnet {
10+
private final byte[] subnet;
11+
private final byte[] networkMask;
12+
private final byte[] upper;
13+
private final byte[] lower;
14+
15+
private Subnet(byte[] subnet, byte[] networkMask) {
16+
this.subnet = subnet;
17+
this.networkMask = networkMask;
18+
19+
byte[] upper = new byte[subnet.length];
20+
byte[] lower = new byte[subnet.length];
21+
for (int i = 0; i < subnet.length; i++) {
22+
upper[i] = (byte) (subnet[i] | ~networkMask[i]);
23+
lower[i] = (byte) (subnet[i] & networkMask[i]);
24+
}
25+
this.upper = upper;
26+
this.lower = lower;
27+
}
28+
29+
static Subnet parse(String subnetCIDR) throws UnknownHostException {
30+
String[] parts = subnetCIDR.split("/");
31+
if (parts.length != 2) {
32+
throw new IllegalArgumentException("Invalid subnet: " + subnetCIDR);
33+
}
34+
35+
boolean isIPv6 = parts[0].contains(":");
36+
byte[] subnet = InetAddress.getByName(parts[0]).getAddress();
37+
if (isIPv4(subnet) && isIPv6) {
38+
subnet = toIPv6(subnet);
39+
}
40+
int prefixLength = Integer.parseInt(parts[1]);
41+
validatePrefixLength(subnet, prefixLength);
42+
43+
byte[] networkMask = toNetworkMask(subnet, prefixLength);
44+
validateSubnetIsPrefixBlock(subnet, networkMask, subnetCIDR);
45+
return new Subnet(subnet, networkMask);
46+
}
47+
48+
private static byte[] toNetworkMask(byte[] subnet, int prefixLength) {
49+
int fullBytes = prefixLength / 8;
50+
int remainingBits = prefixLength % 8;
51+
byte[] mask = new byte[subnet.length];
52+
Arrays.fill(mask, 0, fullBytes, (byte) 0xFF);
53+
if (remainingBits > 0) {
54+
mask[fullBytes] = (byte) (0xFF << (8 - remainingBits));
55+
}
56+
return mask;
57+
}
58+
59+
private static void validatePrefixLength(byte[] subnet, int prefixLength) {
60+
int max_prefix_length = subnet.length * 8;
61+
if (prefixLength < 0 || max_prefix_length < prefixLength) {
62+
throw new IllegalArgumentException(
63+
String.format("Prefix length %s must be within [0; %s]", prefixLength, max_prefix_length));
64+
}
65+
}
66+
67+
private static void validateSubnetIsPrefixBlock(byte[] subnet, byte[] networkMask, String subnetCIDR) {
68+
byte[] prefixBlock = toPrefixBlock(subnet, networkMask);
69+
if (!Arrays.equals(subnet, prefixBlock)) {
70+
throw new IllegalArgumentException(
71+
String.format("Subnet %s must be represented as a network prefix block", subnetCIDR));
72+
}
73+
}
74+
75+
private static byte[] toPrefixBlock(byte[] subnet, byte[] networkMask) {
76+
byte[] prefixBlock = new byte[subnet.length];
77+
for (int i = 0; i < subnet.length; i++) {
78+
prefixBlock[i] = (byte) (subnet[i] & networkMask[i]);
79+
}
80+
return prefixBlock;
81+
}
82+
83+
@VisibleForTesting
84+
byte[] getSubnet() {
85+
return Arrays.copyOf(subnet, subnet.length);
86+
}
87+
88+
@VisibleForTesting
89+
byte[] getNetworkMask() {
90+
return Arrays.copyOf(networkMask, networkMask.length);
91+
}
92+
93+
byte[] getUpper() {
94+
return Arrays.copyOf(upper, upper.length);
95+
}
96+
97+
byte[] getLower() {
98+
return Arrays.copyOf(lower, lower.length);
99+
}
100+
101+
boolean isIPv4() {
102+
return isIPv4(subnet);
103+
}
104+
105+
boolean isIPv6() {
106+
return isIPv6(subnet);
107+
}
108+
109+
boolean contains(byte[] ip) {
110+
if (isIPv4() && !isIPv4(ip)) {
111+
return false;
112+
}
113+
if (isIPv6() && isIPv4(ip)) {
114+
ip = toIPv6(ip);
115+
}
116+
if (subnet.length != ip.length) {
117+
throw new IllegalArgumentException("IP version is unknown: " + Arrays.toString(toZeroBasedByteArray(ip)));
118+
}
119+
for (int i = 0; i < subnet.length; i++) {
120+
if (subnet[i] != (byte) (ip[i] & networkMask[i])) {
121+
return false;
122+
}
123+
}
124+
return true;
125+
}
126+
127+
private static boolean isIPv4(byte[] ip) {
128+
return ip.length == 4;
129+
}
130+
131+
private static boolean isIPv6(byte[] ip) {
132+
return ip.length == 16;
133+
}
134+
135+
private static byte[] toIPv6(byte[] ipv4) {
136+
byte[] ipv6 = new byte[16];
137+
ipv6[10] = (byte) 0xFF;
138+
ipv6[11] = (byte) 0xFF;
139+
System.arraycopy(ipv4, 0, ipv6, 12, 4);
140+
return ipv6;
141+
}
142+
143+
@Override
144+
public String toString() {
145+
return Arrays.toString(toZeroBasedByteArray(subnet));
146+
}
147+
148+
private static int[] toZeroBasedByteArray(byte[] bytes) {
149+
int[] res = new int[bytes.length];
150+
for (int i = 0; i < bytes.length; i++) {
151+
res[i] = bytes[i] & 0xFF;
152+
}
153+
return res;
154+
}
155+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.datastax.oss.driver.internal.core.addresstranslation;
2+
3+
import java.net.InetSocketAddress;
4+
import java.net.UnknownHostException;
5+
6+
class SubnetAddress {
7+
private final Subnet subnet;
8+
private final InetSocketAddress address;
9+
10+
SubnetAddress(String subnetCIDR, InetSocketAddress address) {
11+
try {
12+
this.subnet = Subnet.parse(subnetCIDR);
13+
} catch (UnknownHostException e) {
14+
throw new RuntimeException(e);
15+
}
16+
this.address = address;
17+
}
18+
19+
InetSocketAddress getAddress() {
20+
return this.address;
21+
}
22+
23+
boolean isOverlapping(SubnetAddress other) {
24+
Subnet thisSubnet = this.subnet;
25+
Subnet otherSubnet = other.subnet;
26+
return thisSubnet.contains(otherSubnet.getLower())
27+
|| thisSubnet.contains(otherSubnet.getUpper())
28+
|| otherSubnet.contains(thisSubnet.getLower())
29+
|| otherSubnet.contains(thisSubnet.getUpper());
30+
}
31+
32+
boolean contains(InetSocketAddress address) {
33+
return subnet.contains(address.getAddress().getAddress());
34+
}
35+
36+
boolean isIPv4() {
37+
return subnet.isIPv4();
38+
}
39+
40+
boolean isIPv6() {
41+
return subnet.isIPv6();
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return "SubnetAddress[subnet=" + subnet +
47+
", address=" + address +
48+
"]";
49+
}
50+
}

core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java

Lines changed: 34 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
import com.datastax.oss.driver.internal.core.util.AddressUtils;
2727
import edu.umd.cs.findbugs.annotations.NonNull;
2828
import edu.umd.cs.findbugs.annotations.Nullable;
29-
import inet.ipaddr.IPAddress;
30-
import inet.ipaddr.IPAddressString;
29+
3130
import java.net.InetSocketAddress;
3231
import java.util.List;
3332
import java.util.Optional;
@@ -83,16 +82,47 @@ public SubnetAddressTranslator(@NonNull DriverContext context) {
8382
.getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS, null))
8483
.map(address -> parseAddress(address, resolveAddresses));
8584

85+
validateSubnetsAreOfSameProtocol(this.subnetAddresses);
8686
validateSubnetsAreNotOverlapping(this.subnetAddresses);
8787
}
8888

89+
private static void validateSubnetsAreOfSameProtocol(List<SubnetAddress> subnets) {
90+
for (int i = 0; i < subnets.size() - 1; i++) {
91+
for (int j = i + 1; j < subnets.size(); j++) {
92+
SubnetAddress subnet1 = subnets.get(i);
93+
SubnetAddress subnet2 = subnets.get(j);
94+
if (subnet1.isIPv4() != subnet2.isIPv4() && subnet1.isIPv6() != subnet2.isIPv6()) {
95+
throw new IllegalArgumentException(
96+
String.format(
97+
"Configured subnets are of the different protocols: %s, %s",
98+
subnet1, subnet2));
99+
}
100+
}
101+
}
102+
}
103+
104+
private static void validateSubnetsAreNotOverlapping(List<SubnetAddress> subnets) {
105+
for (int i = 0; i < subnets.size() - 1; i++) {
106+
for (int j = i + 1; j < subnets.size(); j++) {
107+
SubnetAddress subnet1 = subnets.get(i);
108+
SubnetAddress subnet2 = subnets.get(j);
109+
if (subnet1.isOverlapping(subnet2)) {
110+
throw new IllegalArgumentException(
111+
String.format(
112+
"Configured subnets are overlapping: %s, %s",
113+
subnet1, subnet2));
114+
}
115+
}
116+
}
117+
}
118+
89119
@NonNull
90120
@Override
91121
public InetSocketAddress translate(@NonNull InetSocketAddress address) {
92122
InetSocketAddress translatedAddress = null;
93123
for (SubnetAddress subnetAddress : subnetAddresses) {
94124
if (subnetAddress.contains(address)) {
95-
translatedAddress = subnetAddress.address;
125+
translatedAddress = subnetAddress.getAddress();
96126
}
97127
}
98128
if (translatedAddress == null && defaultAddress.isPresent()) {
@@ -107,7 +137,7 @@ public InetSocketAddress translate(@NonNull InetSocketAddress address) {
107137

108138
@Override
109139
public void close() {}
110-
140+
111141
@Nullable
112142
private InetSocketAddress parseAddress(String address, boolean resolve) {
113143
try {
@@ -119,62 +149,4 @@ private InetSocketAddress parseAddress(String address, boolean resolve) {
119149
String.format("Invalid address %s (%s)", address, e.getMessage()), e);
120150
}
121151
}
122-
123-
private static void validateSubnetsAreNotOverlapping(List<SubnetAddress> subnetAddresses) {
124-
for (int i = 0; i < subnetAddresses.size() - 1; i++) {
125-
for (int j = i + 1; j < subnetAddresses.size(); j++) {
126-
SubnetAddress subnetAddress1 = subnetAddresses.get(i);
127-
SubnetAddress subnetAddress2 = subnetAddresses.get(j);
128-
if (subnetAddress1.isOverlapping(subnetAddress2)) {
129-
throw new IllegalArgumentException(
130-
String.format(
131-
"Configured subnets are overlapping: %s, %s",
132-
subnetAddress1.subnet, subnetAddress2.subnet));
133-
}
134-
}
135-
}
136-
}
137-
138-
private static class SubnetAddress {
139-
private final IPAddress subnet;
140-
private final InetSocketAddress address;
141-
142-
private SubnetAddress(String subnetCIDR, InetSocketAddress address) {
143-
this.subnet = parseSubnet(subnetCIDR);
144-
this.address = address;
145-
}
146-
147-
private static IPAddress parseSubnet(String subnetCIDR) {
148-
IPAddress subnet = new IPAddressString(subnetCIDR).getAddress();
149-
if (subnet == null) {
150-
throw new IllegalArgumentException("Invalid subnet: " + subnetCIDR);
151-
}
152-
if (!subnet.isPrefixBlock()) {
153-
throw new IllegalArgumentException(
154-
String.format(
155-
"Subnet %s must be represented as a network prefix block %s",
156-
subnet, subnet.toPrefixBlock()));
157-
}
158-
return subnet;
159-
}
160-
161-
private boolean isOverlapping(SubnetAddress other) {
162-
IPAddress thisSubnet = this.subnet;
163-
IPAddress otherSubnet = other.subnet;
164-
return thisSubnet.contains(otherSubnet.getLower())
165-
|| thisSubnet.contains(otherSubnet.getUpper())
166-
|| otherSubnet.contains(thisSubnet.getLower())
167-
|| otherSubnet.contains(thisSubnet.getUpper());
168-
}
169-
170-
private boolean contains(InetSocketAddress address) {
171-
IPAddress ipAddress = new IPAddressString(address.getAddress().getHostAddress()).getAddress();
172-
if (subnet.isIPv4() && ipAddress.isIPv4Convertible()) {
173-
ipAddress = ipAddress.toIPv4();
174-
} else if (subnet.isIPv6() && ipAddress.isIPv6Convertible()) {
175-
ipAddress = ipAddress.toIPv6();
176-
}
177-
return subnet.contains(ipAddress);
178-
}
179-
}
180152
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.datastax.oss.driver.internal.core.addresstranslation;
2+
3+
import org.junit.Test;
4+
import org.mockito.Mockito;
5+
6+
import java.net.InetAddress;
7+
import java.net.InetSocketAddress;
8+
import java.net.UnknownHostException;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
12+
import static org.assertj.core.api.Assertions.assertThatNoException;
13+
import static org.mockito.Mockito.doReturn;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.when;
17+
18+
public class SubnetAddressTest {
19+
@Test
20+
public void should_return_return_true_on_overlapping_with_another_subnet_address() {
21+
SubnetAddress subnetAddress1 = new SubnetAddress("100.64.0.0/15", mock(InetSocketAddress.class));
22+
SubnetAddress subnetAddress2 = new SubnetAddress("100.65.0.0/16", mock(InetSocketAddress.class));
23+
assertThat(subnetAddress1.isOverlapping(subnetAddress2)).isTrue();
24+
}
25+
26+
@Test
27+
public void should_return_return_false_on_not_overlapping_with_another_subnet_address() {
28+
SubnetAddress subnetAddress1 = new SubnetAddress("100.64.0.0/15", mock(InetSocketAddress.class));
29+
SubnetAddress subnetAddress2 = new SubnetAddress("100.66.0.0/15", mock(InetSocketAddress.class));
30+
assertThat(subnetAddress1.isOverlapping(subnetAddress2)).isFalse();
31+
}
32+
}

0 commit comments

Comments
 (0)