diff --git a/core/src/main/java/org/zstack/core/Platform.java b/core/src/main/java/org/zstack/core/Platform.java index 67bdbb40a34..34df20e3e12 100755 --- a/core/src/main/java/org/zstack/core/Platform.java +++ b/core/src/main/java/org/zstack/core/Platform.java @@ -1387,6 +1387,47 @@ public static String getRouteSourceIp(String remoteIp) { return null; } + public static String getManagementServerIpForRemote(String remoteIp) { + return selectManagementServerIpForRemote(remoteIp, null); + } + + public static String selectManagementServerIpForRemote(String remoteIp, String routeSourceIp) { + if (StringUtils.isBlank(remoteIp)) { + return getManagementServerIp(); + } + + remoteIp = normalizeManagementIp(remoteIp); + routeSourceIp = normalizeManagementIp(routeSourceIp); + if (IPv6NetworkUtils.isIpv6Address(remoteIp)) { + if (IPv6NetworkUtils.isIpv6Address(routeSourceIp) && isManagementServerIp(routeSourceIp)) { + return routeSourceIp; + } + String ip6 = getManagementServerIp6(); + return ip6 == null ? getManagementServerIp() : ip6; + } + + if (NetworkUtils.isIpv4Address(remoteIp)) { + if (NetworkUtils.isIpv4Address(routeSourceIp) && isManagementServerIp(routeSourceIp)) { + return routeSourceIp; + } + String ip4 = getManagementServerIp4(); + return ip4 == null ? getManagementServerIp() : ip4; + } + + return getManagementServerIp(); + } + + private static boolean isManagementServerIp(String ip) { + if (StringUtils.isBlank(ip)) { + return false; + } + + String normalizedIp = normalizeManagementIp(ip); + return getManagementServerIps().stream() + .map(Platform::normalizeManagementIp) + .anyMatch(normalizedIp::equals); + } + public static String selectManagementServerIp(Collection addresses) { String ipv4 = null; String ipv6 = null; diff --git a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java index 915a8ec3531..26dbb7ec675 100755 --- a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java +++ b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java @@ -50,12 +50,14 @@ import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import org.zstack.utils.network.IPv6NetworkUtils; +import org.zstack.utils.network.NetworkUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -227,6 +229,31 @@ public static String buildCallbackUrl(String hostName, int port, String path) { return ub.build().toUriString(); } + public static String selectCallbackUrl(String requestUrl, Map headers, String defaultCallbackUrl, int port, String path) { + if (headers != null && headers.keySet().stream().anyMatch(RESTConstant.CALLBACK_URL::equalsIgnoreCase)) { + return defaultCallbackUrl; + } + + String host = extractRequestHost(requestUrl); + if (host == null) { + return defaultCallbackUrl; + } + + if (!NetworkUtils.isIpv4Address(host) && !IPv6NetworkUtils.isIpv6Address(host)) { + return defaultCallbackUrl; + } + + return buildCallbackUrl(Platform.getManagementServerIpForRemote(host), port, path); + } + + private static String extractRequestHost(String requestUrl) { + try { + return IPv6NetworkUtils.stripHostUrlBrackets(new URI(requestUrl).getHost()); + } catch (URISyntaxException | IllegalArgumentException e) { + return null; + } + } + public static String buildSendCommandUrl(String hostName, int port, String path) { UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(buildBaseUrl(hostName, port, path)); ub.path(RESTConstant.COMMAND_CHANNEL_PATH); @@ -415,7 +442,7 @@ public void asyncJson(final String url, final String body, Map h HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setContentLength(body.length()); requestHeaders.set(RESTConstant.TASK_UUID, taskUuid); - requestHeaders.set(RESTConstant.CALLBACK_URL, callbackUrl); + requestHeaders.set(RESTConstant.CALLBACK_URL, selectCallbackUrl(url, headers, callbackUrl, port, path)); MediaType JSON = MediaType.parseMediaType("application/json; charset=utf-8"); requestHeaders.setContentType(JSON); if (headers != null) { diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/CephBackupStorageMonBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/CephBackupStorageMonBase.java index 150c27ab41f..2dab5a39bf5 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/CephBackupStorageMonBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/CephBackupStorageMonBase.java @@ -201,7 +201,7 @@ public void run(final FlowTrigger trigger, Map data) { callbackChecker.setUsername(getSelf().getSshUsername()); callbackChecker.setPassword(getSelf().getSshPassword()); callbackChecker.setPort(getSelf().getSshPort()); - callbackChecker.setCallbackIp(Platform.getManagementServerIp()); + callbackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getHostname())); callbackChecker.setCallBackPort(CloudBusGlobalProperty.HTTP_PORT); AnsibleRunner runner = new AnsibleRunner(); diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java index e3ba7578f12..e9d5449b586 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java @@ -198,7 +198,7 @@ public void run(final FlowTrigger trigger, Map data) { callbackChecker.setUsername(getSelf().getSshUsername()); callbackChecker.setPassword(getSelf().getSshPassword()); callbackChecker.setPort(getSelf().getSshPort()); - callbackChecker.setCallbackIp(Platform.getManagementServerIp()); + callbackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getHostname())); callbackChecker.setCallBackPort(CloudBusGlobalProperty.HTTP_PORT); AnsibleRunner runner = new AnsibleRunner(); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 4c6114c9ce0..89e0f8a4e45 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -6083,7 +6083,7 @@ public void run(final FlowTrigger trigger, Map data) { callbackChecker.setUsername(getSelf().getUsername()); callbackChecker.setPassword(getSelf().getPassword()); callbackChecker.setPort(getSelf().getPort()); - callbackChecker.setCallbackIp(Platform.getManagementServerIp()); + callbackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getManagementIp())); callbackChecker.setCallBackPort(CloudBusGlobalProperty.HTTP_PORT); KvmHostConfigChecker kvmHostConfigChecker = new KvmHostConfigChecker(); @@ -6107,7 +6107,7 @@ public void run(final FlowTrigger trigger, Map data) { hostTcpConnectionCallbackChecker.setUsername(getSelf().getUsername()); hostTcpConnectionCallbackChecker.setPassword(getSelf().getPassword()); hostTcpConnectionCallbackChecker.setPort(getSelf().getPort()); - hostTcpConnectionCallbackChecker.setCallbackIp(Platform.getManagementServerIp()); + hostTcpConnectionCallbackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getManagementIp())); hostTcpConnectionCallbackChecker.setCallBackPort(KVMGlobalProperty.TCP_SERVER_PORT); runner.installChecker(hostTcpConnectionCallbackChecker); } diff --git a/plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorage.java b/plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorage.java index cdc2622aa3f..debce4a6b13 100755 --- a/plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorage.java +++ b/plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorage.java @@ -392,7 +392,7 @@ private void connect(final Completion complete) { callbackChecker.setUsername(getSelf().getUsername()); callbackChecker.setPassword(getSelf().getPassword()); callbackChecker.setPort(getSelf().getSshPort()); - callbackChecker.setCallbackIp(Platform.getManagementServerIp()); + callbackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getHostname())); callbackChecker.setCallBackPort(CloudBusGlobalProperty.HTTP_PORT); AnsibleRunner runner = new AnsibleRunner(); diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsPrimaryStorageMdsBase.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsPrimaryStorageMdsBase.java index bcd230bfdf7..416cc620572 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsPrimaryStorageMdsBase.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsPrimaryStorageMdsBase.java @@ -112,7 +112,7 @@ public void run(FlowTrigger trigger, Map data) { callBackChecker.setUsername(getSelf().getUsername()); callBackChecker.setPassword(getSelf().getPassword()); callBackChecker.setPort(getSelf().getPort()); - callBackChecker.setCallbackIp(Platform.getManagementServerIp()); + callBackChecker.setCallbackIp(Platform.getManagementServerIpForRemote(getSelf().getAddr())); callBackChecker.setCallBackPort(CloudBusGlobalProperty.HTTP_PORT); AnsibleRunner runner = new AnsibleRunner(); diff --git a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy index 3d8f64a1abe..c8a407abd20 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy @@ -249,6 +249,26 @@ class ManagementNetworkIpv6Case extends SubCase { "http://[2001:db8::1]:8080/zstack${RESTConstant.COMMAND_CHANNEL_PATH}" } + void testRestFacadeSelectsCallbackUrlByTargetIpVersion() { + withManagementServerIpProperties([ + "management.server.ip" : IPV4, + "management.server.ip6": IPV6, + ]) { + String defaultCallbackUrl = "http://${IPV4}:${REST_PORT}/zstack${RESTConstant.CALLBACK_PATH}" + + assert RESTFacadeImpl.selectCallbackUrl( + "http://[${IPV6_2}]:7070/host/ping", [:], defaultCallbackUrl, REST_PORT, "zstack") == + "http://[${IPV6}]:${REST_PORT}/zstack${RESTConstant.CALLBACK_PATH}" + assert RESTFacadeImpl.selectCallbackUrl( + "http://host.example.com:7070/host/ping", [:], defaultCallbackUrl, REST_PORT, "zstack") == + defaultCallbackUrl + assert RESTFacadeImpl.selectCallbackUrl( + "http://[${IPV6_2}]:7070/host/ping", + [(RESTConstant.CALLBACK_URL): "http://override.example.com/callback"], + defaultCallbackUrl, REST_PORT, "zstack") == defaultCallbackUrl + } + } + void testSshTargetUsesRawIpv6Host() { assert SshShell.formatSshTarget("root", IPV4) == "root@192.168.1.10" assert SshShell.formatSshTarget("root", IPV6) == "root@2001:db8::1" @@ -357,6 +377,25 @@ class ManagementNetworkIpv6Case extends SubCase { } } + void testSelectManagementServerIpForRemote() { + withManagementServerIpProperties([ + "management.server.ip" : IPV4, + "management.server.ip6": IPV6, + ]) { + assert Platform.selectManagementServerIpForRemote(IPV6_2, null) == IPV6 + assert Platform.selectManagementServerIpForRemote(IPV6_2, "2001:db8::88") == IPV6 + assert Platform.selectManagementServerIpForRemote("host.example.com", null) == IPV4 + } + + withManagementServerIpProperties([ + "management.server.ip" : IPV6, + "management.server.ip4": IPV4, + ]) { + assert Platform.selectManagementServerIpForRemote("192.168.1.20", null) == IPV4 + assert Platform.selectManagementServerIpForRemote("192.168.1.20", "192.168.1.88") == IPV4 + } + } + void testManagementServerSecondaryPropertyRejectsWrongAddressFamily() { withManagementServerIpProperties([ "management.server.ip" : IPV4,