Skip to content

Conversation

@hrishikesh-nalawade
Copy link
Member

Description

Code changes to support IPv6 address

Linked to #4318

Type of change

Please delete options that are not relevant.

  • fix: Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the style guidelines of this project
  • PR title conforms to commit message guideline ## Commit Message Structure Guideline
  • I have commented my code, particularly in hard-to-understand areas. In JS I did provide JSDoc
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The java tests in the area I was working on leverage @nested annotations
  • Any dependent changes have been merged and published in downstream modules

Signed-off-by: hrishikesh-nalawade <[email protected]>
Copy link

Copilot AI left a 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 UrlUtils utility 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.

@hrishikesh-nalawade hrishikesh-nalawade changed the title IPv6 host support fix: IPv6 host support Oct 29, 2025
@EvaJavornicka EvaJavornicka moved this from New to In Progress in API Mediation Layer Backlog Management Oct 29, 2025
openAPI.getServers()
.forEach(server -> server.setUrl(
String.format("%s://%s/%s", scheme, getHostname(), server.getUrl())));
UrlUtils.getUrl(scheme, UrlUtils.formatHostnameForUrl(getHostname())) + "/" + server.getUrl()));
Copy link
Member

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

Copy link
Member Author

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("[")) {
Copy link
Member

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?

Copy link
Member Author

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.

hrishikesh-nalawade and others added 4 commits November 5, 2025 17:08
Signed-off-by: hrishikesh-nalawade <[email protected]>
…host-formatting' into hrishikesh-nalawade/GH4318/IPv6-host-formatting
Signed-off-by: hrishikesh-nalawade <[email protected]>
hrishikesh-nalawade and others added 5 commits November 7, 2025 16:14
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]>
Comment on lines 167 to 192

// 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 + "]";
}

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// 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

Copy link
Member Author

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+://", "");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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

Copy link
Member Author

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);
}
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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?

Copy link
Member Author

@hrishikesh-nalawade hrishikesh-nalawade Nov 17, 2025

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.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
35.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Sensitive Sensitive change that requires peer review size/L

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants