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

Add port conflict exception #4565

Merged
merged 16 commits into from
Nov 2, 2022
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
27 changes: 26 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
import org.hyperledger.besu.services.SecurityModuleServiceImpl;
import org.hyperledger.besu.services.StorageServiceImpl;
import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin;
import org.hyperledger.besu.util.InvalidConfigurationException;
import org.hyperledger.besu.util.Log4j2ConfiguratorUtil;
import org.hyperledger.besu.util.NetworkUtility;
import org.hyperledger.besu.util.PermissioningConfigurationValidator;
Expand Down Expand Up @@ -1972,7 +1973,7 @@ private void issueOptionWarnings() {

private void configure() throws Exception {
checkPortClash();

checkIfRequiredPortsAreAvailable();
syncMode = getDefaultSyncModeIfNotSet(syncMode);

ethNetworkConfig = updateNetworkConfig(network);
Expand Down Expand Up @@ -3126,6 +3127,30 @@ private void checkPortClash() {
});
}

private void checkIfRequiredPortsAreAvailable() {
final List<Integer> unavailablePorts = new ArrayList<>();
getEffectivePorts().stream()
.filter(Objects::nonNull)
.filter(port -> port > 0)
.forEach(
port -> {
if (port.equals(p2PDiscoveryOptionGroup.p2pPort)
&& !NetworkUtility.isPortAvailable(port)) {
unavailablePorts.add(port);
}
if (!port.equals(p2PDiscoveryOptionGroup.p2pPort)
&& !NetworkUtility.isPortAvailableForTcp(port)) {
unavailablePorts.add(port);
}
});
if (!unavailablePorts.isEmpty()) {
throw new InvalidConfigurationException(
"Port(s) '"
+ unavailablePorts
+ "' already in use. Check for other processes using the port(s).");
}
}

/**
* * Gets the list of effective ports (ports that are enabled).
*
Expand Down
14 changes: 14 additions & 0 deletions besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
Expand Down Expand Up @@ -5371,4 +5372,17 @@ public void posBlockCreationMaxTimeOutOfAllowedRange() {
assertThat(commandErrorOutput.toString(UTF_8))
.contains("--Xpos-block-creation-max-time must be positive and ≤ 12000");
}

@Test
public void portInUseReportsError() throws IOException {
final ServerSocket serverSocket = new ServerSocket(8545);

parseCommand("--rpc-http-enabled");

assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Port(s) '[8545]' already in use. Check for other processes using the port(s).");

serverSocket.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ public CompletableFuture<?> start() {

final CompletableFuture<?> resultFuture = new CompletableFuture<>();
try {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to add the extra empty line?

// Create the HTTP server and a router object.
httpServer = vertx.createHttpServer(getHttpServerOptions());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ public CompletableFuture<?> start() {
"Starting Websocket service on {}:{}", configuration.getHost(), configuration.getPort());

final CompletableFuture<?> resultFuture = new CompletableFuture<>();

httpServer =
vertx
.createHttpServer(
Expand Down
33 changes: 33 additions & 0 deletions util/src/main/java/org/hyperledger/besu/util/NetworkUtility.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,27 @@
*/
package org.hyperledger.besu.util;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.function.Supplier;

import com.google.common.base.Suppliers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NetworkUtility {
public static final String INADDR_ANY = "0.0.0.0";
public static final String INADDR6_ANY = "0:0:0:0:0:0:0:0";

private static final Logger LOG = LoggerFactory.getLogger(NetworkUtility.class);

private NetworkUtility() {}

private static final Supplier<Boolean> ipv6Available =
Expand Down Expand Up @@ -98,4 +105,30 @@ public static void checkPort(final int port, final String portTypeName) {
"%s port requires a value between 1 and 65535. Got %d.", portTypeName, port));
}
}

public static boolean isPortAvailableForTcp(final int port) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be private?

try (final ServerSocket serverSocket = new ServerSocket()) {
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(port));
return true;
} catch (IOException ex) {
LOG.trace(String.format("Failed to open port %d for TCP", port), ex);
}
return false;
}

private static boolean isPortAvailableForUdp(final int port) {
try (final DatagramSocket datagramSocket = new DatagramSocket(null)) {
datagramSocket.setReuseAddress(true);
datagramSocket.bind(new InetSocketAddress(port));
return true;
} catch (IOException ex) {
LOG.trace(String.format("failed to open port %d for UDP", port), ex);
}
return false;
}

public static boolean isPortAvailable(final int port) {
jframe marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider renaming to make to clear this is checking both udp and tcp. maybe rename to isPortAvailableForTcpAndUdp

return isPortAvailableForTcp(port) && isPortAvailableForUdp(port);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;

import org.junit.Test;

Expand All @@ -31,4 +33,11 @@ public void urlForSocketAddressHandlesIPv6() {
final InetSocketAddress ipv6 = new InetSocketAddress("1:2:3:4:5:6:7:8", 80);
assertThat(NetworkUtility.urlForSocketAddress("http", ipv6)).contains("[1:2:3:4:5:6:7:8]");
}

@Test
public void assertPortIsNotAvailable() throws IOException {
final ServerSocket serverSocket = new ServerSocket(8541);
assertThat(!NetworkUtility.isPortAvailable(8541)).isEqualTo(true);
serverSocket.close();
}
}