Skip to content

Commit 7bbace5

Browse files
authored
Support IPv6 subnets in --net-restrict (besu-eth#10028)
* Replace commons-net SubnetUtils with IPAddress library for IPv6 subnet support Apache Commons Net SubnetUtils only supports IPv4 CIDR notation, causing --net-restrict to fail at CLI parse time for IPv6 subnets (e.g. fd00::/64). Replace it with the IPAddress library (com.github.seancfoley:ipaddress) which supports both IPv4 and IPv6 CIDR notation natively. Closes besu-eth#10026 Signed-off-by: Usman Saleem <usman@usmans.info> * Address PR review: rename SubnetInfoConverter and add mixed IPv4+IPv6 test - Rename SubnetInfoConverter to SubnetCidrConverter to match new IPAddress return type - Add mixed IPv4+IPv6 test case ("127.0.0.0/24,fd00::/64") to BesuCommandTest parameterized net-restrict tests Signed-off-by: Usman Saleem <usman@usmans.info> * Add changelog entry for IPv6 --net-restrict support Signed-off-by: Usman Saleem <usman@usmans.info> --------- Signed-off-by: Usman Saleem <usman@usmans.info>
1 parent 1bf859b commit 7bbace5

File tree

15 files changed

+150
-94
lines changed

15 files changed

+150
-94
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
### Additions and Improvements
2828
- Add IPv6 dual-stack support for DiscV5 peer discovery (enabled via `--Xv5-discovery-enabled`): new `--p2p-host-ipv6`, `--p2p-interface-ipv6`, and `--p2p-port-ipv6` CLI options enable a second UDP discovery socket; `--p2p-ipv6-outbound-enabled` controls whether IPv6 is preferred for outbound connections when a peer advertises both address families [#9763](https://github.com/hyperledger/besu/pull/9763); RLPx now also binds a second TCP socket on the IPv6 interface so IPv6-only peers can establish connections [#9873](https://github.com/hyperledger/besu/pull/9873)
29+
- `--net-restrict` now supports IPv6 CIDR notation (e.g. `fd00::/64`) in addition to IPv4, enabling subnet-based peer filtering in IPv6 and dual-stack deployments [#10028](https://github.com/besu-eth/besu/pull/10028)
2930
- Stop EngineQosTimer as part of shutdown [#9903](https://github.com/hyperledger/besu/pull/9903)
3031
- Add `--max-blobs-per-transaction` CLI option to configure the maximum number of blobs per transaction [#9912](https://github.com/hyperledger/besu/pull/9912)
3132
- Add `--max-blobs-per-block` CLI option to configure the maximum number of blobs per block when block building [#9983](https://github.com/hyperledger/besu/pull/9983)

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ dependencies {
7171
implementation 'com.google.guava:guava'
7272
implementation 'com.google.dagger:dagger'
7373
implementation 'com.graphql-java:graphql-java'
74-
implementation 'commons-net:commons-net'
74+
implementation 'com.github.seancfoley:ipaddress'
7575
implementation 'info.picocli:picocli'
7676
implementation 'io.vertx:vertx-core'
7777
implementation 'io.vertx:vertx-web'

app/src/main/java/org/hyperledger/besu/RunnerBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@
143143
import com.google.common.base.Preconditions;
144144
import com.google.common.base.Strings;
145145
import graphql.GraphQL;
146+
import inet.ipaddr.IPAddress;
146147
import io.vertx.core.Vertx;
147148
import io.vertx.core.VertxOptions;
148-
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
149149
import org.apache.tuweni.bytes.Bytes;
150150
import org.apache.tuweni.units.bigints.UInt256;
151151
import org.slf4j.Logger;
@@ -194,7 +194,7 @@ public class RunnerBuilder {
194194
private RpcEndpointServiceImpl rpcEndpointServiceImpl;
195195
private JsonRpcIpcConfiguration jsonRpcIpcConfiguration;
196196
private Optional<EnodeDnsConfiguration> enodeDnsConfiguration;
197-
private List<SubnetInfo> allowedSubnets = new ArrayList<>();
197+
private List<IPAddress> allowedSubnets = new ArrayList<>();
198198
private boolean poaDiscoveryRetryBootnodes = true;
199199
private TransactionValidatorServiceImpl transactionValidatorService;
200200

@@ -610,7 +610,7 @@ public RunnerBuilder enodeDnsConfiguration(final EnodeDnsConfiguration enodeDnsC
610610
* @param allowedSubnets the allowedSubnets
611611
* @return the runner builder
612612
*/
613-
public RunnerBuilder allowedSubnets(final List<SubnetInfo> allowedSubnets) {
613+
public RunnerBuilder allowedSubnets(final List<IPAddress> allowedSubnets) {
614614
this.allowedSubnets = allowedSubnets;
615615
return this;
616616
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.cli.converter;
16+
17+
import inet.ipaddr.IPAddress;
18+
import inet.ipaddr.IPAddressString;
19+
import picocli.CommandLine;
20+
21+
/** Converts CIDR notation strings to IPAddress prefix blocks. Supports both IPv4 and IPv6. */
22+
public class SubnetCidrConverter implements CommandLine.ITypeConverter<IPAddress> {
23+
/** Default Constructor. */
24+
public SubnetCidrConverter() {}
25+
26+
/**
27+
* Converts an IP address with CIDR notation into an IPAddress prefix block.
28+
*
29+
* @param value The IP address with CIDR notation (e.g. "192.168.1.0/24" or "fd00::/64").
30+
* @return the IPAddress prefix block
31+
*/
32+
@Override
33+
public IPAddress convert(final String value) {
34+
final IPAddressString addrString = new IPAddressString(value);
35+
if (!addrString.isValid() || addrString.getNetworkPrefixLength() == null) {
36+
throw new CommandLine.TypeConversionException(
37+
"Invalid CIDR notation: " + value + ". Expected format: <ip>/<prefix-length>");
38+
}
39+
return addrString.getAddress().toPrefixBlock();
40+
}
41+
}

app/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java

Lines changed: 0 additions & 36 deletions
This file was deleted.

app/src/main/java/org/hyperledger/besu/cli/options/P2PDiscoveryOptions.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import org.hyperledger.besu.cli.DefaultCommandValues;
1818
import org.hyperledger.besu.cli.converter.PercentageConverter;
19-
import org.hyperledger.besu.cli.converter.SubnetInfoConverter;
19+
import org.hyperledger.besu.cli.converter.SubnetCidrConverter;
2020
import org.hyperledger.besu.cli.util.CommandLineUtils;
2121
import org.hyperledger.besu.ethereum.p2p.discovery.P2PDiscoveryConfiguration;
2222
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
@@ -34,7 +34,7 @@
3434
import java.util.stream.Collectors;
3535

3636
import com.google.common.net.InetAddresses;
37-
import org.apache.commons.net.util.SubnetUtils;
37+
import inet.ipaddr.IPAddress;
3838
import org.apache.tuweni.bytes.Bytes;
3939
import org.slf4j.Logger;
4040
import org.slf4j.LoggerFactory;
@@ -257,10 +257,10 @@ public InetAddress autoDiscoverDefaultIP() {
257257
names = {"--net-restrict"},
258258
arity = "1..*",
259259
split = ",",
260-
converter = SubnetInfoConverter.class,
260+
converter = SubnetCidrConverter.class,
261261
description =
262262
"Comma-separated list of allowed IP subnets (e.g., '192.168.1.0/24,10.0.0.0/8').")
263-
private List<SubnetUtils.SubnetInfo> allowedSubnets;
263+
private List<IPAddress> allowedSubnets;
264264

265265
@Override
266266
public P2PDiscoveryConfiguration toDomainObject() {

app/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import java.util.stream.Collectors;
104104
import java.util.stream.Stream;
105105

106+
import com.google.common.base.Splitter;
106107
import com.google.common.io.Resources;
107108
import io.vertx.core.json.JsonObject;
108109
import org.apache.tuweni.bytes.Bytes;
@@ -1315,22 +1316,23 @@ public void parsesValidSyncMinPeersOption() {
13151316
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
13161317
}
13171318

1318-
@Test
1319-
public void netRestrictParsedCorrectly() {
1320-
final String subnet1 = "127.0.0.1/24";
1321-
final String subnet2 = "10.0.0.1/24";
1322-
parseCommand("--net-restrict", String.join(",", subnet1, subnet2));
1319+
@ParameterizedTest
1320+
@ValueSource(
1321+
strings = {"127.0.0.0/24,10.0.0.0/24", "fd00::/64,fe80::/10", "127.0.0.0/24,fd00::/64"})
1322+
public void netRestrictParsedCorrectly(final String subnets) {
1323+
parseCommand("--net-restrict", subnets);
13231324
verify(mockRunnerBuilder).allowedSubnets(allowedSubnetsArgumentCaptor.capture());
1324-
assertThat(allowedSubnetsArgumentCaptor.getValue().size()).isEqualTo(2);
1325-
assertThat(allowedSubnetsArgumentCaptor.getValue().get(0).getCidrSignature())
1326-
.isEqualTo(subnet1);
1327-
assertThat(allowedSubnetsArgumentCaptor.getValue().get(1).getCidrSignature())
1328-
.isEqualTo(subnet2);
1325+
final List<String> expected = Splitter.on(',').splitToList(subnets);
1326+
assertThat(allowedSubnetsArgumentCaptor.getValue()).hasSize(expected.size());
1327+
for (int i = 0; i < expected.size(); i++) {
1328+
assertThat(allowedSubnetsArgumentCaptor.getValue().get(i).toString())
1329+
.isEqualTo(expected.get(i));
1330+
}
13291331
}
13301332

1331-
@Test
1332-
public void netRestrictInvalidShouldFail() {
1333-
final String subnet = "127.0.0.1/abc";
1333+
@ParameterizedTest
1334+
@ValueSource(strings = {"127.0.0.1/abc", "abc", ""})
1335+
public void netRestrictInvalidShouldFail(final String subnet) {
13341336
parseCommand("--net-restrict", subnet);
13351337
verifyNoInteractions(mockRunnerBuilder);
13361338
assertThat(commandErrorOutput.toString(UTF_8))

app/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@
114114
import java.util.function.Function;
115115
import java.util.function.Supplier;
116116

117+
import inet.ipaddr.IPAddress;
117118
import io.opentelemetry.api.GlobalOpenTelemetry;
118119
import io.vertx.core.Vertx;
119120
import io.vertx.core.json.JsonObject;
120121
import jakarta.validation.constraints.NotEmpty;
121122
import jakarta.validation.constraints.NotNull;
122-
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
123123
import org.apache.commons.text.StringEscapeUtils;
124124
import org.apache.tuweni.bytes.Bytes;
125125
import org.apache.tuweni.bytes.Bytes32;
@@ -276,7 +276,7 @@ public abstract class CommandTestAbstract {
276276
@Captor protected ArgumentCaptor<ApiConfiguration> apiConfigurationCaptor;
277277

278278
@Captor protected ArgumentCaptor<EthstatsOptions> ethstatsOptionsArgumentCaptor;
279-
@Captor protected ArgumentCaptor<List<SubnetInfo>> allowedSubnetsArgumentCaptor;
279+
@Captor protected ArgumentCaptor<List<IPAddress>> allowedSubnetsArgumentCaptor;
280280

281281
@BeforeEach
282282
public void initMocks() throws Exception {

app/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java renamed to app/src/test/java/org/hyperledger/besu/cli/converter/SubnetCidrConverterTest.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,58 @@
1515
package org.hyperledger.besu.cli.converter;
1616

1717
import static org.assertj.core.api.Assertions.assertThat;
18-
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1919

20-
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
20+
import inet.ipaddr.IPAddress;
2121
import org.junit.jupiter.api.Test;
22+
import picocli.CommandLine;
2223

23-
public class SubnetInfoConverterTest {
24+
public class SubnetCidrConverterTest {
2425

2526
@Test
2627
void testCreateIpRestrictionHandlerWithValidSubnets() {
2728
String subnet = "192.168.1.0/24";
28-
assertThat(parseSubnetRules(subnet).getCidrSignature()).isEqualTo(subnet);
29+
IPAddress result = parseSubnetRules(subnet);
30+
assertThat(result.toString()).isEqualTo(subnet);
31+
}
32+
33+
@Test
34+
void testCreateIpRestrictionHandlerWithValidIpv6Subnet() {
35+
IPAddress result = parseSubnetRules("fd00::/64");
36+
assertThat(result.toString()).isEqualTo("fd00::/64");
2937
}
3038

3139
@Test
3240
void testCreateIpRestrictionHandlerWithInvalidSubnet() {
33-
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("abc"));
41+
assertThatThrownBy(() -> parseSubnetRules("abc"))
42+
.isInstanceOf(CommandLine.TypeConversionException.class);
3443
}
3544

3645
@Test
3746
void testCreateIpRestrictionHandlerMissingCIDR() {
38-
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0"));
47+
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0"))
48+
.isInstanceOf(CommandLine.TypeConversionException.class);
3949
}
4050

4151
@Test
4252
void testCreateIpRestrictionHandlerBigCIDR() {
43-
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0:25"));
53+
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0:25"))
54+
.isInstanceOf(CommandLine.TypeConversionException.class);
4455
}
4556

4657
@Test
4758
void testCreateIpRestrictionHandlerWithInvalidCIDR() {
48-
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0/abc"));
59+
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0/abc"))
60+
.isInstanceOf(CommandLine.TypeConversionException.class);
4961
}
5062

5163
@Test
5264
void testCreateIpRestrictionHandlerWithEmptyString() {
53-
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules(""));
65+
assertThatThrownBy(() -> parseSubnetRules(""))
66+
.isInstanceOf(CommandLine.TypeConversionException.class);
5467
}
5568

56-
private SubnetInfo parseSubnetRules(final String subnet) {
57-
return new SubnetInfoConverter().convert(subnet);
69+
private IPAddress parseSubnetRules(final String subnet) {
70+
return new SubnetCidrConverter().convert(subnet);
5871
}
5972
}

ethereum/p2p/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ dependencies {
5656
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
5757
implementation 'org.owasp.encoder:encoder'
5858
implementation 'org.xerial.snappy:snappy-java'
59-
implementation 'commons-net:commons-net'
59+
implementation 'com.github.seancfoley:ipaddress'
6060

6161
annotationProcessor "org.immutables:value"
6262
implementation "org.immutables:value-annotations"

0 commit comments

Comments
 (0)