• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19716

06 Mar 2025 10:34AM UTC coverage: 88.494% (+0.02%) from 88.479%
#19716

push

github

web-flow
xds: avoid unnecessary dns lookup (#11932)

34495 of 38980 relevant lines covered (88.49%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.94
/../xds/src/main/java/io/grpc/xds/XdsListenerResource.java
1
/*
2
 * Copyright 2022 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext;
21
import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts;
22
import static io.grpc.xds.client.XdsClient.ResourceUpdate;
23

24
import com.github.udpa.udpa.type.v1.TypedStruct;
25
import com.google.auto.value.AutoValue;
26
import com.google.common.annotations.VisibleForTesting;
27
import com.google.common.collect.ImmutableList;
28
import com.google.common.net.InetAddresses;
29
import com.google.protobuf.Any;
30
import com.google.protobuf.InvalidProtocolBufferException;
31
import com.google.protobuf.Message;
32
import com.google.protobuf.util.Durations;
33
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
34
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
35
import io.envoyproxy.envoy.config.core.v3.TrafficDirection;
36
import io.envoyproxy.envoy.config.listener.v3.Listener;
37
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
38
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
39
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext;
40
import io.grpc.xds.EnvoyServerProtoData.CidrRange;
41
import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType;
42
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
43
import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch;
44
import io.grpc.xds.Filter.FilterConfig;
45
import io.grpc.xds.XdsListenerResource.LdsUpdate;
46
import io.grpc.xds.client.XdsResourceType;
47
import java.util.ArrayList;
48
import java.util.Collection;
49
import java.util.HashSet;
50
import java.util.List;
51
import java.util.Set;
52
import javax.annotation.Nullable;
53

54
class XdsListenerResource extends XdsResourceType<LdsUpdate> {
1✔
55
  static final String ADS_TYPE_URL_LDS =
56
      "type.googleapis.com/envoy.config.listener.v3.Listener";
57
  static final String TYPE_URL_HTTP_CONNECTION_MANAGER =
58
      "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3"
59
          + ".HttpConnectionManager";
60
  private static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls";
61
  private static final XdsListenerResource instance = new XdsListenerResource();
1✔
62
  private static final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
1✔
63

64
  static XdsListenerResource getInstance() {
65
    return instance;
1✔
66
  }
67

68
  @Override
69
  @Nullable
70
  protected String extractResourceName(Message unpackedResource) {
71
    if (!(unpackedResource instanceof Listener)) {
1✔
72
      return null;
×
73
    }
74
    return ((Listener) unpackedResource).getName();
1✔
75
  }
76

77
  @Override
78
  public String typeName() {
79
    return "LDS";
1✔
80
  }
81

82
  @Override
83
  protected Class<Listener> unpackedClassName() {
84
    return Listener.class;
1✔
85
  }
86

87
  @Override
88
  public String typeUrl() {
89
    return ADS_TYPE_URL_LDS;
1✔
90
  }
91

92
  @Override
93
  public boolean shouldRetrieveResourceKeysForArgs() {
94
    return false;
1✔
95
  }
96

97
  @Override
98
  protected boolean isFullStateOfTheWorld() {
99
    return true;
1✔
100
  }
101

102
  @Override
103
  protected LdsUpdate doParse(Args args, Message unpackedMessage)
104
      throws ResourceInvalidException {
105
    if (!(unpackedMessage instanceof Listener)) {
1✔
106
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
107
    }
108
    Listener listener = (Listener) unpackedMessage;
1✔
109

110
    if (listener.hasApiListener()) {
1✔
111
      return processClientSideListener(listener, args);
1✔
112
    } else {
113
      return processServerSideListener(listener, args);
1✔
114
    }
115
  }
116

117
  private LdsUpdate processClientSideListener(Listener listener, XdsResourceType.Args args)
118
      throws ResourceInvalidException {
119
    // Unpack HttpConnectionManager from the Listener.
120
    HttpConnectionManager hcm;
121
    try {
122
      hcm = unpackCompatibleType(
1✔
123
          listener.getApiListener().getApiListener(), HttpConnectionManager.class,
1✔
124
          TYPE_URL_HTTP_CONNECTION_MANAGER, null);
125
    } catch (InvalidProtocolBufferException e) {
1✔
126
      throw new ResourceInvalidException(
1✔
127
          "Could not parse HttpConnectionManager config from ApiListener", e);
128
    }
1✔
129
    return LdsUpdate.forApiListener(
1✔
130
        parseHttpConnectionManager(hcm, filterRegistry, true /* isForClient */, args));
1✔
131
  }
132

133
  private LdsUpdate processServerSideListener(Listener proto, XdsResourceType.Args args)
134
      throws ResourceInvalidException {
135
    Set<String> certProviderInstances = null;
1✔
136
    if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) {
1✔
137
      certProviderInstances = args.getBootstrapInfo().certProviders().keySet();
1✔
138
    }
139
    return LdsUpdate.forTcpListener(parseServerSideListener(proto,
1✔
140
        (TlsContextManager) args.getSecurityConfig(),
1✔
141
        filterRegistry, certProviderInstances, args));
142
  }
143

144
  @VisibleForTesting
145
  static EnvoyServerProtoData.Listener parseServerSideListener(
146
      Listener proto, TlsContextManager tlsContextManager,
147
      FilterRegistry filterRegistry, Set<String> certProviderInstances, XdsResourceType.Args args)
148
      throws ResourceInvalidException {
149
    TrafficDirection trafficDirection = proto.getTrafficDirection();
1✔
150
    if (!trafficDirection.equals(TrafficDirection.INBOUND)
1✔
151
        && !trafficDirection.equals(TrafficDirection.UNSPECIFIED)) {
1✔
152
      throw new ResourceInvalidException(
1✔
153
          "Listener " + proto.getName() + " with invalid traffic direction: " + trafficDirection);
1✔
154
    }
155
    if (!proto.getListenerFiltersList().isEmpty()) {
1✔
156
      throw new ResourceInvalidException(
1✔
157
          "Listener " + proto.getName() + " cannot have listener_filters");
1✔
158
    }
159
    if (proto.hasUseOriginalDst()) {
1✔
160
      throw new ResourceInvalidException(
1✔
161
          "Listener " + proto.getName() + " cannot have use_original_dst set to true");
1✔
162
    }
163

164
    String address = null;
1✔
165
    if (proto.getAddress().hasSocketAddress()) {
1✔
166
      SocketAddress socketAddress = proto.getAddress().getSocketAddress();
1✔
167
      address = socketAddress.getAddress();
1✔
168
      switch (socketAddress.getPortSpecifierCase()) {
1✔
169
        case NAMED_PORT:
170
          address = address + ":" + socketAddress.getNamedPort();
×
171
          break;
×
172
        case PORT_VALUE:
173
          address = address + ":" + socketAddress.getPortValue();
1✔
174
          break;
1✔
175
        default:
176
          // noop
177
      }
178
    }
179

180
    ImmutableList.Builder<FilterChain> filterChains = ImmutableList.builder();
1✔
181
    Set<FilterChainMatch> filterChainMatchSet = new HashSet<>();
1✔
182
    int i = 0;
1✔
183
    for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) {
1✔
184
      // May be empty. If it's not empty, required to be unique.
185
      String filterChainName = fc.getName();
1✔
186
      if (filterChainName.isEmpty()) {
1✔
187
        // Generate a name, so we can identify it in the logs.
188
        filterChainName = "chain_" + i;
1✔
189
      }
190
      filterChains.add(
1✔
191
          parseFilterChain(fc, filterChainName, tlsContextManager, filterRegistry,
1✔
192
              filterChainMatchSet, certProviderInstances, args));
193
      i++;
1✔
194
    }
1✔
195

196
    FilterChain defaultFilterChain = null;
1✔
197
    if (proto.hasDefaultFilterChain()) {
1✔
198
      String defaultFilterChainName = proto.getDefaultFilterChain().getName();
1✔
199
      if (defaultFilterChainName.isEmpty()) {
1✔
200
        defaultFilterChainName = "chain_default";
1✔
201
      }
202
      defaultFilterChain = parseFilterChain(
1✔
203
          proto.getDefaultFilterChain(), defaultFilterChainName, tlsContextManager, filterRegistry,
1✔
204
          null, certProviderInstances, args);
205
    }
206

207
    return EnvoyServerProtoData.Listener.create(
1✔
208
        proto.getName(), address, filterChains.build(), defaultFilterChain);
1✔
209
  }
210

211
  @VisibleForTesting
212
  static FilterChain parseFilterChain(
213
      io.envoyproxy.envoy.config.listener.v3.FilterChain proto,
214
      String filterChainName,
215
      TlsContextManager tlsContextManager,
216
      FilterRegistry filterRegistry,
217
      // null disables FilterChainMatch uniqueness check, used for defaultFilterChain
218
      @Nullable Set<FilterChainMatch> filterChainMatchSet,
219
      Set<String> certProviderInstances,
220
      XdsResourceType.Args args)
221
      throws ResourceInvalidException {
222
    // FilterChain contains L4 filters, so we ensure it contains only HCM.
223
    if (proto.getFiltersCount() != 1) {
1✔
224
      throw new ResourceInvalidException("FilterChain " + filterChainName
1✔
225
          + " should contain exact one HttpConnectionManager filter");
226
    }
227
    io.envoyproxy.envoy.config.listener.v3.Filter l4Filter = proto.getFiltersList().get(0);
1✔
228
    if (!l4Filter.hasTypedConfig()) {
1✔
229
      throw new ResourceInvalidException(
1✔
230
          "FilterChain " + filterChainName + " contains filter " + l4Filter.getName()
1✔
231
              + " without typed_config");
232
    }
233
    Any any = l4Filter.getTypedConfig();
1✔
234
    if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) {
1✔
235
      throw new ResourceInvalidException(
1✔
236
          "FilterChain " + filterChainName + " contains filter " + l4Filter.getName()
1✔
237
              + " with unsupported typed_config type " + any.getTypeUrl());
1✔
238
    }
239

240
    // Parse HCM.
241
    HttpConnectionManager hcmProto;
242
    try {
243
      hcmProto = any.unpack(HttpConnectionManager.class);
1✔
244
    } catch (InvalidProtocolBufferException e) {
×
245
      throw new ResourceInvalidException("FilterChain " + filterChainName + " with filter "
×
246
          + l4Filter.getName() + " failed to unpack message", e);
×
247
    }
1✔
248
    io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager(
1✔
249
        hcmProto, filterRegistry, false /* isForClient */, args);
250

251
    // Parse Transport Socket.
252
    EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null;
1✔
253
    if (proto.hasTransportSocket()) {
1✔
254
      if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) {
1✔
255
        throw new ResourceInvalidException("transport-socket with name "
1✔
256
            + proto.getTransportSocket().getName() + " not supported.");
1✔
257
      }
258
      DownstreamTlsContext downstreamTlsContextProto;
259
      try {
260
        downstreamTlsContextProto =
1✔
261
            proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class);
1✔
262
      } catch (InvalidProtocolBufferException e) {
×
263
        throw new ResourceInvalidException("FilterChain " + filterChainName
×
264
            + " failed to unpack message", e);
265
      }
1✔
266
      downstreamTlsContext =
1✔
267
          EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext(
1✔
268
              validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances));
1✔
269
    }
270

271
    // Parse FilterChainMatch.
272
    FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch());
1✔
273
    // null used to skip this check for defaultFilterChain.
274
    if (filterChainMatchSet != null) {
1✔
275
      validateFilterChainMatchForUniqueness(filterChainMatchSet, filterChainMatch);
1✔
276
    }
277

278
    return FilterChain.create(
1✔
279
        filterChainName,
280
        filterChainMatch,
281
        httpConnectionManager,
282
        downstreamTlsContext,
283
        tlsContextManager
284
    );
285
  }
286

287
  @VisibleForTesting
288
  static DownstreamTlsContext validateDownstreamTlsContext(
289
      DownstreamTlsContext downstreamTlsContext, Set<String> certProviderInstances)
290
      throws ResourceInvalidException {
291
    if (downstreamTlsContext.hasCommonTlsContext()) {
1✔
292
      validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
293
          true);
294
    } else {
295
      throw new ResourceInvalidException(
1✔
296
          "common-tls-context is required in downstream-tls-context");
297
    }
298
    if (downstreamTlsContext.hasRequireSni()) {
1✔
299
      throw new ResourceInvalidException(
1✔
300
          "downstream-tls-context with require-sni is not supported");
301
    }
302
    DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext
1✔
303
        .getOcspStaplePolicy();
1✔
304
    if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED
1✔
305
        && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) {
306
      throw new ResourceInvalidException(
1✔
307
          "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name()
1✔
308
              + " is not supported");
309
    }
310
    return downstreamTlsContext;
1✔
311
  }
312

313
  private static void validateFilterChainMatchForUniqueness(
314
      Set<FilterChainMatch> filterChainMatchSet,
315
      FilterChainMatch filterChainMatch) throws ResourceInvalidException {
316
    // Flattens complex FilterChainMatch into a list of simple FilterChainMatch'es.
317
    List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
1✔
318
    for (FilterChainMatch cur : crossProduct) {
1✔
319
      if (!filterChainMatchSet.add(cur)) {
1✔
320
        throw new ResourceInvalidException("FilterChainMatch must be unique. "
1✔
321
            + "Found duplicate: " + cur);
322
      }
323
    }
1✔
324
  }
1✔
325

326
  private static List<FilterChainMatch> getCrossProduct(FilterChainMatch filterChainMatch) {
327
    // repeating fields to process:
328
    // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames
329
    List<FilterChainMatch> expandedList = expandOnPrefixRange(filterChainMatch);
1✔
330
    expandedList = expandOnApplicationProtocols(expandedList);
1✔
331
    expandedList = expandOnSourcePrefixRange(expandedList);
1✔
332
    expandedList = expandOnSourcePorts(expandedList);
1✔
333
    return expandOnServerNames(expandedList);
1✔
334
  }
335

336
  private static List<FilterChainMatch> expandOnPrefixRange(FilterChainMatch filterChainMatch) {
337
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
338
    if (filterChainMatch.prefixRanges().isEmpty()) {
1✔
339
      expandedList.add(filterChainMatch);
1✔
340
    } else {
341
      for (CidrRange cidrRange : filterChainMatch.prefixRanges()) {
1✔
342
        expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
343
            ImmutableList.of(cidrRange),
1✔
344
            filterChainMatch.applicationProtocols(),
1✔
345
            filterChainMatch.sourcePrefixRanges(),
1✔
346
            filterChainMatch.connectionSourceType(),
1✔
347
            filterChainMatch.sourcePorts(),
1✔
348
            filterChainMatch.serverNames(),
1✔
349
            filterChainMatch.transportProtocol()));
1✔
350
      }
1✔
351
    }
352
    return expandedList;
1✔
353
  }
354

355
  private static List<FilterChainMatch> expandOnApplicationProtocols(
356
      Collection<FilterChainMatch> set) {
357
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
358
    for (FilterChainMatch filterChainMatch : set) {
1✔
359
      if (filterChainMatch.applicationProtocols().isEmpty()) {
1✔
360
        expandedList.add(filterChainMatch);
1✔
361
      } else {
362
        for (String applicationProtocol : filterChainMatch.applicationProtocols()) {
1✔
363
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
364
              filterChainMatch.prefixRanges(),
1✔
365
              ImmutableList.of(applicationProtocol),
1✔
366
              filterChainMatch.sourcePrefixRanges(),
1✔
367
              filterChainMatch.connectionSourceType(),
1✔
368
              filterChainMatch.sourcePorts(),
1✔
369
              filterChainMatch.serverNames(),
1✔
370
              filterChainMatch.transportProtocol()));
1✔
371
        }
1✔
372
      }
373
    }
1✔
374
    return expandedList;
1✔
375
  }
376

377
  private static List<FilterChainMatch> expandOnSourcePrefixRange(
378
      Collection<FilterChainMatch> set) {
379
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
380
    for (FilterChainMatch filterChainMatch : set) {
1✔
381
      if (filterChainMatch.sourcePrefixRanges().isEmpty()) {
1✔
382
        expandedList.add(filterChainMatch);
1✔
383
      } else {
384
        for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) {
×
385
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
×
386
              filterChainMatch.prefixRanges(),
×
387
              filterChainMatch.applicationProtocols(),
×
388
              ImmutableList.of(cidrRange),
×
389
              filterChainMatch.connectionSourceType(),
×
390
              filterChainMatch.sourcePorts(),
×
391
              filterChainMatch.serverNames(),
×
392
              filterChainMatch.transportProtocol()));
×
393
        }
×
394
      }
395
    }
1✔
396
    return expandedList;
1✔
397
  }
398

399
  private static List<FilterChainMatch> expandOnSourcePorts(Collection<FilterChainMatch> set) {
400
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
401
    for (FilterChainMatch filterChainMatch : set) {
1✔
402
      if (filterChainMatch.sourcePorts().isEmpty()) {
1✔
403
        expandedList.add(filterChainMatch);
1✔
404
      } else {
405
        for (Integer sourcePort : filterChainMatch.sourcePorts()) {
1✔
406
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
407
              filterChainMatch.prefixRanges(),
1✔
408
              filterChainMatch.applicationProtocols(),
1✔
409
              filterChainMatch.sourcePrefixRanges(),
1✔
410
              filterChainMatch.connectionSourceType(),
1✔
411
              ImmutableList.of(sourcePort),
1✔
412
              filterChainMatch.serverNames(),
1✔
413
              filterChainMatch.transportProtocol()));
1✔
414
        }
1✔
415
      }
416
    }
1✔
417
    return expandedList;
1✔
418
  }
419

420
  private static List<FilterChainMatch> expandOnServerNames(Collection<FilterChainMatch> set) {
421
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
422
    for (FilterChainMatch filterChainMatch : set) {
1✔
423
      if (filterChainMatch.serverNames().isEmpty()) {
1✔
424
        expandedList.add(filterChainMatch);
1✔
425
      } else {
426
        for (String serverName : filterChainMatch.serverNames()) {
×
427
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
×
428
              filterChainMatch.prefixRanges(),
×
429
              filterChainMatch.applicationProtocols(),
×
430
              filterChainMatch.sourcePrefixRanges(),
×
431
              filterChainMatch.connectionSourceType(),
×
432
              filterChainMatch.sourcePorts(),
×
433
              ImmutableList.of(serverName),
×
434
              filterChainMatch.transportProtocol()));
×
435
        }
×
436
      }
437
    }
1✔
438
    return expandedList;
1✔
439
  }
440

441
  private static FilterChainMatch parseFilterChainMatch(
442
      io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto)
443
      throws ResourceInvalidException {
444
    ImmutableList.Builder<CidrRange> prefixRanges = ImmutableList.builder();
1✔
445
    ImmutableList.Builder<CidrRange> sourcePrefixRanges = ImmutableList.builder();
1✔
446
    try {
447
      for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) {
1✔
448
        prefixRanges.add(
1✔
449
            CidrRange.create(InetAddresses.forString(range.getAddressPrefix()),
1✔
450
                range.getPrefixLen().getValue()));
1✔
451
      }
1✔
452
      for (io.envoyproxy.envoy.config.core.v3.CidrRange range
453
          : proto.getSourcePrefixRangesList()) {
1✔
454
        sourcePrefixRanges.add(CidrRange.create(
×
455
            InetAddresses.forString(range.getAddressPrefix()), range.getPrefixLen().getValue()));
×
456
      }
×
457
    } catch (IllegalArgumentException ex) {
×
458
      throw new ResourceInvalidException("Failed to create CidrRange", ex);
×
459
    }
1✔
460

461
    ConnectionSourceType sourceType;
462
    switch (proto.getSourceType()) {
1✔
463
      case ANY:
464
        sourceType = ConnectionSourceType.ANY;
1✔
465
        break;
1✔
466
      case EXTERNAL:
467
        sourceType = ConnectionSourceType.EXTERNAL;
1✔
468
        break;
1✔
469
      case SAME_IP_OR_LOOPBACK:
470
        sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK;
×
471
        break;
×
472
      default:
473
        throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType());
×
474
    }
475
    return FilterChainMatch.create(
1✔
476
        proto.getDestinationPort().getValue(),
1✔
477
        prefixRanges.build(),
1✔
478
        ImmutableList.copyOf(proto.getApplicationProtocolsList()),
1✔
479
        sourcePrefixRanges.build(),
1✔
480
        sourceType,
481
        ImmutableList.copyOf(proto.getSourcePortsList()),
1✔
482
        ImmutableList.copyOf(proto.getServerNamesList()),
1✔
483
        proto.getTransportProtocol());
1✔
484
  }
485

486
  @VisibleForTesting
487
  static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
488
      HttpConnectionManager proto, FilterRegistry filterRegistry,
489
      boolean isForClient, XdsResourceType.Args args) throws ResourceInvalidException {
490
    if (proto.getXffNumTrustedHops() != 0) {
1✔
491
      throw new ResourceInvalidException(
1✔
492
          "HttpConnectionManager with xff_num_trusted_hops unsupported");
493
    }
494
    if (!proto.getOriginalIpDetectionExtensionsList().isEmpty()) {
1✔
495
      throw new ResourceInvalidException("HttpConnectionManager with "
1✔
496
          + "original_ip_detection_extensions unsupported");
497
    }
498
    // Obtain max_stream_duration from Http Protocol Options.
499
    long maxStreamDuration = 0;
1✔
500
    if (proto.hasCommonHttpProtocolOptions()) {
1✔
501
      HttpProtocolOptions options = proto.getCommonHttpProtocolOptions();
1✔
502
      if (options.hasMaxStreamDuration()) {
1✔
503
        maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration());
1✔
504
      }
505
    }
506

507
    // Parse http filters.
508
    if (proto.getHttpFiltersList().isEmpty()) {
1✔
509
      throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager.");
1✔
510
    }
511
    List<Filter.NamedFilterConfig> filterConfigs = new ArrayList<>();
1✔
512
    Set<String> names = new HashSet<>();
1✔
513
    for (int i = 0; i < proto.getHttpFiltersCount(); i++) {
1✔
514
      io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
515
          httpFilter = proto.getHttpFiltersList().get(i);
1✔
516
      String filterName = httpFilter.getName();
1✔
517
      if (!names.add(filterName)) {
1✔
518
        throw new ResourceInvalidException(
1✔
519
            "HttpConnectionManager contains duplicate HttpFilter: " + filterName);
520
      }
521
      StructOrError<Filter.FilterConfig> filterConfig =
1✔
522
          parseHttpFilter(httpFilter, filterRegistry, isForClient);
1✔
523
      if ((i == proto.getHttpFiltersCount() - 1)
1✔
524
          && (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) {
1✔
525
        throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: "
1✔
526
            + filterName);
527
      }
528
      if (filterConfig == null) {
1✔
529
        continue;
1✔
530
      }
531
      if (filterConfig.getErrorDetail() != null) {
1✔
532
        throw new ResourceInvalidException(
×
533
            "HttpConnectionManager contains invalid HttpFilter: "
534
                + filterConfig.getErrorDetail());
×
535
      }
536
      if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) {
1✔
537
        throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: "
1✔
538
            + filterName);
539
      }
540
      filterConfigs.add(new Filter.NamedFilterConfig(filterName, filterConfig.getStruct()));
1✔
541
    }
542

543
    // Parse inlined RouteConfiguration or RDS.
544
    if (proto.hasRouteConfig()) {
1✔
545
      List<VirtualHost> virtualHosts = extractVirtualHosts(
1✔
546
          proto.getRouteConfig(), filterRegistry, args);
1✔
547
      return io.grpc.xds.HttpConnectionManager.forVirtualHosts(
1✔
548
          maxStreamDuration, virtualHosts, filterConfigs);
549
    }
550
    if (proto.hasRds()) {
1✔
551
      Rds rds = proto.getRds();
1✔
552
      if (!rds.hasConfigSource()) {
1✔
553
        throw new ResourceInvalidException(
×
554
            "HttpConnectionManager contains invalid RDS: missing config_source");
555
      }
556
      if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) {
1✔
557
        throw new ResourceInvalidException(
1✔
558
            "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource");
559
      }
560
      return io.grpc.xds.HttpConnectionManager.forRdsName(
1✔
561
          maxStreamDuration, rds.getRouteConfigName(), filterConfigs);
1✔
562
    }
563
    throw new ResourceInvalidException(
1✔
564
        "HttpConnectionManager neither has inlined route_config nor RDS");
565
  }
566

567
  // hard-coded: currently router config is the only terminal filter.
568
  private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) {
569
    return RouterFilter.ROUTER_CONFIG.equals(filterConfig);
1✔
570
  }
571

572
  @VisibleForTesting
573
  @Nullable // Returns null if the filter is optional but not supported.
574
  static StructOrError<Filter.FilterConfig> parseHttpFilter(
575
      io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
576
          httpFilter, FilterRegistry filterRegistry, boolean isForClient) {
577
    String filterName = httpFilter.getName();
1✔
578
    boolean isOptional = httpFilter.getIsOptional();
1✔
579
    if (!httpFilter.hasTypedConfig()) {
1✔
580
      return isOptional ? null : StructOrError.fromError(
1✔
581
          "HttpFilter [" + filterName + "] is not optional and has no typed config");
582
    }
583
    Message rawConfig = httpFilter.getTypedConfig();
1✔
584
    String typeUrl = httpFilter.getTypedConfig().getTypeUrl();
1✔
585

586
    try {
587
      if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) {
1✔
588
        TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class);
1✔
589
        typeUrl = typedStruct.getTypeUrl();
1✔
590
        rawConfig = typedStruct.getValue();
1✔
591
      } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) {
1✔
592
        com.github.xds.type.v3.TypedStruct newTypedStruct =
×
593
            httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class);
×
594
        typeUrl = newTypedStruct.getTypeUrl();
×
595
        rawConfig = newTypedStruct.getValue();
×
596
      }
597
    } catch (InvalidProtocolBufferException e) {
×
598
      return StructOrError.fromError(
×
599
          "HttpFilter [" + filterName + "] contains invalid proto: " + e);
600
    }
1✔
601

602
    Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
603
    if (provider == null
1✔
604
        || (isForClient && !provider.isClientFilter())
1✔
605
        || (!isForClient && !provider.isServerFilter())) {
1✔
606
      // Filter type not supported.
607
      return isOptional ? null : StructOrError.fromError(
1✔
608
          "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + (
609
              isForClient ? "client" : "server"));
1✔
610
    }
611
    ConfigOrError<? extends FilterConfig> filterConfig = provider.parseFilterConfig(rawConfig);
1✔
612
    if (filterConfig.errorDetail != null) {
1✔
613
      return StructOrError.fromError(
×
614
          "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);
615
    }
616
    return StructOrError.fromStruct(filterConfig.config);
1✔
617
  }
618

619
  @AutoValue
620
  abstract static class LdsUpdate implements ResourceUpdate {
1✔
621
    // Http level api listener configuration.
622
    @Nullable
623
    abstract io.grpc.xds.HttpConnectionManager httpConnectionManager();
624

625
    // Tcp level listener configuration.
626
    @Nullable
627
    abstract EnvoyServerProtoData.Listener listener();
628

629
    static LdsUpdate forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager) {
630
      checkNotNull(httpConnectionManager, "httpConnectionManager");
1✔
631
      return new io.grpc.xds.AutoValue_XdsListenerResource_LdsUpdate(httpConnectionManager, null);
1✔
632
    }
633

634
    static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) {
635
      checkNotNull(listener, "listener");
1✔
636
      return new io.grpc.xds.AutoValue_XdsListenerResource_LdsUpdate(null, listener);
1✔
637
    }
638
  }
639
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc