Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### Additions and Improvements
- 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)
- `--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)
- Stop EngineQosTimer as part of shutdown [#9903](https://github.com/hyperledger/besu/pull/9903)
- Add `--max-blobs-per-transaction` CLI option to configure the maximum number of blobs per transaction [#9912](https://github.com/hyperledger/besu/pull/9912)
- 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)
Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ dependencies {
implementation 'com.google.guava:guava'
implementation 'com.google.dagger:dagger'
implementation 'com.graphql-java:graphql-java'
implementation 'commons-net:commons-net'
implementation 'com.github.seancfoley:ipaddress'
implementation 'info.picocli:picocli'
implementation 'io.vertx:vertx-core'
implementation 'io.vertx:vertx-web'
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/org/hyperledger/besu/RunnerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import graphql.GraphQL;
import inet.ipaddr.IPAddress;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
Expand Down Expand Up @@ -194,7 +194,7 @@ public class RunnerBuilder {
private RpcEndpointServiceImpl rpcEndpointServiceImpl;
private JsonRpcIpcConfiguration jsonRpcIpcConfiguration;
private Optional<EnodeDnsConfiguration> enodeDnsConfiguration;
private List<SubnetInfo> allowedSubnets = new ArrayList<>();
private List<IPAddress> allowedSubnets = new ArrayList<>();
private boolean poaDiscoveryRetryBootnodes = true;
private TransactionValidatorServiceImpl transactionValidatorService;

Expand Down Expand Up @@ -610,7 +610,7 @@ public RunnerBuilder enodeDnsConfiguration(final EnodeDnsConfiguration enodeDnsC
* @param allowedSubnets the allowedSubnets
* @return the runner builder
*/
public RunnerBuilder allowedSubnets(final List<SubnetInfo> allowedSubnets) {
public RunnerBuilder allowedSubnets(final List<IPAddress> allowedSubnets) {
this.allowedSubnets = allowedSubnets;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.cli.converter;

import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import picocli.CommandLine;

/** Converts CIDR notation strings to IPAddress prefix blocks. Supports both IPv4 and IPv6. */
public class SubnetCidrConverter implements CommandLine.ITypeConverter<IPAddress> {
/** Default Constructor. */
public SubnetCidrConverter() {}

/**
* Converts an IP address with CIDR notation into an IPAddress prefix block.
*
* @param value The IP address with CIDR notation (e.g. "192.168.1.0/24" or "fd00::/64").
* @return the IPAddress prefix block
*/
@Override
public IPAddress convert(final String value) {
final IPAddressString addrString = new IPAddressString(value);
if (!addrString.isValid() || addrString.getNetworkPrefixLength() == null) {
throw new CommandLine.TypeConversionException(
"Invalid CIDR notation: " + value + ". Expected format: <ip>/<prefix-length>");
}
return addrString.getAddress().toPrefixBlock();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import org.hyperledger.besu.cli.DefaultCommandValues;
import org.hyperledger.besu.cli.converter.PercentageConverter;
import org.hyperledger.besu.cli.converter.SubnetInfoConverter;
import org.hyperledger.besu.cli.converter.SubnetCidrConverter;
import org.hyperledger.besu.cli.util.CommandLineUtils;
import org.hyperledger.besu.ethereum.p2p.discovery.P2PDiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
Expand All @@ -34,7 +34,7 @@
import java.util.stream.Collectors;

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

@Override
public P2PDiscoveryConfiguration toDomainObject() {
Expand Down
28 changes: 15 additions & 13 deletions app/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.base.Splitter;
import com.google.common.io.Resources;
import io.vertx.core.json.JsonObject;
import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -1315,22 +1316,23 @@ public void parsesValidSyncMinPeersOption() {
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}

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

@Test
public void netRestrictInvalidShouldFail() {
final String subnet = "127.0.0.1/abc";
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1/abc", "abc", ""})
public void netRestrictInvalidShouldFail(final String subnet) {
parseCommand("--net-restrict", subnet);
verifyNoInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString(UTF_8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@
import java.util.function.Function;
import java.util.function.Supplier;

import inet.ipaddr.IPAddress;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
Expand Down Expand Up @@ -276,7 +276,7 @@ public abstract class CommandTestAbstract {
@Captor protected ArgumentCaptor<ApiConfiguration> apiConfigurationCaptor;

@Captor protected ArgumentCaptor<EthstatsOptions> ethstatsOptionsArgumentCaptor;
@Captor protected ArgumentCaptor<List<SubnetInfo>> allowedSubnetsArgumentCaptor;
@Captor protected ArgumentCaptor<List<IPAddress>> allowedSubnetsArgumentCaptor;

@BeforeEach
public void initMocks() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,58 @@
package org.hyperledger.besu.cli.converter;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import inet.ipaddr.IPAddress;
import org.junit.jupiter.api.Test;
import picocli.CommandLine;

public class SubnetInfoConverterTest {
public class SubnetCidrConverterTest {

@Test
void testCreateIpRestrictionHandlerWithValidSubnets() {
String subnet = "192.168.1.0/24";
assertThat(parseSubnetRules(subnet).getCidrSignature()).isEqualTo(subnet);
IPAddress result = parseSubnetRules(subnet);
assertThat(result.toString()).isEqualTo(subnet);
}

@Test
void testCreateIpRestrictionHandlerWithValidIpv6Subnet() {
IPAddress result = parseSubnetRules("fd00::/64");
assertThat(result.toString()).isEqualTo("fd00::/64");
}

@Test
void testCreateIpRestrictionHandlerWithInvalidSubnet() {
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("abc"));
assertThatThrownBy(() -> parseSubnetRules("abc"))
.isInstanceOf(CommandLine.TypeConversionException.class);
}

@Test
void testCreateIpRestrictionHandlerMissingCIDR() {
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0"));
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0"))
.isInstanceOf(CommandLine.TypeConversionException.class);
}

@Test
void testCreateIpRestrictionHandlerBigCIDR() {
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0:25"));
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0:25"))
.isInstanceOf(CommandLine.TypeConversionException.class);
}

@Test
void testCreateIpRestrictionHandlerWithInvalidCIDR() {
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0/abc"));
assertThatThrownBy(() -> parseSubnetRules("192.168.1.0/abc"))
.isInstanceOf(CommandLine.TypeConversionException.class);
}

@Test
void testCreateIpRestrictionHandlerWithEmptyString() {
assertThrows(IllegalArgumentException.class, () -> parseSubnetRules(""));
assertThatThrownBy(() -> parseSubnetRules(""))
.isInstanceOf(CommandLine.TypeConversionException.class);
}

private SubnetInfo parseSubnetRules(final String subnet) {
return new SubnetInfoConverter().convert(subnet);
private IPAddress parseSubnetRules(final String subnet) {
return new SubnetCidrConverter().convert(subnet);
}
}
2 changes: 1 addition & 1 deletion ethereum/p2p/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
implementation 'org.owasp.encoder:encoder'
implementation 'org.xerial.snappy:snappy-java'
implementation 'commons-net:commons-net'
implementation 'com.github.seancfoley:ipaddress'

annotationProcessor "org.immutables:value"
implementation "org.immutables:value-annotations"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.List;
import java.util.Optional;

import org.apache.commons.net.util.SubnetUtils;
import inet.ipaddr.IPAddress;
import org.apache.tuweni.bytes.Bytes;

public record P2PDiscoveryConfiguration(
Expand All @@ -37,7 +37,7 @@ public record P2PDiscoveryConfiguration(
Percentage maxRemoteConnectionsPercentage,
Boolean randomPeerPriority,
Collection<Bytes> bannedNodeIds,
List<SubnetUtils.SubnetInfo> allowedSubnets,
List<IPAddress> allowedSubnets,
Boolean poaDiscoveryRetryBootnodes,
List<String> bootNodes,
String discoveryDnsUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

import java.util.List;

import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -29,22 +30,24 @@
* allows for the configuration of permitted subnets and uses these configurations to determine
* whether a peer should be allowed or denied access based on its IP address.
*
* <p>Supports both IPv4 and IPv6 subnets.
*
* <p>Note: If no subnets are specified, all peers are considered permitted by default.
*
* @see PeerPermissions
*/
public class PeerPermissionSubnet extends PeerPermissions {
private static final Logger LOG = LoggerFactory.getLogger(PeerPermissionSubnet.class);

private final List<SubnetInfo> allowedSubnets;
private final List<IPAddress> allowedSubnets;

/**
* Constructs a new {@code PeerPermissionSubnet} instance with specified allowed subnets.
*
* @param allowedSubnets A list of {@link SubnetInfo} objects representing the subnets that are
* allowed to interact with the local node. Cannot be {@code null}.
* @param allowedSubnets A list of {@link IPAddress} prefix blocks representing the subnets that
* are allowed to interact with the local node. Cannot be {@code null}.
*/
public PeerPermissionSubnet(final List<SubnetInfo> allowedSubnets) {
public PeerPermissionSubnet(final List<IPAddress> allowedSubnets) {
this.allowedSubnets = allowedSubnets;
}

Expand All @@ -66,9 +69,14 @@ public boolean isPermitted(final Peer localNode, final Peer remotePeer, final Ac
if (allowedSubnets == null || allowedSubnets.isEmpty()) {
return true;
}
String remotePeerHostAddress = remotePeer.getEnodeURL().getIpAsString();
for (SubnetInfo subnet : allowedSubnets) {
if (subnet.isInRange(remotePeerHostAddress)) {
final String remotePeerHostAddress = remotePeer.getEnodeURL().getIpAsString();
final IPAddress remoteAddress = new IPAddressString(remotePeerHostAddress).getAddress();
if (remoteAddress == null) {
LOG.trace("Could not parse peer address: {}", remotePeerHostAddress);
return false;
}
for (final IPAddress subnet : allowedSubnets) {
if (subnet.contains(remoteAddress)) {
return true;
}
}
Expand Down
Loading
Loading