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