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
8 changes: 6 additions & 2 deletions besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
import org.hyperledger.besu.nat.NatMethod;
import org.hyperledger.besu.nat.NatService;
import org.hyperledger.besu.nat.core.NatManager;
import org.hyperledger.besu.nat.docker.DockerDetector;
import org.hyperledger.besu.nat.docker.DockerNatManager;
import org.hyperledger.besu.nat.manual.ManualNatManager;
import org.hyperledger.besu.nat.upnp.UpnpNatManager;
import org.hyperledger.besu.plugin.BesuPlugin;
Expand Down Expand Up @@ -339,7 +341,6 @@ public Runner build() {
.orElse(bannedNodes);

final NatService natService = new NatService(buildNatManager(natMethod));

final NetworkBuilder inactiveNetwork = (caps) -> new NoopP2PNetwork();
final NetworkBuilder activeNetwork =
(caps) ->
Expand Down Expand Up @@ -581,13 +582,16 @@ private Optional<NatManager> buildNatManager(final NatMethod natMethod) {
final NatMethod detectedNatMethod =
Optional.of(natMethod)
.filter(not(isEqual(NatMethod.AUTO)))
.orElse(NatService.autoDetectNatMethod());
.orElse(NatService.autoDetectNatMethod(new DockerDetector()));
switch (detectedNatMethod) {
case UPNP:
return Optional.of(new UpnpNatManager());
case MANUAL:
return Optional.of(
new ManualNatManager(p2pAdvertisedHost, p2pListenPort, jsonRpcConfiguration.getPort()));
case DOCKER:
return Optional.of(
new DockerNatManager(p2pAdvertisedHost, p2pListenPort, jsonRpcConfiguration.getPort()));
case NONE:
default:
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,9 @@ public void natMethodOptionIsParsedCorrectly() {
parseCommand("--nat-method", "AUTO");
verify(mockRunnerBuilder).natMethod(eq(NatMethod.AUTO));

parseCommand("--nat-method", "DOCKER");
verify(mockRunnerBuilder).natMethod(eq(NatMethod.DOCKER));

assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
}
Expand All @@ -1404,7 +1407,7 @@ public void parsesInvalidNatMethodOptionsShouldFail() {
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString())
.contains(
"Invalid value for option '--nat-method': expected one of [UPNP, MANUAL, AUTO, NONE] (case-insensitive) but was 'invalid'");
"Invalid value for option '--nat-method': expected one of [UPNP, MANUAL, DOCKER, AUTO, NONE] (case-insensitive) but was 'invalid'");
}

@Test
Expand Down
1 change: 1 addition & 0 deletions nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public enum NatMethod {
UPNP,
MANUAL,
DOCKER,
AUTO,
NONE;

Expand Down
26 changes: 10 additions & 16 deletions nat/src/main/java/org/hyperledger/besu/nat/NatService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
*/
package org.hyperledger.besu.nat;

import static com.google.common.base.Preconditions.checkNotNull;

import org.hyperledger.besu.nat.core.AutoDetectionResult;
import org.hyperledger.besu.nat.core.NatManager;
import org.hyperledger.besu.nat.core.NatMethodAutoDetection;
import org.hyperledger.besu.nat.core.NatMethodDetector;
import org.hyperledger.besu.nat.core.domain.NatPortMapping;
import org.hyperledger.besu.nat.core.domain.NatServiceType;
import org.hyperledger.besu.nat.core.domain.NetworkProtocol;

import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
Expand Down Expand Up @@ -185,20 +183,16 @@ private NatMethod retrieveNatMethod(final Optional<NatManager> natManager) {
}

/**
* Attempts to automatically detect the Nat method being used by the node.
* Attempts to automatically detect the Nat method by applying nat method detectors. Will return
* the first one that succeeds in its detection.
*
* @param natMethodAutoDetections list of nat method auto detections
* @param natMethodDetectors list of nat method auto detections
* @return a {@link NatMethod} equal to NONE if no Nat method has been detected automatically.
*/
public static NatMethod autoDetectNatMethod(
final NatMethodAutoDetection... natMethodAutoDetections) {
checkNotNull(natMethodAutoDetections);
for (NatMethodAutoDetection autoDetection : natMethodAutoDetections) {
final AutoDetectionResult result = autoDetection.shouldBeThisNatMethod();
if (result.isDetectedNatMethod()) {
return result.getNatMethod();
}
}
return NatMethod.NONE;
public static NatMethod autoDetectNatMethod(final NatMethodDetector... natMethodDetectors) {
return Arrays.stream(natMethodDetectors)
.flatMap(natMethodDetector -> natMethodDetector.detect().stream())
.findFirst()
.orElse(NatMethod.NONE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@

package org.hyperledger.besu.nat.core;

import org.hyperledger.besu.nat.NatMethod;

import java.util.Optional;

@FunctionalInterface
public interface NatMethodAutoDetection {
public interface NatMethodDetector {

AutoDetectionResult shouldBeThisNatMethod();
Optional<NatMethod> detect();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright ConsenSys AG.
*
* 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.nat.docker;

import org.hyperledger.besu.nat.NatMethod;
import org.hyperledger.besu.nat.core.NatMethodDetector;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.Stream;

public class DockerDetector implements NatMethodDetector {

@Override
public Optional<NatMethod> detect() {
try (Stream<String> stream = Files.lines(Paths.get("/proc/1/cgroup"))) {
return stream
.filter(line -> line.contains("/docker"))
.findFirst()
.map(__ -> NatMethod.DOCKER);
} catch (IOException e) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright ConsenSys AG.
*
* 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.nat.docker;

import org.hyperledger.besu.nat.NatMethod;
import org.hyperledger.besu.nat.core.AbstractNatManager;
import org.hyperledger.besu.nat.core.domain.NatPortMapping;
import org.hyperledger.besu.nat.core.domain.NatServiceType;
import org.hyperledger.besu.nat.core.domain.NetworkProtocol;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* This class describes the behaviour of the Docker NAT manager. Docker Nat manager add support for
* Docker’s NAT implementation when Besu is being run from a Docker container
*/
public class DockerNatManager extends AbstractNatManager {
protected static final Logger LOG = LogManager.getLogger();

private static final String PORT_MAPPING_TAG = "HOST_PORT_";

private final IpDetector ipDetector;

private final String internalAdvertisedHost;
private final int internalP2pPort;
private final int internalRpcHttpPort;

private final List<NatPortMapping> forwardedPorts;

public DockerNatManager(final String advertisedHost, final int p2pPort, final int rpcHttpPort) {
this(new HostBasedIpDetector(), advertisedHost, p2pPort, rpcHttpPort);
}

public DockerNatManager(
final IpDetector ipDetector,
final String advertisedHost,
final int p2pPort,
final int rpcHttpPort) {
super(NatMethod.DOCKER);
this.ipDetector = ipDetector;
this.internalAdvertisedHost = advertisedHost;
this.internalP2pPort = p2pPort;
this.internalRpcHttpPort = rpcHttpPort;
this.forwardedPorts = buildForwardedPorts();
}

private List<NatPortMapping> buildForwardedPorts() {
try {
final String internalHost = queryLocalIPAddress().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
final String advertisedHost =
retrieveExternalIPAddress().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return Arrays.asList(
new NatPortMapping(
NatServiceType.DISCOVERY,
NetworkProtocol.UDP,
internalHost,
advertisedHost,
internalP2pPort,
getExternalPort(internalP2pPort)),
new NatPortMapping(
NatServiceType.RLPX,
NetworkProtocol.TCP,
internalHost,
advertisedHost,
internalP2pPort,
getExternalPort(internalP2pPort)),
new NatPortMapping(
NatServiceType.JSON_RPC,
NetworkProtocol.TCP,
internalHost,
advertisedHost,
internalRpcHttpPort,
getExternalPort(internalRpcHttpPort)));
} catch (Exception e) {
LOG.warn("Failed to create forwarded port list", e);
}
return Collections.emptyList();
}

@Override
protected void doStart() {
LOG.info("Starting docker NAT manager.");
}

@Override
protected void doStop() {
LOG.info("Stopping docker NAT manager.");
}

@Override
protected CompletableFuture<String> retrieveExternalIPAddress() {
return ipDetector
.detectExternalIp()
.map(CompletableFuture::completedFuture)
.orElse(CompletableFuture.completedFuture(internalAdvertisedHost));
}

@Override
public CompletableFuture<List<NatPortMapping>> getPortMappings() {
return CompletableFuture.completedFuture(forwardedPorts);
}

private int getExternalPort(final int defaultValue) {
return Optional.ofNullable(System.getenv(PORT_MAPPING_TAG + defaultValue))
.map(Integer::valueOf)
.orElse(defaultValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,22 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.hyperledger.besu.nat.core;
package org.hyperledger.besu.nat.docker;

import org.hyperledger.besu.nat.NatMethod;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Optional;

public class AutoDetectionResult {
public class HostBasedIpDetector implements IpDetector {

private final NatMethod natMethod;
private final boolean isDetectedNatMethod;
private static final String HOSTNAME = "HOST_IP";

public AutoDetectionResult(final NatMethod natMethod, final boolean isDetectedNatMethod) {
this.natMethod = natMethod;
this.isDetectedNatMethod = isDetectedNatMethod;
}

public NatMethod getNatMethod() {
return natMethod;
}

public boolean isDetectedNatMethod() {
return isDetectedNatMethod;
@Override
public Optional<String> detectExternalIp() {
try {
return Optional.of(InetAddress.getByName(HOSTNAME).getHostAddress());
Comment thread
RatanRSur marked this conversation as resolved.
} catch (final UnknownHostException e) {
return Optional.empty();
}
}
}
23 changes: 23 additions & 0 deletions nat/src/main/java/org/hyperledger/besu/nat/docker/IpDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright ConsenSys AG.
*
* 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.nat.docker;

import java.util.Optional;

public interface IpDetector {

Optional<String> detectExternalIp();
}
15 changes: 2 additions & 13 deletions nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.nat.core.AutoDetectionResult;
import org.hyperledger.besu.nat.core.NatManager;
import org.hyperledger.besu.nat.core.domain.NatPortMapping;
import org.hyperledger.besu.nat.core.domain.NatServiceType;
Expand Down Expand Up @@ -150,23 +149,13 @@ public void assertThatQueryLocalIPAddressWorksProperlyWithoutNat() {

@Test
public void givenOneAutoDetectionWorksWhenAutoDetectThenReturnCorrectNatMethod() {
final NatMethod natMethod =
NatService.autoDetectNatMethod(NatServiceTest::alwaysTrueShouldBeUpnpMethod);
final NatMethod natMethod = NatService.autoDetectNatMethod(() -> Optional.of(NatMethod.UPNP));
assertThat(natMethod).isEqualTo(NatMethod.UPNP);
}

@Test
public void givenNoAutoDetectionWorksWhenAutoDetectThenReturnEmptyNatMethod() {
final NatMethod natMethod =
NatService.autoDetectNatMethod(NatServiceTest::alwaysFalseShouldBeUpnpMethod);
final NatMethod natMethod = NatService.autoDetectNatMethod(Optional::empty);
assertThat(natMethod).isEqualTo(NatMethod.NONE);
}

private static AutoDetectionResult alwaysTrueShouldBeUpnpMethod() {
return new AutoDetectionResult(NatMethod.UPNP, true);
}

private static AutoDetectionResult alwaysFalseShouldBeUpnpMethod() {
return new AutoDetectionResult(NatMethod.UPNP, false);
}
}
Loading