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

grpc / grpc-java / #20276

11 May 2026 08:24AM UTC coverage: 88.821% (-0.02%) from 88.838%
#20276

push

github

web-flow
Allow injecting bootstrap info into xDS Filter API for config parsing (#12724)

Extend the xDS Filter API to support injecting bootstrap information
into filters during configuration parsing. This allows filters to access
context information (e.g., allowed gRPC services) from the resource loading
layer during configuration validation and parsing.

- Update `Filter.Provider.parseFilterConfig` and
`parseFilterConfigOverride`
  to accept a `FilterContext` parameter.
- Introduce `BootstrapInfoGrpcServiceContextProvider` to encapsulate
  bootstrap info for context resolution.
- Update `XdsListenerResource` and `XdsRouteConfigureResource` to
  construct and pass `FilterContext` during configuration parsing.
- Update sub-filters (`FaultFilter`, `RbacFilter`,
`GcpAuthenticationFilter`,
  `RouterFilter`) to match the updated `FilterContext` signature.

Known Gaps & Limitations:
1. **MetricHolder**: Propagation of `MetricHolder` is not supported with
   this approach currently and is planned for support in a later phase.
2. **NameResolverRegistry**: Propagation is deferred for consistency.
While it could be passed from `XdsNameResolver` on the client side, there is
no equivalent mechanism on the server side. To ensure consistent
behavior, `DefaultRegistry` is used when validating schemes and creating channels.

36254 of 40817 relevant lines covered (88.82%)

0.89 hits per line

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

86.02
/../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
    SocketAddress socketAddress = null;
1✔
166
    if (proto.getAddress().hasSocketAddress()) {
1✔
167
      socketAddress = proto.getAddress().getSocketAddress();
1✔
168
      address = socketAddress.getAddress();
1✔
169
      if (address.isEmpty()) {
1✔
170
        throw new ResourceInvalidException("Invalid address: Empty address is not allowed.");
1✔
171
      }
172
      switch (socketAddress.getPortSpecifierCase()) {
1✔
173
        case NAMED_PORT:
174
          throw new ResourceInvalidException("NAMED_PORT is not supported in gRPC.");
1✔
175
        case PORT_VALUE:
176
          address = address + ":" + socketAddress.getPortValue();
1✔
177
          break;
1✔
178
        default:
179
          // noop
180
      }
181
    }
182

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

204
    FilterChain defaultFilterChain = null;
1✔
205
    if (proto.hasDefaultFilterChain()) {
1✔
206
      String defaultFilterChainName = proto.getDefaultFilterChain().getName();
1✔
207
      if (defaultFilterChainName.isEmpty()) {
1✔
208
        defaultFilterChainName = "chain_default";
1✔
209
      }
210
      defaultFilterChain = parseFilterChain(
1✔
211
          proto.getDefaultFilterChain(), defaultFilterChainName, tlsContextManager, filterRegistry,
1✔
212
          null, certProviderInstances, args);
213
    }
214

215
    return EnvoyServerProtoData.Listener.create(proto.getName(), address, filterChains.build(),
1✔
216
        defaultFilterChain, socketAddress == null ? null : socketAddress.getProtocol());
1✔
217
  }
218

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

248
    // Parse HCM.
249
    HttpConnectionManager hcmProto;
250
    try {
251
      hcmProto = any.unpack(HttpConnectionManager.class);
1✔
252
    } catch (InvalidProtocolBufferException e) {
×
253
      throw new ResourceInvalidException("FilterChain " + filterChainName + " with filter "
×
254
          + l4Filter.getName() + " failed to unpack message", e);
×
255
    }
1✔
256
    io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager(
1✔
257
        hcmProto, filterRegistry, false /* isForClient */, args);
258

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

279
    // Parse FilterChainMatch.
280
    FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch());
1✔
281
    // null used to skip this check for defaultFilterChain.
282
    if (filterChainMatchSet != null) {
1✔
283
      validateFilterChainMatchForUniqueness(filterChainMatchSet, filterChainMatch);
1✔
284
    }
285

286
    return FilterChain.create(
1✔
287
        filterChainName,
288
        filterChainMatch,
289
        httpConnectionManager,
290
        downstreamTlsContext,
291
        tlsContextManager
292
    );
293
  }
294

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

321
  private static void validateFilterChainMatchForUniqueness(
322
      Set<FilterChainMatch> filterChainMatchSet,
323
      FilterChainMatch filterChainMatch) throws ResourceInvalidException {
324
    // Flattens complex FilterChainMatch into a list of simple FilterChainMatch'es.
325
    List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
1✔
326
    for (FilterChainMatch cur : crossProduct) {
1✔
327
      if (!filterChainMatchSet.add(cur)) {
1✔
328
        throw new ResourceInvalidException("FilterChainMatch must be unique. "
1✔
329
            + "Found duplicate: " + cur);
330
      }
331
    }
1✔
332
  }
1✔
333

334
  private static List<FilterChainMatch> getCrossProduct(FilterChainMatch filterChainMatch) {
335
    // repeating fields to process:
336
    // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames
337
    List<FilterChainMatch> expandedList = expandOnPrefixRange(filterChainMatch);
1✔
338
    expandedList = expandOnApplicationProtocols(expandedList);
1✔
339
    expandedList = expandOnSourcePrefixRange(expandedList);
1✔
340
    expandedList = expandOnSourcePorts(expandedList);
1✔
341
    return expandOnServerNames(expandedList);
1✔
342
  }
343

344
  private static List<FilterChainMatch> expandOnPrefixRange(FilterChainMatch filterChainMatch) {
345
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
346
    if (filterChainMatch.prefixRanges().isEmpty()) {
1✔
347
      expandedList.add(filterChainMatch);
1✔
348
    } else {
349
      for (CidrRange cidrRange : filterChainMatch.prefixRanges()) {
1✔
350
        expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
351
            ImmutableList.of(cidrRange),
1✔
352
            filterChainMatch.applicationProtocols(),
1✔
353
            filterChainMatch.sourcePrefixRanges(),
1✔
354
            filterChainMatch.connectionSourceType(),
1✔
355
            filterChainMatch.sourcePorts(),
1✔
356
            filterChainMatch.serverNames(),
1✔
357
            filterChainMatch.transportProtocol()));
1✔
358
      }
1✔
359
    }
360
    return expandedList;
1✔
361
  }
362

363
  private static List<FilterChainMatch> expandOnApplicationProtocols(
364
      Collection<FilterChainMatch> set) {
365
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
366
    for (FilterChainMatch filterChainMatch : set) {
1✔
367
      if (filterChainMatch.applicationProtocols().isEmpty()) {
1✔
368
        expandedList.add(filterChainMatch);
1✔
369
      } else {
370
        for (String applicationProtocol : filterChainMatch.applicationProtocols()) {
1✔
371
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
372
              filterChainMatch.prefixRanges(),
1✔
373
              ImmutableList.of(applicationProtocol),
1✔
374
              filterChainMatch.sourcePrefixRanges(),
1✔
375
              filterChainMatch.connectionSourceType(),
1✔
376
              filterChainMatch.sourcePorts(),
1✔
377
              filterChainMatch.serverNames(),
1✔
378
              filterChainMatch.transportProtocol()));
1✔
379
        }
1✔
380
      }
381
    }
1✔
382
    return expandedList;
1✔
383
  }
384

385
  private static List<FilterChainMatch> expandOnSourcePrefixRange(
386
      Collection<FilterChainMatch> set) {
387
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
388
    for (FilterChainMatch filterChainMatch : set) {
1✔
389
      if (filterChainMatch.sourcePrefixRanges().isEmpty()) {
1✔
390
        expandedList.add(filterChainMatch);
1✔
391
      } else {
392
        for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) {
×
393
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
×
394
              filterChainMatch.prefixRanges(),
×
395
              filterChainMatch.applicationProtocols(),
×
396
              ImmutableList.of(cidrRange),
×
397
              filterChainMatch.connectionSourceType(),
×
398
              filterChainMatch.sourcePorts(),
×
399
              filterChainMatch.serverNames(),
×
400
              filterChainMatch.transportProtocol()));
×
401
        }
×
402
      }
403
    }
1✔
404
    return expandedList;
1✔
405
  }
406

407
  private static List<FilterChainMatch> expandOnSourcePorts(Collection<FilterChainMatch> set) {
408
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
409
    for (FilterChainMatch filterChainMatch : set) {
1✔
410
      if (filterChainMatch.sourcePorts().isEmpty()) {
1✔
411
        expandedList.add(filterChainMatch);
1✔
412
      } else {
413
        for (Integer sourcePort : filterChainMatch.sourcePorts()) {
1✔
414
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
1✔
415
              filterChainMatch.prefixRanges(),
1✔
416
              filterChainMatch.applicationProtocols(),
1✔
417
              filterChainMatch.sourcePrefixRanges(),
1✔
418
              filterChainMatch.connectionSourceType(),
1✔
419
              ImmutableList.of(sourcePort),
1✔
420
              filterChainMatch.serverNames(),
1✔
421
              filterChainMatch.transportProtocol()));
1✔
422
        }
1✔
423
      }
424
    }
1✔
425
    return expandedList;
1✔
426
  }
427

428
  private static List<FilterChainMatch> expandOnServerNames(Collection<FilterChainMatch> set) {
429
    ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
1✔
430
    for (FilterChainMatch filterChainMatch : set) {
1✔
431
      if (filterChainMatch.serverNames().isEmpty()) {
1✔
432
        expandedList.add(filterChainMatch);
1✔
433
      } else {
434
        for (String serverName : filterChainMatch.serverNames()) {
×
435
          expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
×
436
              filterChainMatch.prefixRanges(),
×
437
              filterChainMatch.applicationProtocols(),
×
438
              filterChainMatch.sourcePrefixRanges(),
×
439
              filterChainMatch.connectionSourceType(),
×
440
              filterChainMatch.sourcePorts(),
×
441
              ImmutableList.of(serverName),
×
442
              filterChainMatch.transportProtocol()));
×
443
        }
×
444
      }
445
    }
1✔
446
    return expandedList;
1✔
447
  }
448

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

469
    ConnectionSourceType sourceType;
470
    switch (proto.getSourceType()) {
1✔
471
      case ANY:
472
        sourceType = ConnectionSourceType.ANY;
1✔
473
        break;
1✔
474
      case EXTERNAL:
475
        sourceType = ConnectionSourceType.EXTERNAL;
1✔
476
        break;
1✔
477
      case SAME_IP_OR_LOOPBACK:
478
        sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK;
×
479
        break;
×
480
      default:
481
        throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType());
×
482
    }
483
    return FilterChainMatch.create(
1✔
484
        proto.getDestinationPort().getValue(),
1✔
485
        prefixRanges.build(),
1✔
486
        ImmutableList.copyOf(proto.getApplicationProtocolsList()),
1✔
487
        sourcePrefixRanges.build(),
1✔
488
        sourceType,
489
        ImmutableList.copyOf(proto.getSourcePortsList()),
1✔
490
        ImmutableList.copyOf(proto.getServerNamesList()),
1✔
491
        proto.getTransportProtocol());
1✔
492
  }
493

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

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

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

575
  // hard-coded: currently router config is the only terminal filter.
576
  private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) {
577
    return RouterFilter.ROUTER_CONFIG.equals(filterConfig);
1✔
578
  }
579

580
  @VisibleForTesting
581
  @Nullable // Returns null if the filter is optional but not supported.
582
  static StructOrError<Filter.FilterConfig> parseHttpFilter(
583
      io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
584
          httpFilter, FilterRegistry filterRegistry, boolean isForClient,
585
      XdsResourceType.Args args) {
586
    String filterName = httpFilter.getName();
1✔
587
    boolean isOptional = httpFilter.getIsOptional();
1✔
588
    if (!httpFilter.hasTypedConfig()) {
1✔
589
      return isOptional ? null : StructOrError.fromError(
1✔
590
          "HttpFilter [" + filterName + "] is not optional and has no typed config");
591
    }
592
    Message rawConfig = httpFilter.getTypedConfig();
1✔
593
    String typeUrl = httpFilter.getTypedConfig().getTypeUrl();
1✔
594

595
    try {
596
      if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) {
1✔
597
        TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class);
1✔
598
        typeUrl = typedStruct.getTypeUrl();
1✔
599
        rawConfig = typedStruct.getValue();
1✔
600
      } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) {
1✔
601
        com.github.xds.type.v3.TypedStruct newTypedStruct =
×
602
            httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class);
×
603
        typeUrl = newTypedStruct.getTypeUrl();
×
604
        rawConfig = newTypedStruct.getValue();
×
605
      }
606
    } catch (InvalidProtocolBufferException e) {
×
607
      return StructOrError.fromError(
×
608
          "HttpFilter [" + filterName + "] contains invalid proto: " + e);
609
    }
1✔
610

611
    Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
612
    if (provider == null
1✔
613
        || (isForClient && !provider.isClientFilter())
1✔
614
        || (!isForClient && !provider.isServerFilter())) {
1✔
615
      // Filter type not supported.
616
      return isOptional ? null : StructOrError.fromError(
1✔
617
          "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + (
618
              isForClient ? "client" : "server"));
1✔
619
    }
620

621
    Filter.FilterConfigParseContext filterContext = Filter.FilterConfigParseContext.builder()
1✔
622
        .bootstrapInfo(args.getBootstrapInfo())
1✔
623
        .serverInfo(args.getServerInfo())
1✔
624
        .build();
1✔
625
    ConfigOrError<? extends FilterConfig> filterConfig =
1✔
626
        provider.parseFilterConfig(rawConfig, filterContext);
1✔
627
    if (filterConfig.errorDetail != null) {
1✔
628
      return StructOrError.fromError(
×
629
          "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);
630
    }
631
    return StructOrError.fromStruct(filterConfig.config);
1✔
632
  }
633

634
  @AutoValue
635
  abstract static class LdsUpdate implements ResourceUpdate {
1✔
636
    // Http level api listener configuration.
637
    @Nullable
638
    abstract io.grpc.xds.HttpConnectionManager httpConnectionManager();
639

640
    // Tcp level listener configuration.
641
    @Nullable
642
    abstract EnvoyServerProtoData.Listener listener();
643

644
    static LdsUpdate forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager) {
645
      checkNotNull(httpConnectionManager, "httpConnectionManager");
1✔
646
      return new io.grpc.xds.AutoValue_XdsListenerResource_LdsUpdate(httpConnectionManager, null);
1✔
647
    }
648

649
    static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) {
650
      checkNotNull(listener, "listener");
1✔
651
      return new io.grpc.xds.AutoValue_XdsListenerResource_LdsUpdate(null, listener);
1✔
652
    }
653
  }
654
}
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