diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index 1c1d95ed616..7afb53d20ed 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -163,14 +163,32 @@ protected DnsNameResolver( // Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an // opaque URI, thus the authority and host of the resulted URI would be null. URI nameUri = URI.create("//" + checkNotNull(name, "name")); - Preconditions.checkArgument(nameUri.getHost() != null, "Invalid DNS name: %s", name); authority = Preconditions.checkNotNull(nameUri.getAuthority(), "nameUri (%s) doesn't have an authority", nameUri); - host = nameUri.getHost(); - if (nameUri.getPort() == -1) { - port = args.getDefaultPort(); + if (nameUri.getHost() != null) { + host = nameUri.getHost(); + if (nameUri.getPort() == -1) { + port = args.getDefaultPort(); + } else { + port = nameUri.getPort(); + } } else { - port = nameUri.getPort(); + // JDK-8188305: java.net.URI.getHost() returns null when the last label of a hostname + // starts with a digit (e.g. otlp.1234-k8s-namespace). Parse host and port from authority. + Preconditions.checkArgument(authority.indexOf('@') == -1, "Invalid DNS name: %s", name); + int colonIndex = authority.lastIndexOf(':'); + if (colonIndex != -1 && (authority.indexOf(']') == -1 || colonIndex > authority.lastIndexOf(']'))) { + host = authority.substring(0, colonIndex); + try { + port = Integer.parseInt(authority.substring(colonIndex + 1)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid DNS name: " + name, e); + } + } else { + host = authority; + port = args.getDefaultPort(); + } + Preconditions.checkArgument(!host.isEmpty(), "Invalid DNS name: %s", name); } this.proxyDetector = checkNotNull(args.getProxyDetector(), "proxyDetector"); Executor offloadExecutor = args.getOffloadExecutor(); diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 0e962ba0e33..6759d388c9c 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -547,7 +547,15 @@ public static String authorityFromHostAndPort(String host, int port) { try { return new URI(null, null, host, port, null, null, null).getAuthority(); } catch (URISyntaxException ex) { - throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex); + // JDK-8188305: java.net.URI rejects valid RFC 1123 hostnames whose last label starts with + // a digit (e.g. otlp.1234-k8s-namespace). Fall back to manual combination and checkAuthority. + String formattedHost = (host.contains(":") && !host.startsWith("[")) ? "[" + host + "]" : host; + String authority = formattedHost + ":" + port; + try { + return checkAuthority(authority); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex); + } } } diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java index 75b82df544f..7cf11e5eba5 100644 --- a/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java +++ b/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java @@ -79,6 +79,16 @@ public void newNameResolver_acceptsHostAndPort() { assertThat(((DnsNameResolver) nameResolver).getPort()).isEqualTo(443); } + @Test + public void newNameResolver_acceptsHostStartingWithDigit() { + NameResolver nameResolver = newNameResolver("dns:///otlp.1234-k8s-namespace:4317", args); + assertThat(nameResolver).isNotNull(); + assertThat(nameResolver.getClass()).isSameInstanceAs(DnsNameResolver.class); + assertThat(nameResolver.getServiceAuthority()).isEqualTo("otlp.1234-k8s-namespace:4317"); + assertThat(((DnsNameResolver) nameResolver).getHost()).isEqualTo("otlp.1234-k8s-namespace"); + assertThat(((DnsNameResolver) nameResolver).getPort()).isEqualTo(4317); + } + @Test public void newNameResolver_acceptsRootless() { assume().that(enableRfc3986UrisParam).isTrue(); diff --git a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java index dbc3051628f..6d0fe33012d 100644 --- a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java +++ b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java @@ -202,6 +202,12 @@ public void checkAuthority_failsOnNull() { assertThrows(NullPointerException.class, () -> GrpcUtil.checkAuthority(null)); } + @Test + public void authorityFromHostAndPort_succeedsOnHostStartingWithDigit() { + String actual = GrpcUtil.authorityFromHostAndPort("otlp.1234-k8s-namespace", 4317); + assertEquals("otlp.1234-k8s-namespace:4317", actual); + } + @Test public void checkAuthority_succeedsOnHostAndPort() { String actual = GrpcUtil.checkAuthority("valid:1234");