// // Copyright 2018 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include "src/core/ext/xds/xds_listener.h" #include #include #include #include #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "envoy/config/core/v3/address.upb.h" #include "envoy/config/core/v3/base.upb.h" #include "envoy/config/core/v3/config_source.upb.h" #include "envoy/config/core/v3/protocol.upb.h" #include "envoy/config/listener/v3/api_listener.upb.h" #include "envoy/config/listener/v3/listener.upb.h" #include "envoy/config/listener/v3/listener.upbdefs.h" #include "envoy/config/listener/v3/listener_components.upb.h" #include "envoy/config/route/v3/route.upb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" #include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" #include "google/protobuf/any.upb.h" #include "google/protobuf/duration.upb.h" #include "google/protobuf/wrappers.upb.h" #include "upb/text_encode.h" #include "upb/upb.h" #include #include "src/core/ext/xds/xds_common_types.h" #include "src/core/ext/xds/xds_resource_type.h" #include "src/core/lib/address_utils/parse_address.h" #include "src/core/lib/address_utils/sockaddr_utils.h" #include "src/core/lib/debug/trace.h" #include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/gprpp/status_helper.h" #include "src/core/lib/iomgr/error.h" #include "src/core/lib/iomgr/sockaddr.h" #include "src/core/lib/json/json.h" #include "src/core/lib/transport/error_utils.h" namespace grpc_core { // // XdsListenerResource::DownstreamTlsContext // std::string XdsListenerResource::DownstreamTlsContext::ToString() const { return absl::StrFormat("common_tls_context=%s, require_client_certificate=%s", common_tls_context.ToString(), require_client_certificate ? "true" : "false"); } bool XdsListenerResource::DownstreamTlsContext::Empty() const { return common_tls_context.Empty(); } // // XdsListenerResource::HttpConnectionManager // std::string XdsListenerResource::HttpConnectionManager::ToString() const { std::vector contents; contents.push_back(absl::StrFormat( "route_config_name=%s", !route_config_name.empty() ? route_config_name.c_str() : "")); contents.push_back(absl::StrFormat("http_max_stream_duration=%s", http_max_stream_duration.ToString())); if (rds_update.has_value()) { contents.push_back( absl::StrFormat("rds_update=%s", rds_update->ToString())); } if (!http_filters.empty()) { std::vector filter_strings; for (const auto& http_filter : http_filters) { filter_strings.push_back(http_filter.ToString()); } contents.push_back(absl::StrCat("http_filters=[", absl::StrJoin(filter_strings, ", "), "]")); } return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } // // XdsListenerResource::HttpFilter // std::string XdsListenerResource::HttpConnectionManager::HttpFilter::ToString() const { return absl::StrCat("{name=", name, ", config=", config.ToString(), "}"); } // // XdsListenerResource::FilterChainData // std::string XdsListenerResource::FilterChainData::ToString() const { return absl::StrCat( "{downstream_tls_context=", downstream_tls_context.ToString(), " http_connection_manager=", http_connection_manager.ToString(), "}"); } // // XdsListenerResource::FilterChainMap::CidrRange // std::string XdsListenerResource::FilterChainMap::CidrRange::ToString() const { auto addr_str = grpc_sockaddr_to_string(&address, false); return absl::StrCat( "{address_prefix=", addr_str.ok() ? addr_str.value() : addr_str.status().ToString(), ", prefix_len=", prefix_len, "}"); } // // FilterChain // struct FilterChain { struct FilterChainMatch { uint32_t destination_port = 0; std::vector prefix_ranges; XdsListenerResource::FilterChainMap::ConnectionSourceType source_type = XdsListenerResource::FilterChainMap::ConnectionSourceType::kAny; std::vector source_prefix_ranges; std::vector source_ports; std::vector server_names; std::string transport_protocol; std::vector application_protocols; std::string ToString() const; } filter_chain_match; std::shared_ptr filter_chain_data; }; std::string FilterChain::FilterChainMatch::ToString() const { std::vector contents; if (destination_port != 0) { contents.push_back(absl::StrCat("destination_port=", destination_port)); } if (!prefix_ranges.empty()) { std::vector prefix_ranges_content; for (const auto& range : prefix_ranges) { prefix_ranges_content.push_back(range.ToString()); } contents.push_back(absl::StrCat( "prefix_ranges={", absl::StrJoin(prefix_ranges_content, ", "), "}")); } if (source_type == XdsListenerResource::FilterChainMap::ConnectionSourceType:: kSameIpOrLoopback) { contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); } else if (source_type == XdsListenerResource::FilterChainMap:: ConnectionSourceType::kExternal) { contents.push_back("source_type=EXTERNAL"); } if (!source_prefix_ranges.empty()) { std::vector source_prefix_ranges_content; for (const auto& range : source_prefix_ranges) { source_prefix_ranges_content.push_back(range.ToString()); } contents.push_back( absl::StrCat("source_prefix_ranges={", absl::StrJoin(source_prefix_ranges_content, ", "), "}")); } if (!source_ports.empty()) { contents.push_back( absl::StrCat("source_ports={", absl::StrJoin(source_ports, ", "), "}")); } if (!server_names.empty()) { contents.push_back( absl::StrCat("server_names={", absl::StrJoin(server_names, ", "), "}")); } if (!transport_protocol.empty()) { contents.push_back(absl::StrCat("transport_protocol=", transport_protocol)); } if (!application_protocols.empty()) { contents.push_back(absl::StrCat("application_protocols={", absl::StrJoin(application_protocols, ", "), "}")); } return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } // // XdsListenerResource::FilterChainMap // std::string XdsListenerResource::FilterChainMap::ToString() const { std::vector contents; for (const auto& destination_ip : destination_ip_vector) { for (int source_type = 0; source_type < 3; ++source_type) { for (const auto& source_ip : destination_ip.source_types_array[source_type]) { for (const auto& source_port_pair : source_ip.ports_map) { FilterChain::FilterChainMatch filter_chain_match; if (destination_ip.prefix_range.has_value()) { filter_chain_match.prefix_ranges.push_back( *destination_ip.prefix_range); } filter_chain_match.source_type = static_cast< XdsListenerResource::FilterChainMap::ConnectionSourceType>( source_type); if (source_ip.prefix_range.has_value()) { filter_chain_match.source_prefix_ranges.push_back( *source_ip.prefix_range); } if (source_port_pair.first != 0) { filter_chain_match.source_ports.push_back(source_port_pair.first); } contents.push_back(absl::StrCat( "{filter_chain_match=", filter_chain_match.ToString(), ", filter_chain=", source_port_pair.second.data->ToString(), "}")); } } } } return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } // // XdsListenerResource // std::string XdsListenerResource::ToString() const { std::vector contents; if (type == ListenerType::kTcpListener) { contents.push_back(absl::StrCat("address=", address)); contents.push_back( absl::StrCat("filter_chain_map=", filter_chain_map.ToString())); if (default_filter_chain.has_value()) { contents.push_back(absl::StrCat("default_filter_chain=", default_filter_chain->ToString())); } } else if (type == ListenerType::kHttpApiListener) { contents.push_back(absl::StrFormat("http_connection_manager=%s", http_connection_manager.ToString())); } return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } // // XdsListenerResourceType // namespace { void MaybeLogHttpConnectionManager( const XdsEncodingContext& context, const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* http_connection_manager_config) { if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_MessageDef* msg_type = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( context.symtab); char buf[10240]; upb_TextEncode(http_connection_manager_config, msg_type, nullptr, 0, buf, sizeof(buf)); gpr_log(GPR_DEBUG, "[xds_client %p] HttpConnectionManager: %s", context.client, buf); } } grpc_error_handle HttpConnectionManagerParse( bool is_client, const XdsEncodingContext& context, const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* http_connection_manager_proto, bool is_v2, XdsListenerResource::HttpConnectionManager* http_connection_manager) { MaybeLogHttpConnectionManager(context, http_connection_manager_proto); // NACK a non-zero `xff_num_trusted_hops` and a `non-empty // original_ip_detection_extensions` as mentioned in // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops( http_connection_manager_proto) != 0) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "'xff_num_trusted_hops' must be zero"); } if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_original_ip_detection_extensions( http_connection_manager_proto)) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "'original_ip_detection_extensions' must be empty"); } // Obtain max_stream_duration from Http Protocol Options. const envoy_config_core_v3_HttpProtocolOptions* options = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_common_http_protocol_options( http_connection_manager_proto); if (options != nullptr) { const google_protobuf_Duration* duration = envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options); if (duration != nullptr) { http_connection_manager->http_max_stream_duration = ParseDuration(duration); } } // Parse filters. if (!is_v2) { size_t num_filters = 0; const auto* http_filters = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( http_connection_manager_proto, &num_filters); std::set names_seen; for (size_t i = 0; i < num_filters; ++i) { const auto* http_filter = http_filters[i]; absl::string_view name = UpbStringToAbsl( envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( http_filter)); if (name.empty()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("empty filter name at index ", i)); } if (names_seen.find(name) != names_seen.end()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("duplicate HTTP filter name: ", name)); } names_seen.insert(name); const bool is_optional = envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( http_filter); const google_protobuf_Any* any = envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( http_filter); if (any == nullptr) { if (is_optional) continue; return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("no filter config specified for filter name ", name)); } auto filter_type = ExtractExtensionTypeName(context, any); if (!filter_type.ok()) { return absl_status_to_grpc_error(filter_type.status()); } const XdsHttpFilterImpl* filter_impl = XdsHttpFilterRegistry::GetFilterForType(filter_type->type); if (filter_impl == nullptr) { if (is_optional) continue; return GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat( "no filter registered for config type ", filter_type->type)); } if ((is_client && !filter_impl->IsSupportedOnClients()) || (!is_client && !filter_impl->IsSupportedOnServers())) { if (is_optional) continue; return GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrFormat( "Filter %s is not supported on %s", filter_type->type, is_client ? "clients" : "servers")); } absl::StatusOr filter_config = filter_impl->GenerateFilterConfig(google_protobuf_Any_value(any), context.arena); if (!filter_config.ok()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat( "filter config for type ", filter_type->type, " failed to parse: ", StatusToString(filter_config.status()))); } http_connection_manager->http_filters.emplace_back( XdsListenerResource::HttpConnectionManager::HttpFilter{ std::string(name), std::move(*filter_config)}); } if (http_connection_manager->http_filters.empty()) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Expected at least one HTTP filter"); } // Make sure that the last filter is terminal and non-last filters are // non-terminal. Note that this check is being performed in a separate loop // to take care of the case where there are two terminal filters in the list // out of which only one gets added in the final list. for (const auto& http_filter : http_connection_manager->http_filters) { const XdsHttpFilterImpl* filter_impl = XdsHttpFilterRegistry::GetFilterForType( http_filter.config.config_proto_type_name); if (&http_filter != &http_connection_manager->http_filters.back()) { // Filters before the last filter must not be terminal. if (filter_impl->IsTerminalFilter()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("terminal filter for config type ", http_filter.config.config_proto_type_name, " must be the last filter in the chain")); } } else { // The last filter must be terminal. if (!filter_impl->IsTerminalFilter()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("non-terminal filter for config type ", http_filter.config.config_proto_type_name, " is the last filter in the chain")); } } } } else { // If using a v2 config, we just hard-code a list containing only the // router filter without actually looking at the config. This ensures // that the right thing happens in the xds resolver without having // to expose whether the resource we received was v2 or v3. http_connection_manager->http_filters.emplace_back( XdsListenerResource::HttpConnectionManager::HttpFilter{ "router", {kXdsHttpRouterFilterConfigName, Json()}}); } // Guarding parsing of RouteConfig on the server side with the environmental // variable since that's the first feature on the server side that will be // using this. if (is_client || XdsRbacEnabled()) { // Found inlined route_config. Parse it to find the cluster_name. if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_route_config( http_connection_manager_proto)) { const envoy_config_route_v3_RouteConfiguration* route_config = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_route_config( http_connection_manager_proto); XdsRouteConfigResource rds_update; grpc_error_handle error = XdsRouteConfigResource::Parse(context, route_config, &rds_update); if (!GRPC_ERROR_IS_NONE(error)) return error; http_connection_manager->rds_update = std::move(rds_update); return GRPC_ERROR_NONE; } // Validate that RDS must be used to get the route_config dynamically. const envoy_extensions_filters_network_http_connection_manager_v3_Rds* rds = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_rds( http_connection_manager_proto); if (rds == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "HttpConnectionManager neither has inlined route_config nor RDS."); } // Check that the ConfigSource specifies ADS. const envoy_config_core_v3_ConfigSource* config_source = envoy_extensions_filters_network_http_connection_manager_v3_Rds_config_source( rds); if (config_source == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "HttpConnectionManager missing config_source for RDS."); } if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && !envoy_config_core_v3_ConfigSource_has_self(config_source)) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "HttpConnectionManager ConfigSource for RDS does not specify ADS " "or SELF."); } // Get the route_config_name. http_connection_manager->route_config_name = UpbStringToStdString( envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( rds)); } return GRPC_ERROR_NONE; } grpc_error_handle LdsResourceParseClient( const XdsEncodingContext& context, const envoy_config_listener_v3_ApiListener* api_listener, bool is_v2, XdsListenerResource* lds_update) { lds_update->type = XdsListenerResource::ListenerType::kHttpApiListener; const upb_StringView encoded_api_listener = google_protobuf_Any_value( envoy_config_listener_v3_ApiListener_api_listener(api_listener)); const auto* http_connection_manager = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( encoded_api_listener.data, encoded_api_listener.size, context.arena); if (http_connection_manager == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Could not parse HttpConnectionManager config from ApiListener"); } return HttpConnectionManagerParse(true /* is_client */, context, http_connection_manager, is_v2, &lds_update->http_connection_manager); } grpc_error_handle DownstreamTlsContextParse( const XdsEncodingContext& context, const envoy_config_core_v3_TransportSocket* transport_socket, XdsListenerResource::DownstreamTlsContext* downstream_tls_context) { absl::string_view name = UpbStringToAbsl( envoy_config_core_v3_TransportSocket_name(transport_socket)); if (name != "envoy.transport_sockets.tls") { return GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("Unrecognized transport socket: ", name)); } auto* typed_config = envoy_config_core_v3_TransportSocket_typed_config(transport_socket); std::vector errors; if (typed_config != nullptr) { const upb_StringView encoded_downstream_tls_context = google_protobuf_Any_value(typed_config); auto* downstream_tls_context_proto = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse( encoded_downstream_tls_context.data, encoded_downstream_tls_context.size, context.arena); if (downstream_tls_context_proto == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Can't decode downstream tls context."); } auto* common_tls_context = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_common_tls_context( downstream_tls_context_proto); if (common_tls_context != nullptr) { grpc_error_handle error = CommonTlsContext::Parse(context, common_tls_context, &downstream_tls_context->common_tls_context); if (!GRPC_ERROR_IS_NONE(error)) errors.push_back(error); } auto* require_client_certificate = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_client_certificate( downstream_tls_context_proto); if (require_client_certificate != nullptr) { downstream_tls_context->require_client_certificate = google_protobuf_BoolValue_value(require_client_certificate); } auto* require_sni = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni( downstream_tls_context_proto); if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) { errors.push_back( GRPC_ERROR_CREATE_FROM_STATIC_STRING("require_sni: unsupported")); } if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy( downstream_tls_context_proto) != envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "ocsp_staple_policy: Only LENIENT_STAPLING supported")); } } if (downstream_tls_context->common_tls_context .tls_certificate_provider_instance.instance_name.empty()) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "TLS configuration provided but no " "tls_certificate_provider_instance found.")); } if (downstream_tls_context->require_client_certificate && downstream_tls_context->common_tls_context.certificate_validation_context .ca_certificate_provider_instance.instance_name.empty()) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "TLS configuration requires client certificates but no certificate " "provider instance specified for validation.")); } if (!downstream_tls_context->common_tls_context.certificate_validation_context .match_subject_alt_names.empty()) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "match_subject_alt_names not supported on servers")); } return GRPC_ERROR_CREATE_FROM_VECTOR("Error parsing DownstreamTlsContext", &errors); } grpc_error_handle CidrRangeParse( const envoy_config_core_v3_CidrRange* cidr_range_proto, XdsListenerResource::FilterChainMap::CidrRange* cidr_range) { std::string address_prefix = UpbStringToStdString( envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto)); grpc_error_handle error = grpc_string_to_sockaddr(&cidr_range->address, address_prefix.c_str(), 0); if (!GRPC_ERROR_IS_NONE(error)) return error; cidr_range->prefix_len = 0; auto* prefix_len_proto = envoy_config_core_v3_CidrRange_prefix_len(cidr_range_proto); if (prefix_len_proto != nullptr) { cidr_range->prefix_len = std::min( google_protobuf_UInt32Value_value(prefix_len_proto), (reinterpret_cast(cidr_range->address.addr)) ->sa_family == GRPC_AF_INET ? uint32_t(32) : uint32_t(128)); } // Normalize the network address by masking it with prefix_len grpc_sockaddr_mask_bits(&cidr_range->address, cidr_range->prefix_len); return GRPC_ERROR_NONE; } grpc_error_handle FilterChainMatchParse( const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto, FilterChain::FilterChainMatch* filter_chain_match) { auto* destination_port = envoy_config_listener_v3_FilterChainMatch_destination_port( filter_chain_match_proto); if (destination_port != nullptr) { filter_chain_match->destination_port = google_protobuf_UInt32Value_value(destination_port); } size_t size = 0; auto* prefix_ranges = envoy_config_listener_v3_FilterChainMatch_prefix_ranges( filter_chain_match_proto, &size); filter_chain_match->prefix_ranges.reserve(size); for (size_t i = 0; i < size; i++) { XdsListenerResource::FilterChainMap::CidrRange cidr_range; grpc_error_handle error = CidrRangeParse(prefix_ranges[i], &cidr_range); if (!GRPC_ERROR_IS_NONE(error)) return error; filter_chain_match->prefix_ranges.push_back(cidr_range); } filter_chain_match->source_type = static_cast( envoy_config_listener_v3_FilterChainMatch_source_type( filter_chain_match_proto)); auto* source_prefix_ranges = envoy_config_listener_v3_FilterChainMatch_source_prefix_ranges( filter_chain_match_proto, &size); filter_chain_match->source_prefix_ranges.reserve(size); for (size_t i = 0; i < size; i++) { XdsListenerResource::FilterChainMap::CidrRange cidr_range; grpc_error_handle error = CidrRangeParse(source_prefix_ranges[i], &cidr_range); if (!GRPC_ERROR_IS_NONE(error)) return error; filter_chain_match->source_prefix_ranges.push_back(cidr_range); } auto* source_ports = envoy_config_listener_v3_FilterChainMatch_source_ports( filter_chain_match_proto, &size); filter_chain_match->source_ports.reserve(size); for (size_t i = 0; i < size; i++) { filter_chain_match->source_ports.push_back(source_ports[i]); } auto* server_names = envoy_config_listener_v3_FilterChainMatch_server_names( filter_chain_match_proto, &size); for (size_t i = 0; i < size; i++) { filter_chain_match->server_names.push_back( UpbStringToStdString(server_names[i])); } filter_chain_match->transport_protocol = UpbStringToStdString( envoy_config_listener_v3_FilterChainMatch_transport_protocol( filter_chain_match_proto)); auto* application_protocols = envoy_config_listener_v3_FilterChainMatch_application_protocols( filter_chain_match_proto, &size); for (size_t i = 0; i < size; i++) { filter_chain_match->application_protocols.push_back( UpbStringToStdString(application_protocols[i])); } return GRPC_ERROR_NONE; } grpc_error_handle FilterChainParse( const XdsEncodingContext& context, const envoy_config_listener_v3_FilterChain* filter_chain_proto, bool is_v2, FilterChain* filter_chain) { std::vector errors; auto* filter_chain_match = envoy_config_listener_v3_FilterChain_filter_chain_match( filter_chain_proto); if (filter_chain_match != nullptr) { grpc_error_handle error = FilterChainMatchParse( filter_chain_match, &filter_chain->filter_chain_match); if (!GRPC_ERROR_IS_NONE(error)) errors.push_back(error); } filter_chain->filter_chain_data = std::make_shared(); // Parse the filters list. Currently we only support HttpConnectionManager. size_t size = 0; auto* filters = envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); if (size != 1) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "FilterChain should have exactly one filter: HttpConnectionManager; no " "other filter is supported at the moment")); } else { auto* typed_config = envoy_config_listener_v3_Filter_typed_config(filters[0]); if (typed_config == nullptr) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "No typed_config found in filter.")); } else { absl::string_view type_url = UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)); if (type_url != "type.googleapis.com/" "envoy.extensions.filters.network.http_connection_manager.v3." "HttpConnectionManager") { errors.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING( absl::StrCat("Unsupported filter type ", type_url))); } else { const upb_StringView encoded_http_connection_manager = google_protobuf_Any_value(typed_config); const auto* http_connection_manager = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( encoded_http_connection_manager.data, encoded_http_connection_manager.size, context.arena); if (http_connection_manager == nullptr) { errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Could not parse HttpConnectionManager config from filter " "typed_config")); } else { grpc_error_handle error = HttpConnectionManagerParse( false /* is_client */, context, http_connection_manager, is_v2, &filter_chain->filter_chain_data->http_connection_manager); if (!GRPC_ERROR_IS_NONE(error)) errors.push_back(error); } } } } auto* transport_socket = envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto); if (transport_socket != nullptr) { grpc_error_handle error = DownstreamTlsContextParse( context, transport_socket, &filter_chain->filter_chain_data->downstream_tls_context); if (!GRPC_ERROR_IS_NONE(error)) errors.push_back(error); } return GRPC_ERROR_CREATE_FROM_VECTOR("Error parsing FilterChain", &errors); } grpc_error_handle AddressParse( const envoy_config_core_v3_Address* address_proto, std::string* address) { const auto* socket_address = envoy_config_core_v3_Address_socket_address(address_proto); if (socket_address == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Address does not have socket_address"); } if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != envoy_config_core_v3_SocketAddress_TCP) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "SocketAddress protocol is not TCP"); } uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); if (port > 65535) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid port"); } *address = JoinHostPort( UpbStringToAbsl( envoy_config_core_v3_SocketAddress_address(socket_address)), port); return GRPC_ERROR_NONE; } // An intermediate map for filter chains that we create to validate the list of // filter chains received from the control plane and to finally create // XdsListenerResource::FilterChainMap struct InternalFilterChainMap { using SourceIpMap = std::map; using ConnectionSourceTypesArray = std::array; struct DestinationIp { absl::optional prefix_range; bool transport_protocol_raw_buffer_provided = false; ConnectionSourceTypesArray source_types_array; }; using DestinationIpMap = std::map; DestinationIpMap destination_ip_map; }; grpc_error_handle AddFilterChainDataForSourcePort( const FilterChain& filter_chain, XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, uint32_t port) { auto insert_result = ports_map->emplace( port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{ filter_chain.filter_chain_data}); if (!insert_result.second) { return GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat( "Duplicate matching rules detected when adding filter chain: ", filter_chain.filter_chain_match.ToString())); } return GRPC_ERROR_NONE; } grpc_error_handle AddFilterChainDataForSourcePorts( const FilterChain& filter_chain, XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map) { if (filter_chain.filter_chain_match.source_ports.empty()) { return AddFilterChainDataForSourcePort(filter_chain, ports_map, 0); } else { for (uint32_t port : filter_chain.filter_chain_match.source_ports) { grpc_error_handle error = AddFilterChainDataForSourcePort(filter_chain, ports_map, port); if (!GRPC_ERROR_IS_NONE(error)) return error; } } return GRPC_ERROR_NONE; } grpc_error_handle AddFilterChainDataForSourceIpRange( const FilterChain& filter_chain, InternalFilterChainMap::SourceIpMap* source_ip_map) { if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) { auto insert_result = source_ip_map->emplace( "", XdsListenerResource::FilterChainMap::SourceIp()); return AddFilterChainDataForSourcePorts( filter_chain, &insert_result.first->second.ports_map); } else { for (const auto& prefix_range : filter_chain.filter_chain_match.source_prefix_ranges) { auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); if (!addr_str.ok()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING(addr_str.status().ToString()); } auto insert_result = source_ip_map->emplace( absl::StrCat(*addr_str, "/", prefix_range.prefix_len), XdsListenerResource::FilterChainMap::SourceIp()); if (insert_result.second) { insert_result.first->second.prefix_range.emplace(prefix_range); } grpc_error_handle error = AddFilterChainDataForSourcePorts( filter_chain, &insert_result.first->second.ports_map); if (!GRPC_ERROR_IS_NONE(error)) return error; } } return GRPC_ERROR_NONE; } grpc_error_handle AddFilterChainDataForSourceType( const FilterChain& filter_chain, InternalFilterChainMap::DestinationIp* destination_ip) { GPR_ASSERT(static_cast( filter_chain.filter_chain_match.source_type) < 3); return AddFilterChainDataForSourceIpRange( filter_chain, &destination_ip->source_types_array[static_cast( filter_chain.filter_chain_match.source_type)]); } grpc_error_handle AddFilterChainDataForApplicationProtocols( const FilterChain& filter_chain, InternalFilterChainMap::DestinationIp* destination_ip) { // Only allow filter chains that do not mention application protocols if (!filter_chain.filter_chain_match.application_protocols.empty()) { return GRPC_ERROR_NONE; } return AddFilterChainDataForSourceType(filter_chain, destination_ip); } grpc_error_handle AddFilterChainDataForTransportProtocol( const FilterChain& filter_chain, InternalFilterChainMap::DestinationIp* destination_ip) { const std::string& transport_protocol = filter_chain.filter_chain_match.transport_protocol; // Only allow filter chains with no transport protocol or "raw_buffer" if (!transport_protocol.empty() && transport_protocol != "raw_buffer") { return GRPC_ERROR_NONE; } // If for this configuration, we've already seen filter chains that mention // the transport protocol as "raw_buffer", we will never match filter chains // that do not mention it. if (destination_ip->transport_protocol_raw_buffer_provided && transport_protocol.empty()) { return GRPC_ERROR_NONE; } if (!transport_protocol.empty() && !destination_ip->transport_protocol_raw_buffer_provided) { destination_ip->transport_protocol_raw_buffer_provided = true; // Clear out the previous entries if any since those entries did not mention // "raw_buffer" destination_ip->source_types_array = InternalFilterChainMap::ConnectionSourceTypesArray(); } return AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip); } grpc_error_handle AddFilterChainDataForServerNames( const FilterChain& filter_chain, InternalFilterChainMap::DestinationIp* destination_ip) { // Don't continue adding filter chains with server names mentioned if (!filter_chain.filter_chain_match.server_names.empty()) { return GRPC_ERROR_NONE; } return AddFilterChainDataForTransportProtocol(filter_chain, destination_ip); } grpc_error_handle AddFilterChainDataForDestinationIpRange( const FilterChain& filter_chain, InternalFilterChainMap::DestinationIpMap* destination_ip_map) { if (filter_chain.filter_chain_match.prefix_ranges.empty()) { auto insert_result = destination_ip_map->emplace( "", InternalFilterChainMap::DestinationIp()); return AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second); } else { for (const auto& prefix_range : filter_chain.filter_chain_match.prefix_ranges) { auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); if (!addr_str.ok()) { return GRPC_ERROR_CREATE_FROM_CPP_STRING(addr_str.status().ToString()); } auto insert_result = destination_ip_map->emplace( absl::StrCat(*addr_str, "/", prefix_range.prefix_len), InternalFilterChainMap::DestinationIp()); if (insert_result.second) { insert_result.first->second.prefix_range.emplace(prefix_range); } grpc_error_handle error = AddFilterChainDataForServerNames( filter_chain, &insert_result.first->second); if (!GRPC_ERROR_IS_NONE(error)) return error; } } return GRPC_ERROR_NONE; } XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( InternalFilterChainMap* internal_filter_chain_map) { XdsListenerResource::FilterChainMap filter_chain_map; for (auto& destination_ip_pair : internal_filter_chain_map->destination_ip_map) { XdsListenerResource::FilterChainMap::DestinationIp destination_ip; destination_ip.prefix_range = destination_ip_pair.second.prefix_range; for (int i = 0; i < 3; i++) { auto& source_ip_map = destination_ip_pair.second.source_types_array[i]; for (auto& source_ip_pair : source_ip_map) { destination_ip.source_types_array[i].push_back( std::move(source_ip_pair.second)); } } filter_chain_map.destination_ip_vector.push_back(std::move(destination_ip)); } return filter_chain_map; } grpc_error_handle BuildFilterChainMap( const std::vector& filter_chains, XdsListenerResource::FilterChainMap* filter_chain_map) { InternalFilterChainMap internal_filter_chain_map; for (const auto& filter_chain : filter_chains) { // Discard filter chain entries that specify destination port if (filter_chain.filter_chain_match.destination_port != 0) continue; grpc_error_handle error = AddFilterChainDataForDestinationIpRange( filter_chain, &internal_filter_chain_map.destination_ip_map); if (!GRPC_ERROR_IS_NONE(error)) return error; } *filter_chain_map = BuildFromInternalFilterChainMap(&internal_filter_chain_map); return GRPC_ERROR_NONE; } grpc_error_handle LdsResourceParseServer( const XdsEncodingContext& context, const envoy_config_listener_v3_Listener* listener, bool is_v2, XdsListenerResource* lds_update) { lds_update->type = XdsListenerResource::ListenerType::kTcpListener; grpc_error_handle error = AddressParse(envoy_config_listener_v3_Listener_address(listener), &lds_update->address); if (!GRPC_ERROR_IS_NONE(error)) return error; const auto* use_original_dst = envoy_config_listener_v3_Listener_use_original_dst(listener); if (use_original_dst != nullptr) { if (google_protobuf_BoolValue_value(use_original_dst)) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Field \'use_original_dst\' is not supported."); } } size_t size = 0; auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains(listener, &size); std::vector parsed_filter_chains; parsed_filter_chains.reserve(size); for (size_t i = 0; i < size; i++) { FilterChain filter_chain; error = FilterChainParse(context, filter_chains[i], is_v2, &filter_chain); if (!GRPC_ERROR_IS_NONE(error)) return error; parsed_filter_chains.push_back(std::move(filter_chain)); } error = BuildFilterChainMap(parsed_filter_chains, &lds_update->filter_chain_map); if (!GRPC_ERROR_IS_NONE(error)) return error; auto* default_filter_chain = envoy_config_listener_v3_Listener_default_filter_chain(listener); if (default_filter_chain != nullptr) { FilterChain filter_chain; error = FilterChainParse(context, default_filter_chain, is_v2, &filter_chain); if (!GRPC_ERROR_IS_NONE(error)) return error; if (filter_chain.filter_chain_data != nullptr) { lds_update->default_filter_chain = std::move(*filter_chain.filter_chain_data); } } if (size == 0 && default_filter_chain == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING("No filter chain provided."); } return GRPC_ERROR_NONE; } grpc_error_handle LdsResourceParse( const XdsEncodingContext& context, const envoy_config_listener_v3_Listener* listener, bool is_v2, XdsListenerResource* lds_update) { // Check whether it's a client or server listener. const envoy_config_listener_v3_ApiListener* api_listener = envoy_config_listener_v3_Listener_api_listener(listener); const envoy_config_core_v3_Address* address = envoy_config_listener_v3_Listener_address(listener); // TODO(roth): Re-enable the following check once // github.com/istio/istio/issues/38914 is resolved. // if (api_listener != nullptr && address != nullptr) { // return GRPC_ERROR_CREATE_FROM_STATIC_STRING( // "Listener has both address and ApiListener"); // } if (api_listener == nullptr && address == nullptr) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Listener has neither address nor ApiListener"); } // Validate Listener fields. grpc_error_handle error = GRPC_ERROR_NONE; if (api_listener != nullptr) { error = LdsResourceParseClient(context, api_listener, is_v2, lds_update); } else { error = LdsResourceParseServer(context, listener, is_v2, lds_update); } return error; } void MaybeLogListener(const XdsEncodingContext& context, const envoy_config_listener_v3_Listener* listener) { if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_MessageDef* msg_type = envoy_config_listener_v3_Listener_getmsgdef(context.symtab); char buf[10240]; upb_TextEncode(listener, msg_type, nullptr, 0, buf, sizeof(buf)); gpr_log(GPR_DEBUG, "[xds_client %p] Listener: %s", context.client, buf); } } } // namespace absl::StatusOr XdsListenerResourceType::Decode( const XdsEncodingContext& context, absl::string_view serialized_resource, bool is_v2) const { // Parse serialized proto. auto* resource = envoy_config_listener_v3_Listener_parse( serialized_resource.data(), serialized_resource.size(), context.arena); if (resource == nullptr) { return absl::InvalidArgumentError("Can't parse Listener resource."); } MaybeLogListener(context, resource); // Validate resource. DecodeResult result; result.name = UpbStringToStdString(envoy_config_listener_v3_Listener_name(resource)); auto listener_data = absl::make_unique(); grpc_error_handle error = LdsResourceParse(context, resource, is_v2, &listener_data->resource); if (!GRPC_ERROR_IS_NONE(error)) { std::string error_str = grpc_error_std_string(error); GRPC_ERROR_UNREF(error); if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) { gpr_log(GPR_ERROR, "[xds_client %p] invalid Listener %s: %s", context.client, result.name.c_str(), error_str.c_str()); } result.resource = absl::InvalidArgumentError(error_str); } else { if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) { gpr_log(GPR_INFO, "[xds_client %p] parsed Listener %s: %s", context.client, result.name.c_str(), listener_data->resource.ToString().c_str()); } result.resource = std::move(listener_data); } return std::move(result); } } // namespace grpc_core