[BOUNTY-2] Add NAT Docker Support (#368)

* add docker detection

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* add port mapping detection

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* add tests and refactor ip detection

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* clean RunnerBuilder

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* clean useless modification

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* spotless

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* resolve tests issues

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

* streamline auto detection

Signed-off-by: Ratan Rai Sur <ratan.r.sur@gmail.com>

Co-authored-by: Abdelhamid Bakhta <45264458+abdelhamidbakhta@users.noreply.github.com>
Co-authored-by: Ratan Rai Sur <ratan.r.sur@gmail.com>
pull/396/head
Karim T 5 years ago committed by GitHub
parent 358ab092b4
commit 39826b1423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  2. 5
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  3. 1
      nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java
  4. 26
      nat/src/main/java/org/hyperledger/besu/nat/NatService.java
  5. 8
      nat/src/main/java/org/hyperledger/besu/nat/core/NatMethodDetector.java
  6. 40
      nat/src/main/java/org/hyperledger/besu/nat/docker/DockerDetector.java
  7. 129
      nat/src/main/java/org/hyperledger/besu/nat/docker/DockerNatManager.java
  8. 27
      nat/src/main/java/org/hyperledger/besu/nat/docker/HostBasedIpDetector.java
  9. 23
      nat/src/main/java/org/hyperledger/besu/nat/docker/IpDetector.java
  10. 15
      nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java
  11. 130
      nat/src/test/java/org/hyperledger/besu/nat/docker/DockerNatManagerTest.java

@ -91,6 +91,8 @@ import org.hyperledger.besu.metrics.prometheus.MetricsService;
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;
@ -339,7 +341,6 @@ public class RunnerBuilder {
.orElse(bannedNodes);
final NatService natService = new NatService(buildNatManager(natMethod));
final NetworkBuilder inactiveNetwork = (caps) -> new NoopP2PNetwork();
final NetworkBuilder activeNetwork =
(caps) ->
@ -581,13 +582,16 @@ public class RunnerBuilder {
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();

@ -1392,6 +1392,9 @@ public class BesuCommandTest extends CommandTestAbstract {
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();
}
@ -1404,7 +1407,7 @@ public class BesuCommandTest extends CommandTestAbstract {
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

@ -17,6 +17,7 @@ package org.hyperledger.besu.nat;
public enum NatMethod {
UPNP,
MANUAL,
DOCKER,
AUTO,
NONE;

@ -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;
@ -185,20 +183,16 @@ public class NatService {
}
/**
* 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);
}
}

@ -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();
}

@ -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();
}
}
}

@ -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
* Dockers 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);
}
}

@ -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;
@Override
public Optional<String> detectExternalIp() {
try {
return Optional.of(InetAddress.getByName(HOSTNAME).getHostAddress());
} catch (final UnknownHostException e) {
return Optional.empty();
}
public NatMethod getNatMethod() {
return natMethod;
}
public boolean isDetectedNatMethod() {
return isDetectedNatMethod;
}
}

@ -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();
}

@ -20,7 +20,6 @@ import static org.mockito.Mockito.mock;
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;
@ -150,23 +149,13 @@ public class NatServiceTest {
@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);
}
}

@ -0,0 +1,130 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
public final class DockerNatManagerTest {
private final String advertisedHost = "99.45.69.12";
private final String detectedAdvertisedHost = "199.45.69.12";
private final int p2pPort = 1;
private final int rpcHttpPort = 2;
@Mock private HostBasedIpDetector hostBasedIpDetector;
private DockerNatManager natManager;
@Before
public void initialize() {
hostBasedIpDetector = mock(HostBasedIpDetector.class);
when(hostBasedIpDetector.detectExternalIp()).thenReturn(Optional.of(detectedAdvertisedHost));
natManager = new DockerNatManager(hostBasedIpDetector, advertisedHost, p2pPort, rpcHttpPort);
natManager.start();
}
@Test
public void assertThatExternalIPIsEqualToRemoteHost()
throws ExecutionException, InterruptedException {
assertThat(natManager.queryExternalIPAddress().get()).isEqualTo(detectedAdvertisedHost);
}
@Test
public void assertThatExternalIPIsEqualToDefaultHostIfIpDetectorCannotRetrieveIP()
throws ExecutionException, InterruptedException {
when(hostBasedIpDetector.detectExternalIp()).thenReturn(Optional.empty());
assertThat(natManager.queryExternalIPAddress().get()).isEqualTo(advertisedHost);
}
@Test
public void assertThatLocalIPIsEqualToLocalHost()
throws ExecutionException, InterruptedException, UnknownHostException {
final String internalHost = InetAddress.getLocalHost().getHostAddress();
assertThat(natManager.queryLocalIPAddress().get()).isEqualTo(internalHost);
}
@Test
public void assertThatMappingForDiscoveryWorks() throws UnknownHostException {
final String internalHost = InetAddress.getLocalHost().getHostAddress();
final NatPortMapping mapping =
natManager.getPortMapping(NatServiceType.DISCOVERY, NetworkProtocol.UDP);
final NatPortMapping expectedMapping =
new NatPortMapping(
NatServiceType.DISCOVERY,
NetworkProtocol.UDP,
internalHost,
detectedAdvertisedHost,
p2pPort,
p2pPort);
assertThat(mapping).isEqualToComparingFieldByField(expectedMapping);
}
@Test
public void assertThatMappingForJsonRpcWorks() throws UnknownHostException {
final String internalHost = InetAddress.getLocalHost().getHostAddress();
final NatPortMapping mapping =
natManager.getPortMapping(NatServiceType.JSON_RPC, NetworkProtocol.TCP);
final NatPortMapping expectedMapping =
new NatPortMapping(
NatServiceType.JSON_RPC,
NetworkProtocol.TCP,
internalHost,
detectedAdvertisedHost,
rpcHttpPort,
rpcHttpPort);
assertThat(mapping).isEqualToComparingFieldByField(expectedMapping);
}
@Test
public void assertThatMappingForRlpxWorks() throws UnknownHostException {
final String internalHost = InetAddress.getLocalHost().getHostAddress();
final NatPortMapping mapping =
natManager.getPortMapping(NatServiceType.RLPX, NetworkProtocol.TCP);
final NatPortMapping expectedMapping =
new NatPortMapping(
NatServiceType.RLPX,
NetworkProtocol.TCP,
internalHost,
detectedAdvertisedHost,
p2pPort,
p2pPort);
assertThat(mapping).isEqualToComparingFieldByField(expectedMapping);
}
}
Loading…
Cancel
Save