Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAN-3143] Handle zero port better in NAT #147

Merged
merged 6 commits into from
Oct 31, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,22 @@ public void start() {
return;
}

final int configuredDiscoveryPort = config.getDiscovery().getBindPort();
final int configuredRlpxPort = config.getRlpx().getBindPort();

final int listeningPort = rlpxAgent.start().join();
final int discoveryPort =
peerDiscoveryAgent
.start(
(configuredDiscoveryPort == 0 && configuredRlpxPort == 0)
? listeningPort
: configuredDiscoveryPort)
.join();

if (natManager.isPresent()) {
this.configureNatEnvironment();
this.configureNatEnvironment(listeningPort, discoveryPort);
}

final int listeningPort = rlpxAgent.start().join();
final int discoveryPort = peerDiscoveryAgent.start(listeningPort).join();
setLocalNode(listeningPort, discoveryPort);

peerBondedObserverId =
Expand Down Expand Up @@ -362,8 +372,9 @@ private void setLocalNode(final int listeningPort, final int discoveryPort) {
localNode.setEnode(localEnode);
}

private void configureNatEnvironment() {
CompletableFuture<String> natQueryFuture = this.natManager.get().queryExternalIPAddress();
private void configureNatEnvironment(final int listeningPort, final int discoveryPort) {
final CompletableFuture<String> natQueryFuture =
this.natManager.orElseThrow().queryExternalIPAddress();
String externalAddress = null;
try {
final int timeoutSeconds = 60;
Expand All @@ -379,19 +390,15 @@ private void configureNatEnvironment() {
LOG.info("External IP detected: " + externalAddress);
this.natManager
.get()
.requestPortForward(
this.config.getDiscovery().getBindPort(),
UpnpNatManager.Protocol.UDP,
"besu-discovery");
.requestPortForward(discoveryPort, UpnpNatManager.Protocol.UDP, "besu-discovery");
this.natManager
.get()
.requestPortForward(
this.config.getRlpx().getBindPort(), UpnpNatManager.Protocol.TCP, "besu-rlpx");
.requestPortForward(listeningPort, UpnpNatManager.Protocol.TCP, "besu-rlpx");
} else {
LOG.info("No external IP detected within timeout.");
}

} catch (Exception e) {
} catch (final Exception e) {
LOG.error("Error configuring NAT environment", e);
}
natExternalAddress = Optional.ofNullable(externalAddress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public void before() {
lenient().when(rlpxAgent.stop()).thenReturn(CompletableFuture.completedFuture(null));
lenient()
.when(discoveryAgent.start(anyInt()))
.thenReturn(CompletableFuture.completedFuture(30303));
.thenAnswer(
invocation ->
CompletableFuture.completedFuture(invocation.getArgument(0, Integer.class)));
lenient().when(discoveryAgent.stop()).thenReturn(CompletableFuture.completedFuture(null));
lenient()
.when(discoveryAgent.observePeerBondedEvents(discoverySubscriberCaptor.capture()))
Expand Down
114 changes: 8 additions & 106 deletions nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
*/
package org.hyperledger.besu.nat.upnp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -38,10 +41,8 @@
import org.jupnp.registry.Registry;
import org.jupnp.registry.RegistryListener;
import org.jupnp.support.igd.callback.GetExternalIP;
import org.jupnp.support.igd.callback.GetStatusInfo;
import org.jupnp.support.igd.callback.PortMappingAdd;
import org.jupnp.support.igd.callback.PortMappingDelete;
import org.jupnp.support.model.Connection;
import org.jupnp.support.model.PortMapping;

/**
Expand Down Expand Up @@ -178,23 +179,6 @@ synchronized CompletableFuture<RemoteService> getWANIPConnectionService() {
return getService(SERVICE_TYPE_WAN_IP_CONNECTION);
}

/**
* Get the local address on which we discovered our external IP address.
*
* <p>This can be useful to distinguish which network interface the external address was
* discovered on.
*
* @return the local address on which our GetExternalIP was discovered on, or an empty value if no
* GetExternalIP query has been performed successfully.
*/
public Optional<String> getDiscoveredOnLocalAddress() {
if (!started) {
throw new IllegalStateException(
"Cannot call getDiscoveredOnLocalAddress() when in stopped state");
}
return this.discoveredOnLocalAddress;
}

/**
* Returns a CompletableFuture that will wait for the given service type to be discovered. No new
* query will be performed, and if the service has already been discovered, the future will
Expand Down Expand Up @@ -225,8 +209,6 @@ public synchronized CompletableFuture<String> queryExternalIPAddress() {
*
* <p>Note that this is not synchronized, as it is expected to be called within an
* already-synchronized context ({@link #start()}).
*
* @return A CompletableFuture that can be used to query the result (or error).
*/
private void initiateExternalIpQuery() {
discoverService(SERVICE_TYPE_WAN_IP_CONNECTION)
Expand Down Expand Up @@ -285,46 +267,6 @@ public void failure(
});
}

/**
* Sends a UPnP request to the discovered IGD to request status info.
*
* @return A CompletableFuture that can be used to query the result (or error).
*/
public CompletableFuture<Connection.StatusInfo> queryStatusInfo() {
if (!started) {
throw new IllegalStateException("Cannot call queryStatusInfo() when in stopped state");
}
final CompletableFuture<Connection.StatusInfo> upnpQueryFuture = new CompletableFuture<>();

return discoverService(SERVICE_TYPE_WAN_IP_CONNECTION)
.thenCompose(
service -> {
GetStatusInfo callback =
new GetStatusInfo(service) {
@Override
public void success(final Connection.StatusInfo statusInfo) {
upnpQueryFuture.complete(statusInfo);
}

/**
* Because the underlying jupnp library omits generics info in this method
* signature, we must too when we override it.
*/
@Override
@SuppressWarnings("rawtypes")
public void failure(
final ActionInvocation invocation,
final UpnpResponse operation,
final String msg) {
upnpQueryFuture.completeExceptionally(new Exception(msg));
}
};
upnpService.getControlPoint().execute(callback);

return upnpQueryFuture;
});
}

/**
* Convenience function to call {@link #requestPortForward(PortMapping)} with the following
* defaults:
Expand All @@ -337,12 +279,11 @@ public void failure(
* @param port is the port to be used for both internal and external port values
* @param protocol is either UDP or TCP
* @param description is a free-form description, often displayed in router UIs
* @return A CompletableFuture which will provide the results of the request
*/
public CompletableFuture<Void> requestPortForward(
public void requestPortForward(
final int port, final Protocol protocol, final String description) {

return this.requestPortForward(
this.requestPortForward(
new PortMapping(
true,
new UnsignedIntegerFourBytes(0),
Expand All @@ -354,55 +295,16 @@ public CompletableFuture<Void> requestPortForward(
description));
}

/**
* Convenience function to avoid use of PortMapping object. Takes the same arguments as are in a
* PortMapping object and constructs such an object for the caller.
*
* <p>This method chains to the {@link #requestPortForward(PortMapping)} method.
*
* @param enabled specifies whether or not the PortMapping is enabled
* @param leaseDurationSeconds is the duration of the PortMapping, in seconds
* @param remoteHost is a domain name or IP address used to filter which remote source this
* forwarding can apply to
* @param externalPort is the source port (the port visible to the Internet)
* @param internalPort is the destination port (the port to be forwarded to)
* @param internalClient is the destination host on the local LAN
* @param protocol is either UDP or TCP
* @param description is a free-form description, often displayed in router UIs
* @return A CompletableFuture which will provide the results of the request
*/
public CompletableFuture<Void> requestPortForward(
final boolean enabled,
final int leaseDurationSeconds,
final String remoteHost,
final int externalPort,
final int internalPort,
final String internalClient,
final Protocol protocol,
final String description) {

return this.requestPortForward(
new PortMapping(
enabled,
new UnsignedIntegerFourBytes(leaseDurationSeconds),
remoteHost,
new UnsignedIntegerTwoBytes(externalPort),
new UnsignedIntegerTwoBytes(internalPort),
internalClient,
toJupnpProtocol(protocol),
description));
}

/**
* Sends a UPnP request to the discovered IGD to request a port forward.
*
* @param portMapping is a portMapping object describing the desired port mapping parameters.
* @return A CompletableFuture that can be used to query the result (or error).
*/
private CompletableFuture<Void> requestPortForward(final PortMapping portMapping) {
if (!started) {
throw new IllegalStateException("Cannot call requestPortForward() when in stopped state");
}
checkArgument(
portMapping.getInternalPort().getValue() != 0, "Cannot map to internal port zero.");
checkState(started, "Cannot call requestPortForward() when in stopped state");

CompletableFuture<Void> upnpQueryFuture = new CompletableFuture<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,21 @@ public void startDoesNothingWhenAlreadyStarted() throws Exception {
}

@Test
public void getDiscoveredOnLocalAddressThrowsWhenCalledBeforeStart() throws Exception {

assertThatThrownBy(
() -> {
upnpManager.getDiscoveredOnLocalAddress();
})
.isInstanceOf(IllegalStateException.class);
}

@Test
public void queryStatusInfoThrowsWhenCalledBeforeStart() throws Exception {
public void requestPortForwardThrowsWhenCalledBeforeStart() throws Exception {

assertThatThrownBy(
() -> {
upnpManager.queryStatusInfo();
upnpManager.requestPortForward(80, UpnpNatManager.Protocol.TCP, "");
})
.isInstanceOf(IllegalStateException.class);
}

@Test
public void requestPortForwardThrowsWhenCalledBeforeStart() throws Exception {
public void requestPortForwardThrowsWhenPortIsZero() {
upnpManager.start();

assertThatThrownBy(
() -> {
upnpManager.requestPortForward(0, UpnpNatManager.Protocol.TCP, "");
})
.isInstanceOf(IllegalStateException.class);
assertThatThrownBy(() -> upnpManager.requestPortForward(0, UpnpNatManager.Protocol.TCP, ""))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
Expand Down