xref: /aosp_15_r20/external/grpc-grpc-java/xds/src/main/java/io/grpc/xds/XdsListenerResource.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
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.XdsClient.ResourceUpdate;
21 import static io.grpc.xds.XdsClientImpl.ResourceInvalidException;
22 import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext;
23 import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts;
24 
25 import com.github.udpa.udpa.type.v1.TypedStruct;
26 import com.google.auto.value.AutoValue;
27 import com.google.common.annotations.VisibleForTesting;
28 import com.google.common.collect.ImmutableList;
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 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> {
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();
62 
getInstance()63   public static XdsListenerResource getInstance() {
64     return instance;
65   }
66 
67   @Override
68   @Nullable
extractResourceName(Message unpackedResource)69   String extractResourceName(Message unpackedResource) {
70     if (!(unpackedResource instanceof Listener)) {
71       return null;
72     }
73     return ((Listener) unpackedResource).getName();
74   }
75 
76   @Override
typeName()77   String typeName() {
78     return "LDS";
79   }
80 
81   @Override
unpackedClassName()82   Class<Listener> unpackedClassName() {
83     return Listener.class;
84   }
85 
86   @Override
typeUrl()87   String typeUrl() {
88     return ADS_TYPE_URL_LDS;
89   }
90 
91   @Override
isFullStateOfTheWorld()92   boolean isFullStateOfTheWorld() {
93     return true;
94   }
95 
96   @Override
doParse(Args args, Message unpackedMessage)97   LdsUpdate doParse(Args args, Message unpackedMessage)
98       throws ResourceInvalidException {
99     if (!(unpackedMessage instanceof Listener)) {
100       throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
101     }
102     Listener listener = (Listener) unpackedMessage;
103 
104     if (listener.hasApiListener()) {
105       return processClientSideListener(
106           listener, args);
107     } else {
108       return processServerSideListener(
109           listener, args);
110     }
111   }
112 
processClientSideListener(Listener listener, Args args)113   private LdsUpdate processClientSideListener(Listener listener, Args args)
114       throws ResourceInvalidException {
115     // Unpack HttpConnectionManager from the Listener.
116     HttpConnectionManager hcm;
117     try {
118       hcm = unpackCompatibleType(
119           listener.getApiListener().getApiListener(), HttpConnectionManager.class,
120           TYPE_URL_HTTP_CONNECTION_MANAGER, null);
121     } catch (InvalidProtocolBufferException e) {
122       throw new ResourceInvalidException(
123           "Could not parse HttpConnectionManager config from ApiListener", e);
124     }
125     return LdsUpdate.forApiListener(parseHttpConnectionManager(
126         hcm, args.filterRegistry, true /* isForClient */));
127   }
128 
processServerSideListener(Listener proto, Args args)129   private LdsUpdate processServerSideListener(Listener proto, Args args)
130       throws ResourceInvalidException {
131     Set<String> certProviderInstances = null;
132     if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) {
133       certProviderInstances = args.bootstrapInfo.certProviders().keySet();
134     }
135     return LdsUpdate.forTcpListener(parseServerSideListener(proto, args.tlsContextManager,
136         args.filterRegistry, certProviderInstances));
137   }
138 
139   @VisibleForTesting
parseServerSideListener( Listener proto, TlsContextManager tlsContextManager, FilterRegistry filterRegistry, Set<String> certProviderInstances)140   static EnvoyServerProtoData.Listener parseServerSideListener(
141       Listener proto, TlsContextManager tlsContextManager,
142       FilterRegistry filterRegistry, Set<String> certProviderInstances)
143       throws ResourceInvalidException {
144     if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND)
145         && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) {
146       throw new ResourceInvalidException(
147           "Listener " + proto.getName() + " with invalid traffic direction: "
148               + proto.getTrafficDirection());
149     }
150     if (!proto.getListenerFiltersList().isEmpty()) {
151       throw new ResourceInvalidException(
152           "Listener " + proto.getName() + " cannot have listener_filters");
153     }
154     if (proto.hasUseOriginalDst()) {
155       throw new ResourceInvalidException(
156           "Listener " + proto.getName() + " cannot have use_original_dst set to true");
157     }
158 
159     String address = null;
160     if (proto.getAddress().hasSocketAddress()) {
161       SocketAddress socketAddress = proto.getAddress().getSocketAddress();
162       address = socketAddress.getAddress();
163       switch (socketAddress.getPortSpecifierCase()) {
164         case NAMED_PORT:
165           address = address + ":" + socketAddress.getNamedPort();
166           break;
167         case PORT_VALUE:
168           address = address + ":" + socketAddress.getPortValue();
169           break;
170         default:
171           // noop
172       }
173     }
174 
175     ImmutableList.Builder<FilterChain> filterChains = ImmutableList.builder();
176     Set<FilterChainMatch> uniqueSet = new HashSet<>();
177     for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) {
178       filterChains.add(
179           parseFilterChain(fc, tlsContextManager, filterRegistry, uniqueSet,
180               certProviderInstances));
181     }
182     FilterChain defaultFilterChain = null;
183     if (proto.hasDefaultFilterChain()) {
184       defaultFilterChain = parseFilterChain(
185           proto.getDefaultFilterChain(), tlsContextManager, filterRegistry,
186           null, certProviderInstances);
187     }
188 
189     return EnvoyServerProtoData.Listener.create(
190         proto.getName(), address, filterChains.build(), defaultFilterChain);
191   }
192 
193   @VisibleForTesting
parseFilterChain( io.envoyproxy.envoy.config.listener.v3.FilterChain proto, TlsContextManager tlsContextManager, FilterRegistry filterRegistry, Set<FilterChainMatch> uniqueSet, Set<String> certProviderInstances)194   static FilterChain parseFilterChain(
195       io.envoyproxy.envoy.config.listener.v3.FilterChain proto,
196       TlsContextManager tlsContextManager, FilterRegistry filterRegistry,
197       Set<FilterChainMatch> uniqueSet, Set<String> certProviderInstances)
198       throws ResourceInvalidException {
199     if (proto.getFiltersCount() != 1) {
200       throw new ResourceInvalidException("FilterChain " + proto.getName()
201           + " should contain exact one HttpConnectionManager filter");
202     }
203     io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0);
204     if (!filter.hasTypedConfig()) {
205       throw new ResourceInvalidException(
206           "FilterChain " + proto.getName() + " contains filter " + filter.getName()
207               + " without typed_config");
208     }
209     Any any = filter.getTypedConfig();
210     // HttpConnectionManager is the only supported network filter at the moment.
211     if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) {
212       throw new ResourceInvalidException(
213           "FilterChain " + proto.getName() + " contains filter " + filter.getName()
214               + " with unsupported typed_config type " + any.getTypeUrl());
215     }
216     HttpConnectionManager hcmProto;
217     try {
218       hcmProto = any.unpack(HttpConnectionManager.class);
219     } catch (InvalidProtocolBufferException e) {
220       throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter "
221           + filter.getName() + " failed to unpack message", e);
222     }
223     io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager(
224         hcmProto, filterRegistry, false /* isForClient */);
225 
226     EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null;
227     if (proto.hasTransportSocket()) {
228       if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) {
229         throw new ResourceInvalidException("transport-socket with name "
230             + proto.getTransportSocket().getName() + " not supported.");
231       }
232       DownstreamTlsContext downstreamTlsContextProto;
233       try {
234         downstreamTlsContextProto =
235             proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class);
236       } catch (InvalidProtocolBufferException e) {
237         throw new ResourceInvalidException("FilterChain " + proto.getName()
238             + " failed to unpack message", e);
239       }
240       downstreamTlsContext =
241           EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext(
242               validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances));
243     }
244 
245     FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch());
246     checkForUniqueness(uniqueSet, filterChainMatch);
247     return FilterChain.create(
248         proto.getName(),
249         filterChainMatch,
250         httpConnectionManager,
251         downstreamTlsContext,
252         tlsContextManager
253     );
254   }
255 
256   @VisibleForTesting
validateDownstreamTlsContext( DownstreamTlsContext downstreamTlsContext, Set<String> certProviderInstances)257   static DownstreamTlsContext validateDownstreamTlsContext(
258       DownstreamTlsContext downstreamTlsContext, Set<String> certProviderInstances)
259       throws ResourceInvalidException {
260     if (downstreamTlsContext.hasCommonTlsContext()) {
261       validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances,
262           true);
263     } else {
264       throw new ResourceInvalidException(
265           "common-tls-context is required in downstream-tls-context");
266     }
267     if (downstreamTlsContext.hasRequireSni()) {
268       throw new ResourceInvalidException(
269           "downstream-tls-context with require-sni is not supported");
270     }
271     DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext
272         .getOcspStaplePolicy();
273     if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED
274         && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) {
275       throw new ResourceInvalidException(
276           "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name()
277               + " is not supported");
278     }
279     return downstreamTlsContext;
280   }
281 
checkForUniqueness(Set<FilterChainMatch> uniqueSet, FilterChainMatch filterChainMatch)282   private static void checkForUniqueness(Set<FilterChainMatch> uniqueSet,
283       FilterChainMatch filterChainMatch) throws ResourceInvalidException {
284     if (uniqueSet != null) {
285       List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
286       for (FilterChainMatch cur : crossProduct) {
287         if (!uniqueSet.add(cur)) {
288           throw new ResourceInvalidException("FilterChainMatch must be unique. "
289               + "Found duplicate: " + cur);
290         }
291       }
292     }
293   }
294 
getCrossProduct(FilterChainMatch filterChainMatch)295   private static List<FilterChainMatch> getCrossProduct(FilterChainMatch filterChainMatch) {
296     // repeating fields to process:
297     // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames
298     List<FilterChainMatch> expandedList = expandOnPrefixRange(filterChainMatch);
299     expandedList = expandOnApplicationProtocols(expandedList);
300     expandedList = expandOnSourcePrefixRange(expandedList);
301     expandedList = expandOnSourcePorts(expandedList);
302     return expandOnServerNames(expandedList);
303   }
304 
expandOnPrefixRange(FilterChainMatch filterChainMatch)305   private static List<FilterChainMatch> expandOnPrefixRange(FilterChainMatch filterChainMatch) {
306     ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
307     if (filterChainMatch.prefixRanges().isEmpty()) {
308       expandedList.add(filterChainMatch);
309     } else {
310       for (CidrRange cidrRange : filterChainMatch.prefixRanges()) {
311         expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
312             ImmutableList.of(cidrRange),
313             filterChainMatch.applicationProtocols(),
314             filterChainMatch.sourcePrefixRanges(),
315             filterChainMatch.connectionSourceType(),
316             filterChainMatch.sourcePorts(),
317             filterChainMatch.serverNames(),
318             filterChainMatch.transportProtocol()));
319       }
320     }
321     return expandedList;
322   }
323 
expandOnApplicationProtocols( Collection<FilterChainMatch> set)324   private static List<FilterChainMatch> expandOnApplicationProtocols(
325       Collection<FilterChainMatch> set) {
326     ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
327     for (FilterChainMatch filterChainMatch : set) {
328       if (filterChainMatch.applicationProtocols().isEmpty()) {
329         expandedList.add(filterChainMatch);
330       } else {
331         for (String applicationProtocol : filterChainMatch.applicationProtocols()) {
332           expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
333               filterChainMatch.prefixRanges(),
334               ImmutableList.of(applicationProtocol),
335               filterChainMatch.sourcePrefixRanges(),
336               filterChainMatch.connectionSourceType(),
337               filterChainMatch.sourcePorts(),
338               filterChainMatch.serverNames(),
339               filterChainMatch.transportProtocol()));
340         }
341       }
342     }
343     return expandedList;
344   }
345 
expandOnSourcePrefixRange( Collection<FilterChainMatch> set)346   private static List<FilterChainMatch> expandOnSourcePrefixRange(
347       Collection<FilterChainMatch> set) {
348     ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
349     for (FilterChainMatch filterChainMatch : set) {
350       if (filterChainMatch.sourcePrefixRanges().isEmpty()) {
351         expandedList.add(filterChainMatch);
352       } else {
353         for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) {
354           expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
355               filterChainMatch.prefixRanges(),
356               filterChainMatch.applicationProtocols(),
357               ImmutableList.of(cidrRange),
358               filterChainMatch.connectionSourceType(),
359               filterChainMatch.sourcePorts(),
360               filterChainMatch.serverNames(),
361               filterChainMatch.transportProtocol()));
362         }
363       }
364     }
365     return expandedList;
366   }
367 
expandOnSourcePorts(Collection<FilterChainMatch> set)368   private static List<FilterChainMatch> expandOnSourcePorts(Collection<FilterChainMatch> set) {
369     ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
370     for (FilterChainMatch filterChainMatch : set) {
371       if (filterChainMatch.sourcePorts().isEmpty()) {
372         expandedList.add(filterChainMatch);
373       } else {
374         for (Integer sourcePort : filterChainMatch.sourcePorts()) {
375           expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
376               filterChainMatch.prefixRanges(),
377               filterChainMatch.applicationProtocols(),
378               filterChainMatch.sourcePrefixRanges(),
379               filterChainMatch.connectionSourceType(),
380               ImmutableList.of(sourcePort),
381               filterChainMatch.serverNames(),
382               filterChainMatch.transportProtocol()));
383         }
384       }
385     }
386     return expandedList;
387   }
388 
expandOnServerNames(Collection<FilterChainMatch> set)389   private static List<FilterChainMatch> expandOnServerNames(Collection<FilterChainMatch> set) {
390     ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
391     for (FilterChainMatch filterChainMatch : set) {
392       if (filterChainMatch.serverNames().isEmpty()) {
393         expandedList.add(filterChainMatch);
394       } else {
395         for (String serverName : filterChainMatch.serverNames()) {
396           expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(),
397               filterChainMatch.prefixRanges(),
398               filterChainMatch.applicationProtocols(),
399               filterChainMatch.sourcePrefixRanges(),
400               filterChainMatch.connectionSourceType(),
401               filterChainMatch.sourcePorts(),
402               ImmutableList.of(serverName),
403               filterChainMatch.transportProtocol()));
404         }
405       }
406     }
407     return expandedList;
408   }
409 
parseFilterChainMatch( io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto)410   private static FilterChainMatch parseFilterChainMatch(
411       io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto)
412       throws ResourceInvalidException {
413     ImmutableList.Builder<CidrRange> prefixRanges = ImmutableList.builder();
414     ImmutableList.Builder<CidrRange> sourcePrefixRanges = ImmutableList.builder();
415     try {
416       for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) {
417         prefixRanges.add(
418             CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue()));
419       }
420       for (io.envoyproxy.envoy.config.core.v3.CidrRange range
421           : proto.getSourcePrefixRangesList()) {
422         sourcePrefixRanges.add(
423             CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue()));
424       }
425     } catch (UnknownHostException e) {
426       throw new ResourceInvalidException("Failed to create CidrRange", e);
427     }
428     ConnectionSourceType sourceType;
429     switch (proto.getSourceType()) {
430       case ANY:
431         sourceType = ConnectionSourceType.ANY;
432         break;
433       case EXTERNAL:
434         sourceType = ConnectionSourceType.EXTERNAL;
435         break;
436       case SAME_IP_OR_LOOPBACK:
437         sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK;
438         break;
439       default:
440         throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType());
441     }
442     return FilterChainMatch.create(
443         proto.getDestinationPort().getValue(),
444         prefixRanges.build(),
445         ImmutableList.copyOf(proto.getApplicationProtocolsList()),
446         sourcePrefixRanges.build(),
447         sourceType,
448         ImmutableList.copyOf(proto.getSourcePortsList()),
449         ImmutableList.copyOf(proto.getServerNamesList()),
450         proto.getTransportProtocol());
451   }
452 
453   @VisibleForTesting
parseHttpConnectionManager( HttpConnectionManager proto, FilterRegistry filterRegistry, boolean isForClient)454   static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
455       HttpConnectionManager proto, FilterRegistry filterRegistry,
456       boolean isForClient) throws ResourceInvalidException {
457     if (proto.getXffNumTrustedHops() != 0) {
458       throw new ResourceInvalidException(
459           "HttpConnectionManager with xff_num_trusted_hops unsupported");
460     }
461     if (!proto.getOriginalIpDetectionExtensionsList().isEmpty()) {
462       throw new ResourceInvalidException("HttpConnectionManager with "
463           + "original_ip_detection_extensions unsupported");
464     }
465     // Obtain max_stream_duration from Http Protocol Options.
466     long maxStreamDuration = 0;
467     if (proto.hasCommonHttpProtocolOptions()) {
468       HttpProtocolOptions options = proto.getCommonHttpProtocolOptions();
469       if (options.hasMaxStreamDuration()) {
470         maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration());
471       }
472     }
473 
474     // Parse http filters.
475     if (proto.getHttpFiltersList().isEmpty()) {
476       throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager.");
477     }
478     List<Filter.NamedFilterConfig> filterConfigs = new ArrayList<>();
479     Set<String> names = new HashSet<>();
480     for (int i = 0; i < proto.getHttpFiltersCount(); i++) {
481       io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
482           httpFilter = proto.getHttpFiltersList().get(i);
483       String filterName = httpFilter.getName();
484       if (!names.add(filterName)) {
485         throw new ResourceInvalidException(
486             "HttpConnectionManager contains duplicate HttpFilter: " + filterName);
487       }
488       StructOrError<Filter.FilterConfig> filterConfig =
489           parseHttpFilter(httpFilter, filterRegistry, isForClient);
490       if ((i == proto.getHttpFiltersCount() - 1)
491           && (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) {
492         throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: "
493             + filterName);
494       }
495       if (filterConfig == null) {
496         continue;
497       }
498       if (filterConfig.getErrorDetail() != null) {
499         throw new ResourceInvalidException(
500             "HttpConnectionManager contains invalid HttpFilter: "
501                 + filterConfig.getErrorDetail());
502       }
503       if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) {
504         throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: "
505             + filterName);
506       }
507       filterConfigs.add(new Filter.NamedFilterConfig(filterName, filterConfig.getStruct()));
508     }
509 
510     // Parse inlined RouteConfiguration or RDS.
511     if (proto.hasRouteConfig()) {
512       List<VirtualHost> virtualHosts = extractVirtualHosts(
513           proto.getRouteConfig(), filterRegistry);
514       return io.grpc.xds.HttpConnectionManager.forVirtualHosts(
515           maxStreamDuration, virtualHosts, filterConfigs);
516     }
517     if (proto.hasRds()) {
518       Rds rds = proto.getRds();
519       if (!rds.hasConfigSource()) {
520         throw new ResourceInvalidException(
521             "HttpConnectionManager contains invalid RDS: missing config_source");
522       }
523       if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) {
524         throw new ResourceInvalidException(
525             "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource");
526       }
527       return io.grpc.xds.HttpConnectionManager.forRdsName(
528           maxStreamDuration, rds.getRouteConfigName(), filterConfigs);
529     }
530     throw new ResourceInvalidException(
531         "HttpConnectionManager neither has inlined route_config nor RDS");
532   }
533 
534   // hard-coded: currently router config is the only terminal filter.
isTerminalFilter(Filter.FilterConfig filterConfig)535   private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) {
536     return RouterFilter.ROUTER_CONFIG.equals(filterConfig);
537   }
538 
539   @VisibleForTesting
540   @Nullable // Returns null if the filter is optional but not supported.
parseHttpFilter( io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter httpFilter, FilterRegistry filterRegistry, boolean isForClient)541   static StructOrError<Filter.FilterConfig> parseHttpFilter(
542       io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
543           httpFilter, FilterRegistry filterRegistry, boolean isForClient) {
544     String filterName = httpFilter.getName();
545     boolean isOptional = httpFilter.getIsOptional();
546     if (!httpFilter.hasTypedConfig()) {
547       if (isOptional) {
548         return null;
549       } else {
550         return StructOrError.fromError(
551             "HttpFilter [" + filterName + "] is not optional and has no typed config");
552       }
553     }
554     Message rawConfig = httpFilter.getTypedConfig();
555     String typeUrl = httpFilter.getTypedConfig().getTypeUrl();
556 
557     try {
558       if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) {
559         TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class);
560         typeUrl = typedStruct.getTypeUrl();
561         rawConfig = typedStruct.getValue();
562       } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) {
563         com.github.xds.type.v3.TypedStruct newTypedStruct =
564             httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class);
565         typeUrl = newTypedStruct.getTypeUrl();
566         rawConfig = newTypedStruct.getValue();
567       }
568     } catch (InvalidProtocolBufferException e) {
569       return StructOrError.fromError(
570           "HttpFilter [" + filterName + "] contains invalid proto: " + e);
571     }
572     Filter filter = filterRegistry.get(typeUrl);
573     if ((isForClient && !(filter instanceof Filter.ClientInterceptorBuilder))
574         || (!isForClient && !(filter instanceof Filter.ServerInterceptorBuilder))) {
575       if (isOptional) {
576         return null;
577       } else {
578         return StructOrError.fromError(
579             "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for "
580                 + (isForClient ? "client" : "server"));
581       }
582     }
583     ConfigOrError<? extends FilterConfig> filterConfig = filter.parseFilterConfig(rawConfig);
584     if (filterConfig.errorDetail != null) {
585       return StructOrError.fromError(
586           "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);
587     }
588     return StructOrError.fromStruct(filterConfig.config);
589   }
590 
591   @AutoValue
592   abstract static class LdsUpdate implements ResourceUpdate {
593     // Http level api listener configuration.
594     @Nullable
httpConnectionManager()595     abstract io.grpc.xds.HttpConnectionManager httpConnectionManager();
596 
597     // Tcp level listener configuration.
598     @Nullable
listener()599     abstract EnvoyServerProtoData.Listener listener();
600 
forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager)601     static LdsUpdate forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager) {
602       checkNotNull(httpConnectionManager, "httpConnectionManager");
603       return new AutoValue_XdsListenerResource_LdsUpdate(httpConnectionManager, null);
604     }
605 
forTcpListener(EnvoyServerProtoData.Listener listener)606     static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) {
607       checkNotNull(listener, "listener");
608       return new AutoValue_XdsListenerResource_LdsUpdate(null, listener);
609     }
610   }
611 }
612