-
Notifications
You must be signed in to change notification settings - Fork 69
fix: IPv6 host support #4367
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
base: v3.x.x
Are you sure you want to change the base?
fix: IPv6 host support #4367
Conversation
Signed-off-by: hrishikesh-nalawade <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements IPv6 host support across the APIML codebase by replacing manual URL string formatting with a centralized utility class to properly handle IPv6 addresses. IPv6 addresses require square brackets in URLs to be RFC-compliant.
- Added
UrlUtilsutility class with methods to format IPv6 addresses correctly - Updated URL construction throughout the codebase to use the new utility methods
- Added proper IPv6 address handling in route definitions and service registrations
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| common-service-core/src/main/java/org/zowe/apiml/util/UrlUtils.java | New utility class with IPv6-aware URL formatting methods |
| zaas-service/src/main/java/org/zowe/apiml/zaas/cache/CachingServiceClient.java | Replaced manual URL formatting with UrlUtils.getUrl() |
| security-service-client-spring/src/main/java/org/zowe/apiml/security/client/service/GatewaySecurityService.java | Updated login, query, and OIDC validation endpoints to use UrlUtils |
| gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java | Updated base URL construction for service info |
| gateway-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java | Added comprehensive IPv6 handling for route definitions and external URLs |
| gateway-service/src/main/java/org/zowe/apiml/gateway/service/GatewayIndexService.java | Updated WebClient base URL construction |
| gateway-service/src/main/java/org/zowe/apiml/gateway/filters/ZaasSchemeTransformRest.java | Added hostname formatting for ZAAS requests |
| gateway-service/src/main/java/org/zowe/apiml/gateway/config/RegistryConfig.java | Updated gateway service address configuration for IPv6 |
| gateway-service/src/main/java/org/zowe/apiml/gateway/caching/CachingServiceClientRest.java | Updated caching service URL construction |
| api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3Service.java | Updated OpenAPI server URL formatting |
| api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/ApiDocRetrievalServiceLocal.java | Updated SpringDoc server URL construction |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Show resolved
Hide resolved
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Outdated
Show resolved
Hide resolved
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Outdated
Show resolved
Hide resolved
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Outdated
Show resolved
Hide resolved
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Outdated
Show resolved
Hide resolved
...ay-service/src/main/java/org/zowe/apiml/gateway/service/routing/RouteDefinitionProducer.java
Outdated
Show resolved
Hide resolved
common-service-core/src/main/java/org/zowe/apiml/util/UrlUtils.java
Outdated
Show resolved
Hide resolved
| openAPI.getServers() | ||
| .forEach(server -> server.setUrl( | ||
| String.format("%s://%s/%s", scheme, getHostname(), server.getUrl()))); | ||
| UrlUtils.getUrl(scheme, UrlUtils.formatHostnameForUrl(getHostname())) + "/" + server.getUrl())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getHostname() returns host+port, which contains ":" even if there is no IPv6 address. This will probably create an incorrect URL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've considered the possibility that getHostname() might be returning the host along with the port, and I've added handling for that here. But, I suspect the issue might be related to the formatHostnameForUrl() method. I'll take a closer look to better understand what's going wrong.
| } | ||
|
|
||
| // Check if this is an IPv6 address (contains multiple colons) | ||
| if (hostname.contains(":") && !hostname.startsWith("[")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it safe to assume that any address containing ":" is IPv6? Isn't there a better validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that’s a valid point. I also feel that relying solely on ":" for validation might not be sufficient. I’ll explore better alternatives and see what could work more reliably.
Signed-off-by: hrishikesh-nalawade <[email protected]>
…host-formatting' into hrishikesh-nalawade/GH4318/IPv6-host-formatting
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
…host-formatting' into hrishikesh-nalawade/GH4318/IPv6-host-formatting
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
|
|
||
| // Check for hostname:port format | ||
| int lastColonIndex = hostname.lastIndexOf(':'); | ||
| if (lastColonIndex > -1) { | ||
| String possibleHost = hostname.substring(0, lastColonIndex); | ||
| String possiblePort = hostname.substring(lastColonIndex + 1); | ||
|
|
||
| // Check if what follows the last colon is a valid port number | ||
| if (isValidPort(possiblePort)) { | ||
| // If we have a port, check if the host part is IPv6 | ||
| if (isIPv6Address(possibleHost)) { | ||
| return "[" + possibleHost + "]:" + possiblePort; | ||
| } | ||
| // If the full string is NOT IPv6, return as-is | ||
| if (!isIPv6Address(hostname)) { | ||
| return hostname; // Regular hostname:port or IPv4:port | ||
| } | ||
| // Otherwise, fall through to check if full hostname is IPv6 | ||
| } | ||
| } | ||
|
|
||
| // No port number, check if it's a plain IPv6 address | ||
| if (isIPv6Address(hostname)) { | ||
| return "[" + hostname + "]"; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // Check for hostname:port format | |
| int lastColonIndex = hostname.lastIndexOf(':'); | |
| if (lastColonIndex > -1) { | |
| String possibleHost = hostname.substring(0, lastColonIndex); | |
| String possiblePort = hostname.substring(lastColonIndex + 1); | |
| // Check if what follows the last colon is a valid port number | |
| if (isValidPort(possiblePort)) { | |
| // If we have a port, check if the host part is IPv6 | |
| if (isIPv6Address(possibleHost)) { | |
| return "[" + possibleHost + "]:" + possiblePort; | |
| } | |
| // If the full string is NOT IPv6, return as-is | |
| if (!isIPv6Address(hostname)) { | |
| return hostname; // Regular hostname:port or IPv4:port | |
| } | |
| // Otherwise, fall through to check if full hostname is IPv6 | |
| } | |
| } | |
| // No port number, check if it's a plain IPv6 address | |
| if (isIPv6Address(hostname)) { | |
| return "[" + hostname + "]"; | |
| } | |
| // SECURITY: Check if the entire string is a valid IPv6 address first | |
| // This prevents incorrectly splitting IPv6 addresses that might have | |
| // numeric suffixes that look like ports | |
| if (isIPv6Address(hostname)) { | |
| return "[" + hostname + "]"; | |
| } | |
| // Check for hostname:port format | |
| // Only split if we're sure it's not an IPv6 address | |
| int lastColonIndex = hostname.lastIndexOf(':'); | |
| if (lastColonIndex > -1) { | |
| String possibleHost = hostname.substring(0, lastColonIndex); | |
| String possiblePort = hostname.substring(lastColonIndex + 1); | |
| // Check if what follows the last colon is a valid port number | |
| if (isValidPort(possiblePort)) { | |
| // SECURITY: Verify the host part is NOT an IPv6 address | |
| // If it is, we need to bracket just the IPv6 part, not the entire string | |
| if (isIPv6Address(possibleHost)) { | |
| // This is an unbracketed IPv6 address with port - format correctly | |
| return "[" + possibleHost + "]:" + possiblePort; | |
| } | |
| // If the host part is not IPv6, it's safe to treat as hostname:port | |
| return hostname; // Regular hostname:port or IPv4:port | |
| } | |
| } |
It is better to check for IPv6 address before looking for port number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @achmelo ,
The problem with checking the entire string as IPv6 before is that, in case where port is present with host (example: - 2001:db8::1:8080), The InetAddress verifies this as a IPv6 address and will return true. Hence resulting into incorrect formatting of complete string [2001:db8::1:8080].
Though your concern "This prevents incorrectly splitting IPv6 addresses that might have numeric suffixes that look like ports" is very much valid and I have handled these scenarios where I have made sure that IPv6 string is returned properly.
| } | ||
|
|
||
| // Remove any existing scheme if present | ||
| String cleanHostWithPort = hostWithPort.replaceFirst("^\\w+://", ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| String cleanHostWithPort = hostWithPort.replaceFirst("^\\w+://", ""); | |
| String cleanHostWithPort = hostWithPort.replaceFirst("^[a-zA-Z][a-zA-Z0-9+.-]*://", ""); |
is probably more accurate according to the RFC 3986
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank You, I have applied your suggestion.
| // Handle IPv6 address format using UrlUtils | ||
| if (host != null) { | ||
| host = UrlUtils.formatHostnameForUrl(host); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if host == null, is it ok to continue? it will probably throw some error in the builder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for pointing that out, I have corrected the above
| output = newUri.toString(); | ||
| } | ||
| } catch (URISyntaxException e) { | ||
| // If there's an error parsing the URI, keeping the original URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some debug log would be good here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added, Thank You
| ).toString(); | ||
| } | ||
| } catch (URISyntaxException e) { | ||
| // Keep original if URI parsing fails |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debug log would be good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added, Thank You
| throw new IllegalArgumentException("Host cannot be null or empty"); | ||
| } | ||
|
|
||
| // Remove any existing scheme if present |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible that there will be scheme if the parameter is called hostWithPort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added this just to safeguard us if any caller passes value which may contain scheme, also as it's a single regex check hence, computationally cheap for us and makes sure correct host/host+port is sent.
...og-services/src/main/java/org/zowe/apiml/apicatalog/swagger/ApiDocRetrievalServiceLocal.java
Show resolved
Hide resolved
gateway-service/src/main/java/org/zowe/apiml/gateway/services/ServicesInfoService.java
Show resolved
Hide resolved
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
…host-formatting' into hrishikesh-nalawade/GH4318/IPv6-host-formatting
Signed-off-by: hrishikesh-nalawade <[email protected]>
Signed-off-by: hrishikesh-nalawade <[email protected]>
|


Description
Code changes to support IPv6 address
Linked to #4318
Type of change
Please delete options that are not relevant.
Checklist: