Skip to content

Commit b7f9074

Browse files
authored
Revert "fix(xds): Allow and normalize trailing dot (FQDN) in matchHostName (#12644) (1.80.x backport) (#12674)
We want to synchronize the behavior across all gRPC languages, and also with envoy. This reverts commit 0ef1b39.
1 parent 09a6e2e commit b7f9074

File tree

4 files changed

+105
-176
lines changed

4 files changed

+105
-176
lines changed

xds/src/main/java/io/grpc/xds/RoutingUtils.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,15 @@ static VirtualHost findVirtualHostForHostName(List<VirtualHost> virtualHosts, St
9292
* </ol>
9393
*/
9494
private static boolean matchHostName(String hostName, String pattern) {
95-
checkArgument(hostName.length() != 0 && !hostName.startsWith("."),
95+
checkArgument(hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."),
9696
"Invalid host name");
97-
checkArgument(pattern.length() != 0 && !pattern.startsWith("."),
97+
checkArgument(pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
9898
"Invalid pattern/domain name");
9999

100100
hostName = hostName.toLowerCase(Locale.US);
101101
pattern = pattern.toLowerCase(Locale.US);
102102
// hostName and pattern are now in lower case -- domain names are case-insensitive.
103103

104-
// Strip trailing dot to normalize FQDN (e.g. "example.com.") to a relative form,
105-
// as per RFC 1034 Section 3.1 the two are semantically equivalent.
106-
if (hostName.endsWith(".")) {
107-
hostName = hostName.substring(0, hostName.length() - 1);
108-
}
109-
if (pattern.endsWith(".")) {
110-
pattern = pattern.substring(0, pattern.length() - 1);
111-
}
112-
113104
if (!pattern.contains("*")) {
114105
// Not a wildcard pattern -- hostName and pattern must match exactly.
115106
return hostName.equals(pattern);

xds/src/main/java/io/grpc/xds/XdsNameResolver.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import java.util.HashMap;
7474
import java.util.HashSet;
7575
import java.util.List;
76+
import java.util.Locale;
7677
import java.util.Map;
7778
import java.util.Objects;
7879
import java.util.Set;
@@ -340,6 +341,66 @@ private void updateResolutionResult(XdsConfig xdsConfig) {
340341
}
341342
}
342343

344+
/**
345+
* Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with
346+
* case-insensitive.
347+
*
348+
* <p>Wildcard pattern rules:
349+
* <ol>
350+
* <li>A single asterisk (*) matches any domain.</li>
351+
* <li>Asterisk (*) is only permitted in the left-most or the right-most part of the pattern,
352+
* but not both.</li>
353+
* </ol>
354+
*/
355+
@VisibleForTesting
356+
static boolean matchHostName(String hostName, String pattern) {
357+
checkArgument(hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."),
358+
"Invalid host name");
359+
checkArgument(pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
360+
"Invalid pattern/domain name");
361+
362+
hostName = hostName.toLowerCase(Locale.US);
363+
pattern = pattern.toLowerCase(Locale.US);
364+
// hostName and pattern are now in lower case -- domain names are case-insensitive.
365+
366+
if (!pattern.contains("*")) {
367+
// Not a wildcard pattern -- hostName and pattern must match exactly.
368+
return hostName.equals(pattern);
369+
}
370+
// Wildcard pattern
371+
372+
if (pattern.length() == 1) {
373+
return true;
374+
}
375+
376+
int index = pattern.indexOf('*');
377+
378+
// At most one asterisk (*) is allowed.
379+
if (pattern.indexOf('*', index + 1) != -1) {
380+
return false;
381+
}
382+
383+
// Asterisk can only match prefix or suffix.
384+
if (index != 0 && index != pattern.length() - 1) {
385+
return false;
386+
}
387+
388+
// HostName must be at least as long as the pattern because asterisk has to
389+
// match one or more characters.
390+
if (hostName.length() < pattern.length()) {
391+
return false;
392+
}
393+
394+
if (index == 0 && hostName.endsWith(pattern.substring(1))) {
395+
// Prefix matching fails.
396+
return true;
397+
}
398+
399+
// Pattern matches hostname if suffix matching succeeds.
400+
return index == pattern.length() - 1
401+
&& hostName.startsWith(pattern.substring(0, pattern.length() - 1));
402+
}
403+
343404
private final class ConfigSelector extends InternalConfigSelector {
344405
@Override
345406
public Result selectConfig(PickSubchannelArgs args) {

xds/src/test/java/io/grpc/xds/RoutingUtilsTest.java

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package io.grpc.xds;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20-
import static org.junit.Assert.assertThrows;
2120
import static org.mockito.Mockito.mock;
2221

2322
import com.google.common.collect.ImmutableMap;
@@ -89,170 +88,6 @@ public void findVirtualHostForHostName_asteriskMatchAnyDomain() {
8988
.isEqualTo(vHost1);
9089
}
9190

92-
@Test
93-
public void findVirtualHostForHostName_trailingDot() {
94-
// FQDN (trailing dot) is semantically equivalent to the relative form
95-
// per RFC 1034 Section 3.1.
96-
List<Route> routes = Collections.emptyList();
97-
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
98-
Collections.singletonList("a.googleapis.com"), routes,
99-
ImmutableMap.of());
100-
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
101-
Collections.singletonList("*.googleapis.com"), routes,
102-
ImmutableMap.of());
103-
VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
104-
Collections.singletonList("*"), routes,
105-
ImmutableMap.of());
106-
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
107-
108-
// Trailing dot in hostName, exact match.
109-
assertThat(RoutingUtils.findVirtualHostForHostName(
110-
virtualHosts, "a.googleapis.com.")).isEqualTo(vHost1);
111-
// Trailing dot in hostName, wildcard match.
112-
assertThat(RoutingUtils.findVirtualHostForHostName(
113-
virtualHosts, "b.googleapis.com.")).isEqualTo(vHost2);
114-
115-
// Trailing dot in domain pattern, exact match.
116-
VirtualHost vHost4 = VirtualHost.create("virtualhost04.googleapis.com",
117-
Collections.singletonList("a.googleapis.com."), routes,
118-
ImmutableMap.of());
119-
List<VirtualHost> virtualHosts2 =
120-
Arrays.asList(vHost4, vHost2, vHost3);
121-
assertThat(RoutingUtils.findVirtualHostForHostName(
122-
virtualHosts2, "a.googleapis.com")).isEqualTo(vHost4);
123-
124-
// Trailing dot in both hostName and domain pattern.
125-
assertThat(RoutingUtils.findVirtualHostForHostName(
126-
virtualHosts2, "a.googleapis.com.")).isEqualTo(vHost4);
127-
128-
// Trailing dot in domain pattern, wildcard match.
129-
VirtualHost vHost5 = VirtualHost.create("virtualhost05.googleapis.com",
130-
Collections.singletonList("*.googleapis.com."), routes,
131-
ImmutableMap.of());
132-
List<VirtualHost> virtualHosts3 =
133-
Arrays.asList(vHost5, vHost3);
134-
assertThat(RoutingUtils.findVirtualHostForHostName(
135-
virtualHosts3, "b.googleapis.com")).isEqualTo(vHost5);
136-
assertThat(RoutingUtils.findVirtualHostForHostName(
137-
virtualHosts3, "b.googleapis.com.")).isEqualTo(vHost5);
138-
}
139-
140-
@Test
141-
public void findVirtualHostForHostName_exactMatch() {
142-
List<Route> routes = Collections.emptyList();
143-
VirtualHost vHostFoo = VirtualHost.create("vhost-foo",
144-
Collections.singletonList("foo.googleapis.com"), routes,
145-
ImmutableMap.of());
146-
VirtualHost vHostBar = VirtualHost.create("vhost-bar",
147-
Collections.singletonList("bar.googleapis.com"), routes,
148-
ImmutableMap.of());
149-
List<VirtualHost> virtualHosts =
150-
Arrays.asList(vHostFoo, vHostBar);
151-
152-
assertThat(RoutingUtils.findVirtualHostForHostName(
153-
virtualHosts, "foo.googleapis.com")).isEqualTo(vHostFoo);
154-
assertThat(RoutingUtils.findVirtualHostForHostName(
155-
virtualHosts, "bar.googleapis.com")).isEqualTo(vHostBar);
156-
// No match returns null.
157-
assertThat(RoutingUtils.findVirtualHostForHostName(
158-
virtualHosts, "baz.googleapis.com")).isNull();
159-
assertThat(RoutingUtils.findVirtualHostForHostName(
160-
virtualHosts, "foo.googleapis")).isNull();
161-
}
162-
163-
@Test
164-
public void findVirtualHostForHostName_invalidHostName() {
165-
List<Route> routes = Collections.emptyList();
166-
VirtualHost vHost = VirtualHost.create("vhost",
167-
Collections.singletonList("a.googleapis.com"), routes,
168-
ImmutableMap.of());
169-
List<VirtualHost> virtualHosts = Collections.singletonList(vHost);
170-
171-
// Empty hostName.
172-
assertThrows(IllegalArgumentException.class,
173-
() -> RoutingUtils.findVirtualHostForHostName(
174-
virtualHosts, ""));
175-
// HostName starting with dot.
176-
assertThrows(IllegalArgumentException.class,
177-
() -> RoutingUtils.findVirtualHostForHostName(
178-
virtualHosts, ".a.googleapis.com"));
179-
}
180-
181-
@Test
182-
public void findVirtualHostForHostName_invalidPattern() {
183-
List<Route> routes = Collections.emptyList();
184-
// Empty domain pattern.
185-
VirtualHost vHostEmpty = VirtualHost.create("vhost-empty",
186-
Collections.singletonList(""), routes,
187-
ImmutableMap.of());
188-
assertThrows(IllegalArgumentException.class,
189-
() -> RoutingUtils.findVirtualHostForHostName(
190-
Collections.singletonList(vHostEmpty),
191-
"a.googleapis.com"));
192-
// Domain pattern starting with dot.
193-
VirtualHost vHostDot = VirtualHost.create("vhost-dot",
194-
Collections.singletonList(".a.googleapis.com"), routes,
195-
ImmutableMap.of());
196-
assertThrows(IllegalArgumentException.class,
197-
() -> RoutingUtils.findVirtualHostForHostName(
198-
Collections.singletonList(vHostDot),
199-
"a.googleapis.com"));
200-
}
201-
202-
@Test
203-
public void findVirtualHostForHostName_prefixWildcard() {
204-
List<Route> routes = Collections.emptyList();
205-
VirtualHost vHostWild = VirtualHost.create("vhost-wild",
206-
Collections.singletonList("*.foo.googleapis.com"),
207-
routes, ImmutableMap.of());
208-
VirtualHost vHostOther = VirtualHost.create("vhost-other",
209-
Collections.singletonList("other.googleapis.com"),
210-
routes, ImmutableMap.of());
211-
List<VirtualHost> virtualHosts =
212-
Arrays.asList(vHostWild, vHostOther);
213-
214-
// Prefix wildcard matches.
215-
assertThat(RoutingUtils.findVirtualHostForHostName(
216-
virtualHosts, "bar.foo.googleapis.com"))
217-
.isEqualTo(vHostWild);
218-
// Base domain without subdomain does not match *.foo.googleapis.com.
219-
assertThat(RoutingUtils.findVirtualHostForHostName(
220-
virtualHosts, "foo.googleapis.com")).isNull();
221-
222-
// Longer prefix wildcard is preferred over shorter one.
223-
VirtualHost vHostLong = VirtualHost.create("vhost-long",
224-
Collections.singletonList("*.bar.foo.googleapis.com"),
225-
routes, ImmutableMap.of());
226-
List<VirtualHost> virtualHosts2 =
227-
Arrays.asList(vHostLong, vHostWild);
228-
assertThat(RoutingUtils.findVirtualHostForHostName(
229-
virtualHosts2, "baz.bar.foo.googleapis.com"))
230-
.isEqualTo(vHostLong);
231-
}
232-
233-
@Test
234-
public void findVirtualHostForHostName_postfixWildcard() {
235-
List<Route> routes = Collections.emptyList();
236-
VirtualHost vHostWild = VirtualHost.create("vhost-wild",
237-
Collections.singletonList("foo.*"), routes,
238-
ImmutableMap.of());
239-
VirtualHost vHostOther = VirtualHost.create("vhost-other",
240-
Collections.singletonList("bar.googleapis.com"),
241-
routes, ImmutableMap.of());
242-
List<VirtualHost> virtualHosts =
243-
Arrays.asList(vHostWild, vHostOther);
244-
245-
// Postfix wildcard matches.
246-
assertThat(RoutingUtils.findVirtualHostForHostName(
247-
virtualHosts, "foo.googleapis.com"))
248-
.isEqualTo(vHostWild);
249-
assertThat(RoutingUtils.findVirtualHostForHostName(
250-
virtualHosts, "foo.com")).isEqualTo(vHostWild);
251-
// Different prefix does not match foo.*.
252-
assertThat(RoutingUtils.findVirtualHostForHostName(
253-
virtualHosts, "bar.foo.googleapis.com")).isNull();
254-
}
255-
25691
@Test
25792
public void routeMatching_pathOnly() {
25893
Metadata headers = new Metadata();

xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,48 @@ public void generateServiceConfig_forPerMethodConfig() throws IOException {
20202020
.isEqualTo(expectedServiceConfig);
20212021
}
20222022

2023+
@Test
2024+
public void matchHostName_exactlyMatch() {
2025+
String pattern = "foo.googleapis.com";
2026+
assertThat(XdsNameResolver.matchHostName("bar.googleapis.com", pattern)).isFalse();
2027+
assertThat(XdsNameResolver.matchHostName("fo.googleapis.com", pattern)).isFalse();
2028+
assertThat(XdsNameResolver.matchHostName("oo.googleapis.com", pattern)).isFalse();
2029+
assertThat(XdsNameResolver.matchHostName("googleapis.com", pattern)).isFalse();
2030+
assertThat(XdsNameResolver.matchHostName("foo.googleapis", pattern)).isFalse();
2031+
assertThat(XdsNameResolver.matchHostName("foo.googleapis.com", pattern)).isTrue();
2032+
}
2033+
2034+
@Test
2035+
public void matchHostName_prefixWildcard() {
2036+
String pattern = "*.foo.googleapis.com";
2037+
assertThat(XdsNameResolver.matchHostName("foo.googleapis.com", pattern)).isFalse();
2038+
assertThat(XdsNameResolver.matchHostName("bar-baz.foo.googleapis", pattern)).isFalse();
2039+
assertThat(XdsNameResolver.matchHostName("bar.foo.googleapis.com", pattern)).isTrue();
2040+
pattern = "*-bar.foo.googleapis.com";
2041+
assertThat(XdsNameResolver.matchHostName("bar.foo.googleapis.com", pattern)).isFalse();
2042+
assertThat(XdsNameResolver.matchHostName("baz-bar.foo.googleapis", pattern)).isFalse();
2043+
assertThat(XdsNameResolver.matchHostName("-bar.foo.googleapis.com", pattern)).isFalse();
2044+
assertThat(XdsNameResolver.matchHostName("baz-bar.foo.googleapis.com", pattern))
2045+
.isTrue();
2046+
}
2047+
2048+
@Test
2049+
public void matchHostName_postfixWildCard() {
2050+
String pattern = "foo.*";
2051+
assertThat(XdsNameResolver.matchHostName("bar.googleapis.com", pattern)).isFalse();
2052+
assertThat(XdsNameResolver.matchHostName("bar.foo.googleapis.com", pattern)).isFalse();
2053+
assertThat(XdsNameResolver.matchHostName("foo.googleapis.com", pattern)).isTrue();
2054+
assertThat(XdsNameResolver.matchHostName("foo.com", pattern)).isTrue();
2055+
pattern = "foo-*";
2056+
assertThat(XdsNameResolver.matchHostName("bar-.googleapis.com", pattern)).isFalse();
2057+
assertThat(XdsNameResolver.matchHostName("foo.googleapis.com", pattern)).isFalse();
2058+
assertThat(XdsNameResolver.matchHostName("foo.googleapis.com", pattern)).isFalse();
2059+
assertThat(XdsNameResolver.matchHostName("foo-", pattern)).isFalse();
2060+
assertThat(XdsNameResolver.matchHostName("foo-bar.com", pattern)).isTrue();
2061+
assertThat(XdsNameResolver.matchHostName("foo-.com", pattern)).isTrue();
2062+
assertThat(XdsNameResolver.matchHostName("foo-bar", pattern)).isTrue();
2063+
}
2064+
20232065
@Test
20242066
public void resolved_faultAbortInLdsUpdate() {
20252067
resolver.start(mockListener);

0 commit comments

Comments
 (0)