mirror of https://github.com/hyperledger/besu
[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
parent
358ab092b4
commit
39826b1423
@ -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 |
||||
* 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); |
||||
} |
||||
} |
@ -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(); |
||||
} |
@ -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…
Reference in new issue