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

grpc / grpc-java / #19682

11 Feb 2025 01:14AM CUT coverage: 88.605% (-0.004%) from 88.609%
#19682

push

github

web-flow
xds: improve code readability of server FilterChain parsing

- Improve code flow and variable names
- Reduce nesting
- Add comments between logical blocks
- Add comments explaining some xDS/gRPC nuances

34182 of 38578 relevant lines covered (88.6%)

0.89 hits per line

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

84.66
/../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.protobuf.Any;
29
import com.google.protobuf.InvalidProtocolBufferException;
30
import com.google.protobuf.Message;
31
import com.google.protobuf.util.Durations;
32
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
33
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
34
import io.envoyproxy.envoy.config.core.v3.TrafficDirection;
35
import io.envoyproxy.envoy.config.listener.v3.Listener;
36
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
37
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
38
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext;
39
import io.grpc.xds.EnvoyServerProtoData.CidrRange;
40
import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType;
41
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
42
import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch;
43
import io.grpc.xds.Filter.FilterConfig;
44
import io.grpc.xds.XdsListenerResource.LdsUpdate;
45
import io.grpc.xds.client.XdsResourceType;
46
import java.net.UnknownHostException;
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(range.getAddressPrefix(), range.getPrefixLen().getValue()));
1✔
450
      }
1✔
451
      for (io.envoyproxy.envoy.config.core.v3.CidrRange range
452
          : proto.getSourcePrefixRangesList()) {
1✔
453
        sourcePrefixRanges.add(
×
454
            CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue()));
×
455
      }
×
456
    } catch (UnknownHostException e) {
×
457
      throw new ResourceInvalidException("Failed to create CidrRange", e);
×
458
    }
1✔
459
    ConnectionSourceType sourceType;
460
    switch (proto.getSourceType()) {
1✔
461
      case ANY:
462
        sourceType = ConnectionSourceType.ANY;
1✔
463
        break;
1✔
464
      case EXTERNAL:
465
        sourceType = ConnectionSourceType.EXTERNAL;
1✔
466
        break;
1✔
467
      case SAME_IP_OR_LOOPBACK:
468
        sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK;
×
469
        break;
×
470
      default:
471
        throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType());
×
472
    }
473
    return FilterChainMatch.create(
1✔
474
        proto.getDestinationPort().getValue(),
1✔
475
        prefixRanges.build(),
1✔
476
        ImmutableList.copyOf(proto.getApplicationProtocolsList()),
1✔
477
        sourcePrefixRanges.build(),
1✔
478
        sourceType,
479
        ImmutableList.copyOf(proto.getSourcePortsList()),
1✔
480
        ImmutableList.copyOf(proto.getServerNamesList()),
1✔
481
        proto.getTransportProtocol());
1✔
482
  }
483

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

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

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

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

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

588
    try {
589
      if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) {
1✔
590
        TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class);
1✔
591
        typeUrl = typedStruct.getTypeUrl();
1✔
592
        rawConfig = typedStruct.getValue();
1✔
593
      } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) {
1✔
594
        com.github.xds.type.v3.TypedStruct newTypedStruct =
×
595
            httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class);
×
596
        typeUrl = newTypedStruct.getTypeUrl();
×
597
        rawConfig = newTypedStruct.getValue();
×
598
      }
599
    } catch (InvalidProtocolBufferException e) {
×
600
      return StructOrError.fromError(
×
601
          "HttpFilter [" + filterName + "] contains invalid proto: " + e);
602
    }
1✔
603
    Filter filter = filterRegistry.get(typeUrl);
1✔
604
    if ((isForClient && !(filter instanceof Filter.ClientInterceptorBuilder))
1✔
605
        || (!isForClient && !(filter instanceof Filter.ServerInterceptorBuilder))) {
606
      if (isOptional) {
1✔
607
        return null;
1✔
608
      } else {
609
        return StructOrError.fromError(
1✔
610
            "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for "
611
                + (isForClient ? "client" : "server"));
1✔
612
      }
613
    }
614
    ConfigOrError<? extends FilterConfig> filterConfig = filter.parseFilterConfig(rawConfig);
1✔
615
    if (filterConfig.errorDetail != null) {
1✔
616
      return StructOrError.fromError(
×
617
          "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);
618
    }
619
    return StructOrError.fromStruct(filterConfig.config);
1✔
620
  }
621

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

628
    // Tcp level listener configuration.
629
    @Nullable
630
    abstract EnvoyServerProtoData.Listener listener();
631

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

637
    static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) {
638
      checkNotNull(listener, "listener");
1✔
639
      return new io.grpc.xds.AutoValue_XdsListenerResource_LdsUpdate(null, listener);
1✔
640
    }
641
  }
642
}
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

© 2025 Coveralls, Inc