diff --git a/modules/dockerized-services/impl/src/main/java/eu/cloudnetservice/modules/docker/impl/DockerizedLocalCloudServiceFactory.java b/modules/dockerized-services/impl/src/main/java/eu/cloudnetservice/modules/docker/impl/DockerizedLocalCloudServiceFactory.java index 9c57d6fbd8..a746af6a59 100644 --- a/modules/dockerized-services/impl/src/main/java/eu/cloudnetservice/modules/docker/impl/DockerizedLocalCloudServiceFactory.java +++ b/modules/dockerized-services/impl/src/main/java/eu/cloudnetservice/modules/docker/impl/DockerizedLocalCloudServiceFactory.java @@ -27,6 +27,7 @@ import eu.cloudnetservice.node.impl.service.InternalCloudServiceManager; import eu.cloudnetservice.node.impl.service.defaults.factory.BaseLocalCloudServiceFactory; import eu.cloudnetservice.node.impl.tick.DefaultTickLoop; +import eu.cloudnetservice.node.impl.util.NetworkUtil; import eu.cloudnetservice.node.impl.version.ServiceVersionProvider; import eu.cloudnetservice.node.service.CloudService; import eu.cloudnetservice.node.service.CloudServiceManager; @@ -95,4 +96,15 @@ public DockerizedLocalCloudServiceFactory( public @NonNull String name() { return this.dockerConfiguration.factoryName(); } + + @Override + protected boolean isPortInUseAtOsLevel(@NonNull String hostAddress, int port) { + // only do OS-level port check if we can actually reach this address + // this handles the case where CloudNet runs in a container with an external address configured + if (!NetworkUtil.isBindableAddress(hostAddress)) { + return false; + } + + return super.isPortInUseAtOsLevel(hostAddress, port); + } } diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/service/defaults/factory/BaseLocalCloudServiceFactory.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/service/defaults/factory/BaseLocalCloudServiceFactory.java index 076098d7ac..335d769f4e 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/service/defaults/factory/BaseLocalCloudServiceFactory.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/service/defaults/factory/BaseLocalCloudServiceFactory.java @@ -119,14 +119,29 @@ protected int findFreeServicePort( protected boolean isPortInUse(@NonNull CloudServiceManager manager, @NonNull String hostAddress, int port) { // check if any local service has the port + if (this.isPortUsedByLocalService(manager, hostAddress, port)) { + return true; + } + + // validate that the port is free at OS level + return this.isPortInUseAtOsLevel(hostAddress, port); + } + + protected boolean isPortUsedByLocalService( + @NonNull CloudServiceManager manager, + @NonNull String hostAddress, + int port + ) { for (var cloudService : manager.localCloudServices()) { var address = cloudService.serviceInfo().address(); if (address.host().equals(hostAddress) && address.port() == port) { return true; } } + return false; + } - // validate that the port is free + protected boolean isPortInUseAtOsLevel(@NonNull String hostAddress, int port) { return NetworkUtil.isInUse(hostAddress, port); } } diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/util/NetworkUtil.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/util/NetworkUtil.java index a7e3dc97dd..6163d5b9bc 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/util/NetworkUtil.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/util/NetworkUtil.java @@ -95,6 +95,35 @@ public static boolean checkAssignable(@NonNull HostAndPort hostAndPort) { } } + /** + * Checks if the given host address is bindable in the current network namespace. + */ + public static boolean isBindableAddress(@NonNull String hostAddress) { + // resolve the address to check against available addresses + String resolvedAddress; + try { + // try to parse as IP address first + var address = InetAddresses.forString(hostAddress); + // wildcard addresses are always bindable + if (address.isAnyLocalAddress()) { + return true; + } + resolvedAddress = extractHostAddress(address); + } catch (IllegalArgumentException exception) { + // not a raw IP address, try to resolve as hostname + try { + var address = InetAddress.getByName(hostAddress); + resolvedAddress = extractHostAddress(address); + } catch (UnknownHostException e) { + // cannot resolve hostname, not bindable + return false; + } + } + + // check if the resolved address is available on this system + return availableIPAddresses().contains(resolvedAddress); + } + public static @Nullable HostAndPort parseAssignableHostAndPort(@NonNull String address, boolean withPort) { // try to parse host and port from the given string var hostAndPort = parseHostAndPort(address, withPort);