diff options
Diffstat (limited to 'grpc/src/core/ext/xds/xds_api.cc')
-rw-r--r-- | grpc/src/core/ext/xds/xds_api.cc | 2853 |
1 files changed, 2172 insertions, 681 deletions
diff --git a/grpc/src/core/ext/xds/xds_api.cc b/grpc/src/core/ext/xds/xds_api.cc index e9403c2c..e51bc07c 100644 --- a/grpc/src/core/ext/xds/xds_api.cc +++ b/grpc/src/core/ext/xds/xds_api.cc @@ -28,26 +28,13 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" - -#include "upb/upb.hpp" - -#include <grpc/impl/codegen/log.h> -#include <grpc/support/alloc.h> -#include <grpc/support/string_util.h> - -#include "src/core/ext/xds/xds_api.h" -#include "src/core/lib/gpr/env.h" -#include "src/core/lib/gpr/string.h" -#include "src/core/lib/gpr/useful.h" -#include "src/core/lib/iomgr/error.h" -#include "src/core/lib/iomgr/sockaddr_utils.h" -#include "src/core/lib/slice/slice_utils.h" - +#include "envoy/admin/v3/config_dump.upb.h" #include "envoy/config/cluster/v3/circuit_breaker.upb.h" #include "envoy/config/cluster/v3/cluster.upb.h" #include "envoy/config/cluster/v3/cluster.upbdefs.h" #include "envoy/config/core/v3/address.upb.h" #include "envoy/config/core/v3/base.upb.h" +#include "envoy/config/core/v3/base.upbdefs.h" #include "envoy/config/core/v3/config_source.upb.h" #include "envoy/config/core/v3/health_check.upb.h" #include "envoy/config/core/v3/protocol.upb.h" @@ -57,12 +44,19 @@ #include "envoy/config/endpoint/v3/load_report.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/config/route/v3/route.upbdefs.h" #include "envoy/config/route/v3/route_components.upb.h" +#include "envoy/config/route/v3/route_components.upbdefs.h" +#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h" +#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.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/common.upb.h" #include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h" #include "envoy/service/cluster/v3/cds.upb.h" #include "envoy/service/cluster/v3/cds.upbdefs.h" #include "envoy/service/discovery/v3/discovery.upb.h" @@ -74,6 +68,8 @@ #include "envoy/service/load_stats/v3/lrs.upbdefs.h" #include "envoy/service/route/v3/rds.upb.h" #include "envoy/service/route/v3/rds.upbdefs.h" +#include "envoy/service/status/v3/csds.upb.h" +#include "envoy/service/status/v3/csds.upbdefs.h" #include "envoy/type/matcher/v3/regex.upb.h" #include "envoy/type/matcher/v3/string.upb.h" #include "envoy/type/v3/percent.upb.h" @@ -81,18 +77,49 @@ #include "google/protobuf/any.upb.h" #include "google/protobuf/duration.upb.h" #include "google/protobuf/struct.upb.h" +#include "google/protobuf/timestamp.upb.h" #include "google/protobuf/wrappers.upb.h" #include "google/rpc/status.upb.h" +#include "udpa/type/v1/typed_struct.upb.h" #include "upb/text_encode.h" #include "upb/upb.h" +#include "upb/upb.hpp" + +#include <grpc/impl/codegen/log.h> +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> + +#include "src/core/ext/xds/xds_api.h" +#include "src/core/lib/address_utils/sockaddr_utils.h" +#include "src/core/lib/gpr/env.h" +#include "src/core/lib/gpr/string.h" +#include "src/core/lib/gpr/useful.h" +#include "src/core/lib/gprpp/host_port.h" +#include "src/core/lib/iomgr/error.h" +#include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/iomgr/socket_utils.h" +#include "src/core/lib/slice/slice_utils.h" namespace grpc_core { -// TODO (donnadionne): Check to see if timeout is enabled, this will be -// removed once timeout feature is fully integration-tested and enabled by +// TODO(donnadionne): Check to see if cluster types aggregate_cluster and +// logical_dns are enabled, this will be +// removed once the cluster types are fully integration-tested and enabled by +// default. +bool XdsAggregateAndLogicalDnsClusterEnabled() { + char* value = gpr_getenv( + "GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER"); + bool parsed_value; + bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value); + gpr_free(value); + return parse_succeeded && parsed_value; +} + +// TODO(donnadionne): Check to see if ring hash policy is enabled, this will be +// removed once ring hash policy is fully integration-tested and enabled by // default. -bool XdsTimeoutEnabled() { - char* value = gpr_getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT"); +bool XdsRingHashEnabled() { + char* value = gpr_getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH"); bool parsed_value; bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value); gpr_free(value); @@ -111,160 +138,80 @@ bool XdsSecurityEnabled() { } // -// XdsApi::Route::Matchers::PathMatcher +// XdsApi::Route::HashPolicy // -XdsApi::Route::Matchers::PathMatcher::PathMatcher(const PathMatcher& other) - : type(other.type), case_sensitive(other.case_sensitive) { - if (type == PathMatcherType::REGEX) { - RE2::Options options; - options.set_case_sensitive(case_sensitive); - regex_matcher = - absl::make_unique<RE2>(other.regex_matcher->pattern(), options); - } else { - string_matcher = other.string_matcher; +XdsApi::Route::HashPolicy::HashPolicy(const HashPolicy& other) + : type(other.type), + header_name(other.header_name), + regex_substitution(other.regex_substitution) { + if (other.regex != nullptr) { + regex = + absl::make_unique<RE2>(other.regex->pattern(), other.regex->options()); } } -XdsApi::Route::Matchers::PathMatcher& XdsApi::Route::Matchers::PathMatcher:: -operator=(const PathMatcher& other) { +XdsApi::Route::HashPolicy& XdsApi::Route::HashPolicy::operator=( + const HashPolicy& other) { type = other.type; - case_sensitive = other.case_sensitive; - if (type == PathMatcherType::REGEX) { - RE2::Options options; - options.set_case_sensitive(case_sensitive); - regex_matcher = - absl::make_unique<RE2>(other.regex_matcher->pattern(), options); - } else { - string_matcher = other.string_matcher; + header_name = other.header_name; + if (other.regex != nullptr) { + regex = + absl::make_unique<RE2>(other.regex->pattern(), other.regex->options()); } + regex_substitution = other.regex_substitution; return *this; } -bool XdsApi::Route::Matchers::PathMatcher::operator==( - const PathMatcher& other) const { - if (type != other.type) return false; - if (case_sensitive != other.case_sensitive) return false; - if (type == PathMatcherType::REGEX) { - // Should never be null. - if (regex_matcher == nullptr || other.regex_matcher == nullptr) { - return false; - } - return regex_matcher->pattern() == other.regex_matcher->pattern(); - } - return string_matcher == other.string_matcher; -} - -std::string XdsApi::Route::Matchers::PathMatcher::ToString() const { - std::string path_type_string; - switch (type) { - case PathMatcherType::PATH: - path_type_string = "path match"; - break; - case PathMatcherType::PREFIX: - path_type_string = "prefix match"; - break; - case PathMatcherType::REGEX: - path_type_string = "regex match"; - break; - default: - break; - } - return absl::StrFormat("Path %s:%s%s", path_type_string, - type == PathMatcherType::REGEX - ? regex_matcher->pattern() - : string_matcher, - case_sensitive ? "" : "[case_sensitive=false]"); -} - -// -// XdsApi::Route::Matchers::HeaderMatcher -// - -XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcher( - const HeaderMatcher& other) - : name(other.name), type(other.type), invert_match(other.invert_match) { - switch (type) { - case HeaderMatcherType::REGEX: - regex_match = absl::make_unique<RE2>(other.regex_match->pattern()); - break; - case HeaderMatcherType::RANGE: - range_start = other.range_start; - range_end = other.range_end; - break; - case HeaderMatcherType::PRESENT: - present_match = other.present_match; - break; - default: - string_matcher = other.string_matcher; - } -} +XdsApi::Route::HashPolicy::HashPolicy(HashPolicy&& other) noexcept + : type(other.type), + header_name(std::move(other.header_name)), + regex(std::move(other.regex)), + regex_substitution(std::move(other.regex_substitution)) {} -XdsApi::Route::Matchers::HeaderMatcher& XdsApi::Route::Matchers::HeaderMatcher:: -operator=(const HeaderMatcher& other) { - name = other.name; +XdsApi::Route::HashPolicy& XdsApi::Route::HashPolicy::operator=( + HashPolicy&& other) noexcept { type = other.type; - invert_match = other.invert_match; - switch (type) { - case HeaderMatcherType::REGEX: - regex_match = absl::make_unique<RE2>(other.regex_match->pattern()); - break; - case HeaderMatcherType::RANGE: - range_start = other.range_start; - range_end = other.range_end; - break; - case HeaderMatcherType::PRESENT: - present_match = other.present_match; - break; - default: - string_matcher = other.string_matcher; - } + header_name = std::move(other.header_name); + regex = std::move(other.regex); + regex_substitution = std::move(other.regex_substitution); return *this; } -bool XdsApi::Route::Matchers::HeaderMatcher::operator==( - const HeaderMatcher& other) const { - if (name != other.name) return false; +bool XdsApi::Route::HashPolicy::HashPolicy::operator==( + const HashPolicy& other) const { if (type != other.type) return false; - if (invert_match != other.invert_match) return false; - switch (type) { - case HeaderMatcherType::REGEX: - return regex_match->pattern() != other.regex_match->pattern(); - case HeaderMatcherType::RANGE: - return range_start != other.range_start && range_end != other.range_end; - case HeaderMatcherType::PRESENT: - return present_match != other.present_match; - default: - return string_matcher != other.string_matcher; + if (type == Type::HEADER) { + if (regex == nullptr) { + if (other.regex != nullptr) return false; + } else { + if (other.regex == nullptr) return false; + return header_name == other.header_name && + regex->pattern() == other.regex->pattern() && + regex_substitution == other.regex_substitution; + } } + return true; } -std::string XdsApi::Route::Matchers::HeaderMatcher::ToString() const { +std::string XdsApi::Route::HashPolicy::ToString() const { + std::vector<std::string> contents; switch (type) { - case HeaderMatcherType::EXACT: - return absl::StrFormat("Header exact match:%s %s:%s", - invert_match ? " not" : "", name, string_matcher); - case HeaderMatcherType::REGEX: - return absl::StrFormat("Header regex match:%s %s:%s", - invert_match ? " not" : "", name, - regex_match->pattern()); - case HeaderMatcherType::RANGE: - return absl::StrFormat("Header range match:%s %s:[%d, %d)", - invert_match ? " not" : "", name, range_start, - range_end); - case HeaderMatcherType::PRESENT: - return absl::StrFormat("Header present match:%s %s:%s", - invert_match ? " not" : "", name, - present_match ? "true" : "false"); - case HeaderMatcherType::PREFIX: - return absl::StrFormat("Header prefix match:%s %s:%s", - invert_match ? " not" : "", name, string_matcher); - case HeaderMatcherType::SUFFIX: - return absl::StrFormat("Header suffix match:%s %s:%s", - invert_match ? " not" : "", name, string_matcher); - default: - return ""; + case Type::HEADER: + contents.push_back("type=HEADER"); + break; + case Type::CHANNEL_ID: + contents.push_back("type=CHANNEL_ID"); + break; + } + contents.push_back( + absl::StrFormat("terminal=%s", terminal ? "true" : "false")); + if (type == Type::HEADER) { + contents.push_back(absl::StrFormat( + "Header %s:/%s/%s", header_name, + (regex == nullptr) ? "" : regex->pattern(), regex_substitution)); } + return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } // @@ -273,7 +220,8 @@ std::string XdsApi::Route::Matchers::HeaderMatcher::ToString() const { std::string XdsApi::Route::Matchers::ToString() const { std::vector<std::string> contents; - contents.push_back(path_matcher.ToString()); + contents.push_back( + absl::StrFormat("PathMatcher{%s}", path_matcher.ToString())); for (const HeaderMatcher& header_matcher : header_matchers) { contents.push_back(header_matcher.ToString()); } @@ -285,12 +233,28 @@ std::string XdsApi::Route::Matchers::ToString() const { } std::string XdsApi::Route::ClusterWeight::ToString() const { - return absl::StrFormat("{cluster=%s, weight=%d}", name, weight); + std::vector<std::string> contents; + contents.push_back(absl::StrCat("cluster=", name)); + contents.push_back(absl::StrCat("weight=", weight)); + if (!typed_per_filter_config.empty()) { + std::vector<std::string> parts; + for (const auto& p : typed_per_filter_config) { + const std::string& key = p.first; + const auto& config = p.second; + parts.push_back(absl::StrCat(key, "=", config.ToString())); + } + contents.push_back(absl::StrCat("typed_per_filter_config={", + absl::StrJoin(parts, ", "), "}")); + } + return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } std::string XdsApi::Route::ToString() const { std::vector<std::string> contents; contents.push_back(matchers.ToString()); + for (const HashPolicy& hash_policy : hash_policies) { + contents.push_back(absl::StrCat("hash_policy=", hash_policy.ToString())); + } if (!cluster_name.empty()) { contents.push_back(absl::StrFormat("Cluster name: %s", cluster_name)); } @@ -300,6 +264,15 @@ std::string XdsApi::Route::ToString() const { if (max_stream_duration.has_value()) { contents.push_back(max_stream_duration->ToString()); } + if (!typed_per_filter_config.empty()) { + contents.push_back("typed_per_filter_config={"); + for (const auto& p : typed_per_filter_config) { + const std::string& name = p.first; + const auto& config = p.second; + contents.push_back(absl::StrCat(" ", name, "=", config.ToString())); + } + contents.push_back("}"); + } return absl::StrJoin(contents, "\n"); } @@ -322,6 +295,14 @@ std::string XdsApi::RdsUpdate::ToString() const { vhosts.push_back("\n }\n"); } vhosts.push_back(" ]\n"); + vhosts.push_back(" typed_per_filter_config={\n"); + for (const auto& p : vhost.typed_per_filter_config) { + const std::string& name = p.first; + const auto& config = p.second; + vhosts.push_back( + absl::StrCat(" ", name, "=", config.ToString(), "\n")); + } + vhosts.push_back(" }\n"); vhosts.push_back("]\n"); } return absl::StrJoin(vhosts, ""); @@ -426,102 +407,6 @@ XdsApi::RdsUpdate::VirtualHost* XdsApi::RdsUpdate::FindVirtualHostForDomain( } // -// XdsApi::StringMatcher -// - -XdsApi::StringMatcher::StringMatcher(StringMatcherType type, - const std::string& matcher, - bool ignore_case) - : type_(type), ignore_case_(ignore_case) { - if (type_ == StringMatcherType::SAFE_REGEX) { - regex_matcher_ = absl::make_unique<RE2>(matcher); - } else { - string_matcher_ = matcher; - } -} - -XdsApi::StringMatcher::StringMatcher(const StringMatcher& other) - : type_(other.type_), ignore_case_(other.ignore_case_) { - switch (type_) { - case StringMatcherType::SAFE_REGEX: - regex_matcher_ = absl::make_unique<RE2>(other.regex_matcher_->pattern()); - break; - default: - string_matcher_ = other.string_matcher_; - } -} - -XdsApi::StringMatcher& XdsApi::StringMatcher::operator=( - const StringMatcher& other) { - type_ = other.type_; - switch (type_) { - case StringMatcherType::SAFE_REGEX: - regex_matcher_ = absl::make_unique<RE2>(other.regex_matcher_->pattern()); - break; - default: - string_matcher_ = other.string_matcher_; - } - ignore_case_ = other.ignore_case_; - return *this; -} - -bool XdsApi::StringMatcher::operator==(const StringMatcher& other) const { - if (type_ != other.type_ || ignore_case_ != other.ignore_case_) return false; - switch (type_) { - case StringMatcherType::SAFE_REGEX: - return regex_matcher_->pattern() == other.regex_matcher_->pattern(); - default: - return string_matcher_ == other.string_matcher_; - } -} - -bool XdsApi::StringMatcher::Match(absl::string_view value) const { - switch (type_) { - case XdsApi::StringMatcher::StringMatcherType::EXACT: - return ignore_case_ ? absl::EqualsIgnoreCase(value, string_matcher_) - : value == string_matcher_; - case XdsApi::StringMatcher::StringMatcherType::PREFIX: - return ignore_case_ ? absl::StartsWithIgnoreCase(value, string_matcher_) - : absl::StartsWith(value, string_matcher_); - case XdsApi::StringMatcher::StringMatcherType::SUFFIX: - return ignore_case_ ? absl::EndsWithIgnoreCase(value, string_matcher_) - : absl::EndsWith(value, string_matcher_); - case XdsApi::StringMatcher::StringMatcherType::CONTAINS: - return ignore_case_ - ? absl::StrContains(absl::AsciiStrToLower(value), - absl::AsciiStrToLower(string_matcher_)) - : absl::StrContains(value, string_matcher_); - case XdsApi::StringMatcher::StringMatcherType::SAFE_REGEX: - // ignore_case_ is ignored for SAFE_REGEX - return RE2::FullMatch(std::string(value), *regex_matcher_); - default: - return false; - } -} - -std::string XdsApi::StringMatcher::ToString() const { - switch (type_) { - case StringMatcherType::EXACT: - return absl::StrFormat("StringMatcher{exact=%s%s}", string_matcher_, - ignore_case_ ? ", ignore_case" : ""); - case StringMatcherType::PREFIX: - return absl::StrFormat("StringMatcher{prefix=%s%s}", string_matcher_, - ignore_case_ ? ", ignore_case" : ""); - case StringMatcherType::SUFFIX: - return absl::StrFormat("StringMatcher{suffix=%s%s}", string_matcher_, - ignore_case_ ? ", ignore_case" : ""); - case StringMatcherType::CONTAINS: - return absl::StrFormat("StringMatcher{contains=%s%s}", string_matcher_, - ignore_case_ ? ", ignore_case" : ""); - case StringMatcherType::SAFE_REGEX: - return absl::StrFormat("StringMatcher{safe_regex=%s}", - regex_matcher_->pattern()); - default: - return ""; - } -} - -// // XdsApi::CommonTlsContext::CertificateValidationContext // @@ -610,6 +495,204 @@ bool XdsApi::CommonTlsContext::Empty() const { } // +// XdsApi::DownstreamTlsContext +// + +std::string XdsApi::DownstreamTlsContext::ToString() const { + return absl::StrFormat("common_tls_context=%s, require_client_certificate=%s", + common_tls_context.ToString(), + require_client_certificate ? "true" : "false"); +} + +bool XdsApi::DownstreamTlsContext::Empty() const { + return common_tls_context.Empty(); +} + +// +// XdsApi::LdsUpdate::HttpConnectionManager +// + +std::string XdsApi::LdsUpdate::HttpConnectionManager::ToString() const { + absl::InlinedVector<std::string, 4> contents; + contents.push_back(absl::StrFormat( + "route_config_name=%s", + !route_config_name.empty() ? route_config_name.c_str() : "<inlined>")); + 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<std::string> 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, ", "), "}"); +} + +// +// XdsApi::LdsUpdate::HttpFilter +// + +std::string XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter::ToString() + const { + return absl::StrCat("{name=", name, ", config=", config.ToString(), "}"); +} + +// +// XdsApi::LdsUpdate::FilterChainData +// + +std::string XdsApi::LdsUpdate::FilterChainData::ToString() const { + return absl::StrCat( + "{downstream_tls_context=", downstream_tls_context.ToString(), + " http_connection_manager=", http_connection_manager.ToString(), "}"); +} + +// +// XdsApi::LdsUpdate::FilterChainMap::CidrRange +// + +std::string XdsApi::LdsUpdate::FilterChainMap::CidrRange::ToString() const { + return absl::StrCat( + "{address_prefix=", grpc_sockaddr_to_string(&address, false), + ", prefix_len=", prefix_len, "}"); +} + +// +// FilterChain +// + +struct FilterChain { + struct FilterChainMatch { + uint32_t destination_port = 0; + std::vector<XdsApi::LdsUpdate::FilterChainMap::CidrRange> prefix_ranges; + XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType source_type = + XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType::kAny; + std::vector<XdsApi::LdsUpdate::FilterChainMap::CidrRange> + source_prefix_ranges; + std::vector<uint32_t> source_ports; + std::vector<std::string> server_names; + std::string transport_protocol; + std::vector<std::string> application_protocols; + + std::string ToString() const; + } filter_chain_match; + + std::shared_ptr<XdsApi::LdsUpdate::FilterChainData> filter_chain_data; +}; + +std::string FilterChain::FilterChainMatch::ToString() const { + absl::InlinedVector<std::string, 8> contents; + if (destination_port != 0) { + contents.push_back(absl::StrCat("destination_port=", destination_port)); + } + if (!prefix_ranges.empty()) { + std::vector<std::string> 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 == XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType:: + kSameIpOrLoopback) { + contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); + } else if (source_type == XdsApi::LdsUpdate::FilterChainMap:: + ConnectionSourceType::kExternal) { + contents.push_back("source_type=EXTERNAL"); + } + if (!source_prefix_ranges.empty()) { + std::vector<std::string> 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, ", "), "}"); +} + +// +// XdsApi::LdsUpdate::FilterChainMap +// + +std::string XdsApi::LdsUpdate::FilterChainMap::ToString() const { + std::vector<std::string> 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< + XdsApi::LdsUpdate::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, ", "), "}"); +} + +// +// XdsApi::LdsUpdate +// + +std::string XdsApi::LdsUpdate::ToString() const { + absl::InlinedVector<std::string, 4> 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, ", "), "}"); +} + +// // XdsApi::CdsUpdate // @@ -724,8 +807,13 @@ const char* kCdsV2TypeUrl = "type.googleapis.com/envoy.api.v2.Cluster"; const char* kEdsV2TypeUrl = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; -bool IsLds(absl::string_view type_url) { - return type_url == XdsApi::kLdsTypeUrl || type_url == kLdsV2TypeUrl; +bool IsLds(absl::string_view type_url, bool* is_v2 = nullptr) { + if (type_url == XdsApi::kLdsTypeUrl) return true; + if (type_url == kLdsV2TypeUrl) { + if (is_v2 != nullptr) *is_v2 = true; + return true; + } + return false; } bool IsRds(absl::string_view type_url) { @@ -749,39 +837,67 @@ XdsApi::XdsApi(XdsClient* client, TraceFlag* tracer, node_(node), build_version_(absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING, " ", grpc_version_string())), - user_agent_name_(absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING)) {} + user_agent_name_(absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING)) { + // Populate upb symtab with xDS proto messages that we want to print + // properly in logs. + // Note: This won't actually work properly until upb adds support for + // Any fields in textproto printing (internal b/178821188). + envoy_config_listener_v3_Listener_getmsgdef(symtab_.ptr()); + envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab_.ptr()); + envoy_config_cluster_v3_Cluster_getmsgdef(symtab_.ptr()); + envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab_.ptr()); + envoy_config_cluster_v3_Cluster_getmsgdef(symtab_.ptr()); + envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab_.ptr()); + envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef( + symtab_.ptr()); + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( + symtab_.ptr()); + // Load HTTP filter proto messages into the upb symtab. + XdsHttpFilterRegistry::PopulateSymtab(symtab_.ptr()); +} namespace { +struct EncodingContext { + XdsClient* client; + TraceFlag* tracer; + upb_symtab* symtab; + upb_arena* arena; + bool use_v3; +}; + // Works for both std::string and absl::string_view. template <typename T> inline upb_strview StdStringToUpbString(const T& str) { return upb_strview_make(str.data(), str.size()); } -void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb, - const Json& value); +void PopulateMetadataValue(const EncodingContext& context, + google_protobuf_Value* value_pb, const Json& value); -void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value, +void PopulateListValue(const EncodingContext& context, + google_protobuf_ListValue* list_value, const Json::Array& values) { for (const auto& value : values) { - auto* value_pb = google_protobuf_ListValue_add_values(list_value, arena); - PopulateMetadataValue(arena, value_pb, value); + auto* value_pb = + google_protobuf_ListValue_add_values(list_value, context.arena); + PopulateMetadataValue(context, value_pb, value); } } -void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb, +void PopulateMetadata(const EncodingContext& context, + google_protobuf_Struct* metadata_pb, const Json::Object& metadata) { for (const auto& p : metadata) { - google_protobuf_Value* value = google_protobuf_Value_new(arena); - PopulateMetadataValue(arena, value, p.second); + google_protobuf_Value* value = google_protobuf_Value_new(context.arena); + PopulateMetadataValue(context, value, p.second); google_protobuf_Struct_fields_set( - metadata_pb, StdStringToUpbString(p.first), value, arena); + metadata_pb, StdStringToUpbString(p.first), value, context.arena); } } -void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb, - const Json& value) { +void PopulateMetadataValue(const EncodingContext& context, + google_protobuf_Value* value_pb, const Json& value) { switch (value.type()) { case Json::Type::JSON_NULL: google_protobuf_Value_set_null_value(value_pb, 0); @@ -802,14 +918,14 @@ void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb, break; case Json::Type::OBJECT: { google_protobuf_Struct* struct_value = - google_protobuf_Value_mutable_struct_value(value_pb, arena); - PopulateMetadata(arena, struct_value, value.object_value()); + google_protobuf_Value_mutable_struct_value(value_pb, context.arena); + PopulateMetadata(context, struct_value, value.object_value()); break; } case Json::Type::ARRAY: { google_protobuf_ListValue* list_value = - google_protobuf_Value_mutable_list_value(value_pb, arena); - PopulateListValue(arena, list_value, value.array_value()); + google_protobuf_Value_mutable_list_value(value_pb, context.arena); + PopulateListValue(context, list_value, value.array_value()); break; } } @@ -836,7 +952,8 @@ std::string EncodeStringField(uint32_t field_number, const std::string& str) { EncodeVarint(str.size()) + str; } -void PopulateBuildVersion(upb_arena* arena, envoy_config_core_v3_Node* node_msg, +void PopulateBuildVersion(const EncodingContext& context, + envoy_config_core_v3_Node* node_msg, const std::string& build_version) { std::string encoded_build_version = EncodeStringField(5, build_version); // TODO(roth): This should use upb_msg_addunknown(), but that API is @@ -844,10 +961,11 @@ void PopulateBuildVersion(upb_arena* arena, envoy_config_core_v3_Node* node_msg, // API for now. Change this once we upgrade to a version of upb that // fixes this bug. _upb_msg_addunknown(node_msg, encoded_build_version.data(), - encoded_build_version.size(), arena); + encoded_build_version.size(), context.arena); } -void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node, bool use_v3, +void PopulateNode(const EncodingContext& context, + const XdsBootstrap::Node* node, const std::string& build_version, const std::string& user_agent_name, envoy_config_core_v3_Node* node_msg) { @@ -862,13 +980,13 @@ void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node, bool use_v3, } if (!node->metadata.object_value().empty()) { google_protobuf_Struct* metadata = - envoy_config_core_v3_Node_mutable_metadata(node_msg, arena); - PopulateMetadata(arena, metadata, node->metadata.object_value()); + envoy_config_core_v3_Node_mutable_metadata(node_msg, context.arena); + PopulateMetadata(context, metadata, node->metadata.object_value()); } if (!node->locality_region.empty() || !node->locality_zone.empty() || - !node->locality_subzone.empty()) { + !node->locality_sub_zone.empty()) { envoy_config_core_v3_Locality* locality = - envoy_config_core_v3_Node_mutable_locality(node_msg, arena); + envoy_config_core_v3_Node_mutable_locality(node_msg, context.arena); if (!node->locality_region.empty()) { envoy_config_core_v3_Locality_set_region( locality, StdStringToUpbString(node->locality_region)); @@ -877,14 +995,14 @@ void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node, bool use_v3, envoy_config_core_v3_Locality_set_zone( locality, StdStringToUpbString(node->locality_zone)); } - if (!node->locality_subzone.empty()) { + if (!node->locality_sub_zone.empty()) { envoy_config_core_v3_Locality_set_sub_zone( - locality, StdStringToUpbString(node->locality_subzone)); + locality, StdStringToUpbString(node->locality_sub_zone)); } } } - if (!use_v3) { - PopulateBuildVersion(arena, node_msg, build_version); + if (!context.use_v3) { + PopulateBuildVersion(context, node_msg, build_version); } envoy_config_core_v3_Node_set_user_agent_name( node_msg, StdStringToUpbString(user_agent_name)); @@ -892,7 +1010,7 @@ void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node, bool use_v3, node_msg, upb_strview_makez(grpc_version_string())); envoy_config_core_v3_Node_add_client_features( node_msg, upb_strview_makez("envoy.lb.does_not_support_overprovisioning"), - arena); + context.arena); } inline absl::string_view UpbStringToAbsl(const upb_strview& str) { @@ -904,24 +1022,25 @@ inline std::string UpbStringToStdString(const upb_strview& str) { } void MaybeLogDiscoveryRequest( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, + const EncodingContext& context, const envoy_service_discovery_v3_DiscoveryRequest* request) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_service_discovery_v3_DiscoveryRequest_getmsgdef(symtab); + envoy_service_discovery_v3_DiscoveryRequest_getmsgdef(context.symtab); char buf[10240]; upb_text_encode(request, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] constructed ADS request: %s", client, - buf); + gpr_log(GPR_DEBUG, "[xds_client %p] constructed ADS request: %s", + context.client, buf); } } grpc_slice SerializeDiscoveryRequest( - upb_arena* arena, envoy_service_discovery_v3_DiscoveryRequest* request) { + const EncodingContext& context, + envoy_service_discovery_v3_DiscoveryRequest* request) { size_t output_length; char* output = envoy_service_discovery_v3_DiscoveryRequest_serialize( - request, arena, &output_length); + request, context.arena, &output_length); return grpc_slice_from_copied_buffer(output, output_length); } @@ -949,9 +1068,11 @@ absl::string_view TypeUrlExternalToInternal(bool use_v3, grpc_slice XdsApi::CreateAdsRequest( const XdsBootstrap::XdsServer& server, const std::string& type_url, const std::set<absl::string_view>& resource_names, - const std::string& version, const std::string& nonce, grpc_error* error, - bool populate_node) { + const std::string& version, const std::string& nonce, + grpc_error_handle error, bool populate_node) { upb::Arena arena; + const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(), + server.ShouldUseV3()}; // Create a request. envoy_service_discovery_v3_DiscoveryRequest* request = envoy_service_discovery_v3_DiscoveryRequest_new(arena.ptr()); @@ -971,6 +1092,7 @@ grpc_slice XdsApi::CreateAdsRequest( request, StdStringToUpbString(nonce)); } // Set error_detail if it's a NACK. + std::string error_string_storage; if (error != GRPC_ERROR_NONE) { google_rpc_Status* error_detail = envoy_service_discovery_v3_DiscoveryRequest_mutable_error_detail( @@ -981,12 +1103,9 @@ grpc_slice XdsApi::CreateAdsRequest( // generate them in the parsing code, and then use that here. google_rpc_Status_set_code(error_detail, GRPC_STATUS_INVALID_ARGUMENT); // Error description comes from the error that was passed in. - grpc_slice error_description_slice; - GPR_ASSERT(grpc_error_get_str(error, GRPC_ERROR_STR_DESCRIPTION, - &error_description_slice)); - upb_strview error_description_strview = - StdStringToUpbString(StringViewFromSlice(error_description_slice)); - google_rpc_Status_set_message(error_detail, error_description_strview); + error_string_storage = grpc_error_std_string(error); + upb_strview error_description = StdStringToUpbString(error_string_storage); + google_rpc_Status_set_message(error_detail, error_description); GRPC_ERROR_UNREF(error); } // Populate node. @@ -994,79 +1113,102 @@ grpc_slice XdsApi::CreateAdsRequest( envoy_config_core_v3_Node* node_msg = envoy_service_discovery_v3_DiscoveryRequest_mutable_node(request, arena.ptr()); - PopulateNode(arena.ptr(), node_, server.ShouldUseV3(), build_version_, - user_agent_name_, node_msg); + PopulateNode(context, node_, build_version_, user_agent_name_, node_msg); } // Add resource_names. for (const auto& resource_name : resource_names) { envoy_service_discovery_v3_DiscoveryRequest_add_resource_names( request, StdStringToUpbString(resource_name), arena.ptr()); } - MaybeLogDiscoveryRequest(client_, tracer_, symtab_.ptr(), request); - return SerializeDiscoveryRequest(arena.ptr(), request); + MaybeLogDiscoveryRequest(context, request); + return SerializeDiscoveryRequest(context, request); } namespace { void MaybeLogDiscoveryResponse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, + const EncodingContext& context, const envoy_service_discovery_v3_DiscoveryResponse* response) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_service_discovery_v3_DiscoveryResponse_getmsgdef(symtab); + envoy_service_discovery_v3_DiscoveryResponse_getmsgdef(context.symtab); char buf[10240]; upb_text_encode(response, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] received response: %s", client, buf); + gpr_log(GPR_DEBUG, "[xds_client %p] received response: %s", context.client, + buf); + } +} + +void MaybeLogHttpConnectionManager( + const EncodingContext& 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_msgdef* msg_type = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( + context.symtab); + char buf[10240]; + upb_text_encode(http_connection_manager_config, msg_type, nullptr, 0, buf, + sizeof(buf)); + gpr_log(GPR_DEBUG, "[xds_client %p] HttpConnectionManager: %s", + context.client, buf); } } void MaybeLogRouteConfiguration( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, + const EncodingContext& context, const envoy_config_route_v3_RouteConfiguration* route_config) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab); + envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab); char buf[10240]; upb_text_encode(route_config, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] RouteConfiguration: %s", client, buf); + gpr_log(GPR_DEBUG, "[xds_client %p] RouteConfiguration: %s", context.client, + buf); } } -void MaybeLogCluster(XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, +void MaybeLogCluster(const EncodingContext& context, const envoy_config_cluster_v3_Cluster* cluster) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_config_cluster_v3_Cluster_getmsgdef(symtab); + envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab); char buf[10240]; upb_text_encode(cluster, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", client, buf); + gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", context.client, buf); } } void MaybeLogClusterLoadAssignment( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, + const EncodingContext& context, const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab); + envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef( + context.symtab); char buf[10240]; upb_text_encode(cla, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] ClusterLoadAssignment: %s", client, - buf); + gpr_log(GPR_DEBUG, "[xds_client %p] ClusterLoadAssignment: %s", + context.client, buf); } } -grpc_error* RoutePathMatchParse(const envoy_config_route_v3_RouteMatch* match, - XdsApi::Route* route, bool* ignore_route) { - auto* case_sensitive = envoy_config_route_v3_RouteMatch_case_sensitive(match); - if (case_sensitive != nullptr) { - route->matchers.path_matcher.case_sensitive = - google_protobuf_BoolValue_value(case_sensitive); +grpc_error_handle RoutePathMatchParse( + const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route, + bool* ignore_route) { + auto* case_sensitive_ptr = + envoy_config_route_v3_RouteMatch_case_sensitive(match); + bool case_sensitive = true; + if (case_sensitive_ptr != nullptr) { + case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr); } + StringMatcher::Type type; + std::string match_string; if (envoy_config_route_v3_RouteMatch_has_prefix(match)) { absl::string_view prefix = UpbStringToAbsl(envoy_config_route_v3_RouteMatch_prefix(match)); @@ -1091,9 +1233,8 @@ grpc_error* RoutePathMatchParse(const envoy_config_route_v3_RouteMatch* match, return GRPC_ERROR_NONE; } } - route->matchers.path_matcher.type = - XdsApi::Route::Matchers::PathMatcher::PathMatcherType::PREFIX; - route->matchers.path_matcher.string_matcher = std::string(prefix); + type = StringMatcher::Type::kPrefix; + match_string = std::string(prefix); } else if (envoy_config_route_v3_RouteMatch_has_path(match)) { absl::string_view path = UpbStringToAbsl(envoy_config_route_v3_RouteMatch_path(match)); @@ -1126,102 +1267,99 @@ grpc_error* RoutePathMatchParse(const envoy_config_route_v3_RouteMatch* match, *ignore_route = true; return GRPC_ERROR_NONE; } - route->matchers.path_matcher.type = - XdsApi::Route::Matchers::PathMatcher::PathMatcherType::PATH; - route->matchers.path_matcher.string_matcher = std::string(path); + type = StringMatcher::Type::kExact; + match_string = std::string(path); } else if (envoy_config_route_v3_RouteMatch_has_safe_regex(match)) { const envoy_type_matcher_v3_RegexMatcher* regex_matcher = envoy_config_route_v3_RouteMatch_safe_regex(match); GPR_ASSERT(regex_matcher != nullptr); - std::string matcher = UpbStringToStdString( + type = StringMatcher::Type::kSafeRegex; + match_string = UpbStringToStdString( envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - RE2::Options options; - options.set_case_sensitive(route->matchers.path_matcher.case_sensitive); - auto regex = absl::make_unique<RE2>(std::move(matcher), options); - if (!regex->ok()) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Invalid regex string specified in path matcher."); - } - route->matchers.path_matcher.type = - XdsApi::Route::Matchers::PathMatcher::PathMatcherType::REGEX; - route->matchers.path_matcher.regex_matcher = std::move(regex); } else { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Invalid route path specifier specified."); } + absl::StatusOr<StringMatcher> string_matcher = + StringMatcher::Create(type, match_string, case_sensitive); + if (!string_matcher.ok()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("path matcher: ", string_matcher.status().message()) + .c_str()); + ; + } + route->matchers.path_matcher = std::move(string_matcher.value()); return GRPC_ERROR_NONE; } -grpc_error* RouteHeaderMatchersParse( +grpc_error_handle RouteHeaderMatchersParse( const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route) { size_t size; const envoy_config_route_v3_HeaderMatcher* const* headers = envoy_config_route_v3_RouteMatch_headers(match, &size); for (size_t i = 0; i < size; ++i) { const envoy_config_route_v3_HeaderMatcher* header = headers[i]; - XdsApi::Route::Matchers::HeaderMatcher header_matcher; - header_matcher.name = + const std::string name = UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header)); + HeaderMatcher::Type type; + std::string match_string; + int64_t range_start = 0; + int64_t range_end = 0; + bool present_match = false; if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) { - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::EXACT; - header_matcher.string_matcher = UpbStringToStdString( + type = HeaderMatcher::Type::kExact; + match_string = UpbStringToStdString( envoy_config_route_v3_HeaderMatcher_exact_match(header)); } else if (envoy_config_route_v3_HeaderMatcher_has_safe_regex_match( header)) { const envoy_type_matcher_v3_RegexMatcher* regex_matcher = envoy_config_route_v3_HeaderMatcher_safe_regex_match(header); GPR_ASSERT(regex_matcher != nullptr); - const std::string matcher = UpbStringToStdString( + type = HeaderMatcher::Type::kSafeRegex; + match_string = UpbStringToStdString( envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - std::unique_ptr<RE2> regex = absl::make_unique<RE2>(matcher); - if (!regex->ok()) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Invalid regex string specified in header matcher."); - } - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::REGEX; - header_matcher.regex_match = std::move(regex); } else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) { - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::RANGE; + type = HeaderMatcher::Type::kRange; const envoy_type_v3_Int64Range* range_matcher = envoy_config_route_v3_HeaderMatcher_range_match(header); - header_matcher.range_start = - envoy_type_v3_Int64Range_start(range_matcher); - header_matcher.range_end = envoy_type_v3_Int64Range_end(range_matcher); - if (header_matcher.range_end < header_matcher.range_start) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Invalid range header matcher specifier specified: end " - "cannot be smaller than start."); - } + range_start = envoy_type_v3_Int64Range_start(range_matcher); + range_end = envoy_type_v3_Int64Range_end(range_matcher); } else if (envoy_config_route_v3_HeaderMatcher_has_present_match(header)) { - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::PRESENT; - header_matcher.present_match = - envoy_config_route_v3_HeaderMatcher_present_match(header); + type = HeaderMatcher::Type::kPresent; + present_match = envoy_config_route_v3_HeaderMatcher_present_match(header); } else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) { - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::PREFIX; - header_matcher.string_matcher = UpbStringToStdString( + type = HeaderMatcher::Type::kPrefix; + match_string = UpbStringToStdString( envoy_config_route_v3_HeaderMatcher_prefix_match(header)); } else if (envoy_config_route_v3_HeaderMatcher_has_suffix_match(header)) { - header_matcher.type = - XdsApi::Route::Matchers::HeaderMatcher::HeaderMatcherType::SUFFIX; - header_matcher.string_matcher = UpbStringToStdString( + type = HeaderMatcher::Type::kSuffix; + match_string = UpbStringToStdString( envoy_config_route_v3_HeaderMatcher_suffix_match(header)); + } else if (envoy_config_route_v3_HeaderMatcher_has_contains_match(header)) { + type = HeaderMatcher::Type::kContains; + match_string = UpbStringToStdString( + envoy_config_route_v3_HeaderMatcher_contains_match(header)); } else { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "Invalid route header matcher specified."); } - header_matcher.invert_match = + bool invert_match = envoy_config_route_v3_HeaderMatcher_invert_match(header); - route->matchers.header_matchers.emplace_back(std::move(header_matcher)); + absl::StatusOr<HeaderMatcher> header_matcher = + HeaderMatcher::Create(name, type, match_string, range_start, range_end, + present_match, invert_match); + if (!header_matcher.ok()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("header matcher: ", header_matcher.status().message()) + .c_str()); + } + route->matchers.header_matchers.emplace_back( + std::move(header_matcher.value())); } return GRPC_ERROR_NONE; } -grpc_error* RouteRuntimeFractionParse( +grpc_error_handle RouteRuntimeFractionParse( const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route) { const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction = envoy_config_route_v3_RouteMatch_runtime_fraction(match); @@ -1254,8 +1392,98 @@ grpc_error* RouteRuntimeFractionParse( return GRPC_ERROR_NONE; } -grpc_error* RouteActionParse(const envoy_config_route_v3_Route* route_msg, - XdsApi::Route* route, bool* ignore_route) { +grpc_error_handle ExtractHttpFilterTypeName(const EncodingContext& context, + const google_protobuf_Any* any, + absl::string_view* filter_type) { + *filter_type = UpbStringToAbsl(google_protobuf_Any_type_url(any)); + if (*filter_type == "type.googleapis.com/udpa.type.v1.TypedStruct") { + upb_strview any_value = google_protobuf_Any_value(any); + const auto* typed_struct = udpa_type_v1_TypedStruct_parse( + any_value.data, any_value.size, context.arena); + if (typed_struct == nullptr) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "could not parse TypedStruct from filter config"); + } + *filter_type = + UpbStringToAbsl(udpa_type_v1_TypedStruct_type_url(typed_struct)); + } + *filter_type = absl::StripPrefix(*filter_type, "type.googleapis.com/"); + return GRPC_ERROR_NONE; +} + +template <typename ParentType, typename EntryType> +grpc_error_handle ParseTypedPerFilterConfig( + const EncodingContext& context, const ParentType* parent, + const EntryType* (*entry_func)(const ParentType*, size_t*), + upb_strview (*key_func)(const EntryType*), + const google_protobuf_Any* (*value_func)(const EntryType*), + XdsApi::TypedPerFilterConfig* typed_per_filter_config) { + size_t filter_it = UPB_MAP_BEGIN; + while (true) { + const auto* filter_entry = entry_func(parent, &filter_it); + if (filter_entry == nullptr) break; + absl::string_view key = UpbStringToAbsl(key_func(filter_entry)); + if (key.empty()) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING("empty filter name in map"); + } + const google_protobuf_Any* any = value_func(filter_entry); + GPR_ASSERT(any != nullptr); + absl::string_view filter_type = + UpbStringToAbsl(google_protobuf_Any_type_url(any)); + if (filter_type.empty()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("no filter config specified for filter name ", key) + .c_str()); + } + bool is_optional = false; + if (filter_type == + "type.googleapis.com/envoy.config.route.v3.FilterConfig") { + upb_strview any_value = google_protobuf_Any_value(any); + const auto* filter_config = envoy_config_route_v3_FilterConfig_parse( + any_value.data, any_value.size, context.arena); + if (filter_config == nullptr) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("could not parse FilterConfig wrapper for ", key) + .c_str()); + } + is_optional = + envoy_config_route_v3_FilterConfig_is_optional(filter_config); + any = envoy_config_route_v3_FilterConfig_config(filter_config); + if (any == nullptr) { + if (is_optional) continue; + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("no filter config specified for filter name ", key) + .c_str()); + } + } + grpc_error_handle error = + ExtractHttpFilterTypeName(context, any, &filter_type); + if (error != GRPC_ERROR_NONE) return error; + const XdsHttpFilterImpl* filter_impl = + XdsHttpFilterRegistry::GetFilterForType(filter_type); + if (filter_impl == nullptr) { + if (is_optional) continue; + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("no filter registered for config type ", filter_type) + .c_str()); + } + absl::StatusOr<XdsHttpFilterImpl::FilterConfig> filter_config = + filter_impl->GenerateFilterConfigOverride( + google_protobuf_Any_value(any), context.arena); + if (!filter_config.ok()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("filter config for type ", filter_type, + " failed to parse: ", filter_config.status().ToString()) + .c_str()); + } + (*typed_per_filter_config)[std::string(key)] = std::move(*filter_config); + } + return GRPC_ERROR_NONE; +} + +grpc_error_handle RouteActionParse(const EncodingContext& context, + const envoy_config_route_v3_Route* route_msg, + XdsApi::Route* route, bool* ignore_route) { if (!envoy_config_route_v3_Route_has_route(route_msg)) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "No RouteAction found in route."); @@ -1307,6 +1535,17 @@ grpc_error* RouteActionParse(const envoy_config_route_v3_Route* route_msg, cluster.weight = google_protobuf_UInt32Value_value(weight); if (cluster.weight == 0) continue; sum_of_weights += cluster.weight; + if (context.use_v3) { + grpc_error_handle error = ParseTypedPerFilterConfig< + envoy_config_route_v3_WeightedCluster_ClusterWeight, + envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>( + context, cluster_weight, + envoy_config_route_v3_WeightedCluster_ClusterWeight_typed_per_filter_config_next, + envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_key, + envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_value, + &cluster.typed_per_filter_config); + if (error != GRPC_ERROR_NONE) return error; + } route->weighted_clusters.emplace_back(std::move(cluster)); } if (total_weight != sum_of_weights) { @@ -1321,7 +1560,7 @@ grpc_error* RouteActionParse(const envoy_config_route_v3_Route* route_msg, // No cluster or weighted_clusters found in RouteAction, ignore this route. *ignore_route = true; } - if (XdsTimeoutEnabled() && !*ignore_route) { + if (!*ignore_route) { const envoy_config_route_v3_RouteAction_MaxStreamDuration* max_stream_duration = envoy_config_route_v3_RouteAction_max_stream_duration(route_action); @@ -1342,20 +1581,102 @@ grpc_error* RouteActionParse(const envoy_config_route_v3_Route* route_msg, } } } + // Get HashPolicy from RouteAction + if (XdsRingHashEnabled()) { + size_t size = 0; + const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies = + envoy_config_route_v3_RouteAction_hash_policy(route_action, &size); + for (size_t i = 0; i < size; ++i) { + const envoy_config_route_v3_RouteAction_HashPolicy* hash_policy = + hash_policies[i]; + XdsApi::Route::HashPolicy policy; + policy.terminal = + envoy_config_route_v3_RouteAction_HashPolicy_terminal(hash_policy); + const envoy_config_route_v3_RouteAction_HashPolicy_Header* header; + const envoy_config_route_v3_RouteAction_HashPolicy_FilterState* + filter_state; + if ((header = envoy_config_route_v3_RouteAction_HashPolicy_header( + hash_policy)) != nullptr) { + policy.type = XdsApi::Route::HashPolicy::Type::HEADER; + policy.header_name = UpbStringToStdString( + envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name( + header)); + const struct envoy_type_matcher_v3_RegexMatchAndSubstitute* + regex_rewrite = + envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite( + header); + if (regex_rewrite == nullptr) { + gpr_log( + GPR_DEBUG, + "RouteAction HashPolicy contains policy specifier Header with " + "RegexMatchAndSubstitution but Regex is missing"); + continue; + } + const envoy_type_matcher_v3_RegexMatcher* regex_matcher = + envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern( + regex_rewrite); + if (regex_matcher == nullptr) { + gpr_log( + GPR_DEBUG, + "RouteAction HashPolicy contains policy specifier Header with " + "RegexMatchAndSubstitution but RegexMatcher pattern is " + "missing"); + continue; + } + RE2::Options options; + policy.regex = absl::make_unique<RE2>( + UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)), + options); + if (!policy.regex->ok()) { + gpr_log( + GPR_DEBUG, + "RouteAction HashPolicy contains policy specifier Header with " + "RegexMatchAndSubstitution but RegexMatcher pattern does not " + "compile"); + continue; + } + policy.regex_substitution = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution( + regex_rewrite)); + } else if ((filter_state = + envoy_config_route_v3_RouteAction_HashPolicy_filter_state( + hash_policy)) != nullptr) { + std::string key = UpbStringToStdString( + envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key( + filter_state)); + if (key == "io.grpc.channel_id") { + policy.type = XdsApi::Route::HashPolicy::Type::CHANNEL_ID; + } else { + gpr_log(GPR_DEBUG, + "RouteAction HashPolicy contains policy specifier " + "FilterState but " + "key is not io.grpc.channel_id."); + continue; + } + } else { + gpr_log( + GPR_DEBUG, + "RouteAction HashPolicy contains unsupported policy specifier."); + continue; + } + route->hash_policies.emplace_back(std::move(policy)); + } + } return GRPC_ERROR_NONE; } -grpc_error* RouteConfigParse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, +grpc_error_handle RouteConfigParse( + const EncodingContext& context, const envoy_config_route_v3_RouteConfiguration* route_config, XdsApi::RdsUpdate* rds_update) { - MaybeLogRouteConfiguration(client, tracer, symtab, route_config); + MaybeLogRouteConfiguration(context, route_config); // Get the virtual hosts. - size_t size; + size_t num_virtual_hosts; const envoy_config_route_v3_VirtualHost* const* virtual_hosts = - envoy_config_route_v3_RouteConfiguration_virtual_hosts(route_config, - &size); - for (size_t i = 0; i < size; ++i) { + envoy_config_route_v3_RouteConfiguration_virtual_hosts( + route_config, &num_virtual_hosts); + for (size_t i = 0; i < num_virtual_hosts; ++i) { rds_update->virtual_hosts.emplace_back(); XdsApi::RdsUpdate::VirtualHost& vhost = rds_update->virtual_hosts.back(); // Parse domains. @@ -1375,6 +1696,18 @@ grpc_error* RouteConfigParse( if (vhost.domains.empty()) { return GRPC_ERROR_CREATE_FROM_STATIC_STRING("VirtualHost has no domains"); } + // Parse typed_per_filter_config. + if (context.use_v3) { + grpc_error_handle error = ParseTypedPerFilterConfig< + envoy_config_route_v3_VirtualHost, + envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry>( + context, virtual_hosts[i], + envoy_config_route_v3_VirtualHost_typed_per_filter_config_next, + envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_key, + envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_value, + &vhost.typed_per_filter_config); + if (error != GRPC_ERROR_NONE) return error; + } // Parse routes. size_t num_routes; const envoy_config_route_v3_Route* const* routes = @@ -1387,6 +1720,9 @@ grpc_error* RouteConfigParse( for (size_t j = 0; j < num_routes; ++j) { const envoy_config_route_v3_RouteMatch* match = envoy_config_route_v3_Route_match(routes[j]); + if (match == nullptr) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Match can't be null."); + } size_t query_parameters_size; static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters( match, &query_parameters_size)); @@ -1395,16 +1731,28 @@ grpc_error* RouteConfigParse( } XdsApi::Route route; bool ignore_route = false; - grpc_error* error = RoutePathMatchParse(match, &route, &ignore_route); + grpc_error_handle error = + RoutePathMatchParse(match, &route, &ignore_route); if (error != GRPC_ERROR_NONE) return error; if (ignore_route) continue; error = RouteHeaderMatchersParse(match, &route); if (error != GRPC_ERROR_NONE) return error; error = RouteRuntimeFractionParse(match, &route); if (error != GRPC_ERROR_NONE) return error; - error = RouteActionParse(routes[j], &route, &ignore_route); + error = RouteActionParse(context, routes[j], &route, &ignore_route); if (error != GRPC_ERROR_NONE) return error; if (ignore_route) continue; + if (context.use_v3) { + grpc_error_handle error = ParseTypedPerFilterConfig< + envoy_config_route_v3_Route, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry>( + context, routes[j], + envoy_config_route_v3_Route_typed_per_filter_config_next, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, + &route.typed_per_filter_config); + if (error != GRPC_ERROR_NONE) return error; + } vhost.routes.emplace_back(std::move(route)); } if (vhost.routes.empty()) { @@ -1414,170 +1762,6 @@ grpc_error* RouteConfigParse( return GRPC_ERROR_NONE; } -grpc_error* LdsResponseParse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, - const envoy_service_discovery_v3_DiscoveryResponse* response, - const std::set<absl::string_view>& expected_listener_names, - XdsApi::LdsUpdateMap* lds_update_map, upb_arena* arena) { - // Get the resources from the response. - size_t size; - const google_protobuf_Any* const* resources = - envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size); - for (size_t i = 0; i < size; ++i) { - // Check the type_url of the resource. - absl::string_view type_url = - UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); - if (!IsLds(type_url)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not LDS."); - } - // Decode the listener. - const upb_strview encoded_listener = - google_protobuf_Any_value(resources[i]); - const envoy_config_listener_v3_Listener* listener = - envoy_config_listener_v3_Listener_parse(encoded_listener.data, - encoded_listener.size, arena); - if (listener == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode listener."); - } - // Check listener name. Ignore unexpected listeners. - std::string listener_name = - UpbStringToStdString(envoy_config_listener_v3_Listener_name(listener)); - if (expected_listener_names.find(listener_name) == - expected_listener_names.end()) { - continue; - } - // Fail if listener name is duplicated. - if (lds_update_map->find(listener_name) != lds_update_map->end()) { - return GRPC_ERROR_CREATE_FROM_COPIED_STRING( - absl::StrCat("duplicate listener name \"", listener_name, "\"") - .c_str()); - } - XdsApi::LdsUpdate& lds_update = (*lds_update_map)[listener_name]; - // Get api_listener and decode it to http_connection_manager. - const envoy_config_listener_v3_ApiListener* api_listener = - envoy_config_listener_v3_Listener_api_listener(listener); - if (api_listener == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Listener has no ApiListener."); - } - const upb_strview encoded_api_listener = google_protobuf_Any_value( - envoy_config_listener_v3_ApiListener_api_listener(api_listener)); - const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* - http_connection_manager = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( - encoded_api_listener.data, encoded_api_listener.size, arena); - if (http_connection_manager == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Could not parse HttpConnectionManager config from ApiListener"); - } - if (XdsTimeoutEnabled()) { - // 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); - if (options != nullptr) { - const google_protobuf_Duration* duration = - envoy_config_core_v3_HttpProtocolOptions_max_stream_duration( - options); - if (duration != nullptr) { - lds_update.http_max_stream_duration.seconds = - google_protobuf_Duration_seconds(duration); - lds_update.http_max_stream_duration.nanos = - google_protobuf_Duration_nanos(duration); - } - } - } - // 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)) { - const envoy_config_route_v3_RouteConfiguration* route_config = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_route_config( - http_connection_manager); - XdsApi::RdsUpdate rds_update; - grpc_error* error = - RouteConfigParse(client, tracer, symtab, route_config, &rds_update); - if (error != GRPC_ERROR_NONE) return error; - lds_update.rds_update = std::move(rds_update); - continue; - } - // Validate that RDS must be used to get the route_config dynamically. - if (!envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_rds( - http_connection_manager)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "HttpConnectionManager neither has inlined route_config nor RDS."); - } - const envoy_extensions_filters_network_http_connection_manager_v3_Rds* rds = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_rds( - http_connection_manager); - // 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)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "HttpConnectionManager ConfigSource for RDS does not specify ADS."); - } - // Get the route_config_name. - lds_update.route_config_name = UpbStringToStdString( - envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( - rds)); - } - return GRPC_ERROR_NONE; -} - -grpc_error* RdsResponseParse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, - const envoy_service_discovery_v3_DiscoveryResponse* response, - const std::set<absl::string_view>& expected_route_configuration_names, - XdsApi::RdsUpdateMap* rds_update_map, upb_arena* arena) { - // Get the resources from the response. - size_t size; - const google_protobuf_Any* const* resources = - envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size); - for (size_t i = 0; i < size; ++i) { - // Check the type_url of the resource. - absl::string_view type_url = - UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); - if (!IsRds(type_url)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not RDS."); - } - // Decode the route_config. - const upb_strview encoded_route_config = - google_protobuf_Any_value(resources[i]); - const envoy_config_route_v3_RouteConfiguration* route_config = - envoy_config_route_v3_RouteConfiguration_parse( - encoded_route_config.data, encoded_route_config.size, arena); - if (route_config == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode route_config."); - } - // Check route_config_name. Ignore unexpected route_config. - std::string route_config_name = UpbStringToStdString( - envoy_config_route_v3_RouteConfiguration_name(route_config)); - if (expected_route_configuration_names.find(route_config_name) == - expected_route_configuration_names.end()) { - continue; - } - // Fail if route config name is duplicated. - if (rds_update_map->find(route_config_name) != rds_update_map->end()) { - return GRPC_ERROR_CREATE_FROM_COPIED_STRING( - absl::StrCat("duplicate route config name \"", route_config_name, - "\"") - .c_str()); - } - // Parse the route_config. - XdsApi::RdsUpdate& rds_update = - (*rds_update_map)[std::move(route_config_name)]; - grpc_error* error = - RouteConfigParse(client, tracer, symtab, route_config, &rds_update); - if (error != GRPC_ERROR_NONE) return error; - } - return GRPC_ERROR_NONE; -} - XdsApi::CommonTlsContext::CertificateProviderInstance CertificateProviderInstanceParse( const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance* @@ -1591,11 +1775,11 @@ CertificateProviderInstanceParse( certificate_provider_instance_proto))}; } -grpc_error* CommonTlsContextParse( +grpc_error_handle CommonTlsContextParse( const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* common_tls_context_proto, XdsApi::CommonTlsContext* common_tls_context) GRPC_MUST_USE_RESULT; -grpc_error* CommonTlsContextParse( +grpc_error_handle CommonTlsContextParse( const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* common_tls_context_proto, XdsApi::CommonTlsContext* common_tls_context) { @@ -1612,35 +1796,35 @@ grpc_error* CommonTlsContextParse( envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names( default_validation_context, &len); for (size_t i = 0; i < len; ++i) { - XdsApi::StringMatcher::StringMatcherType type; + StringMatcher::Type type; std::string matcher; if (envoy_type_matcher_v3_StringMatcher_has_exact( subject_alt_names_matchers[i])) { - type = XdsApi::StringMatcher::StringMatcherType::EXACT; + type = StringMatcher::Type::kExact; matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_exact( subject_alt_names_matchers[i])); } else if (envoy_type_matcher_v3_StringMatcher_has_prefix( subject_alt_names_matchers[i])) { - type = XdsApi::StringMatcher::StringMatcherType::PREFIX; + type = StringMatcher::Type::kPrefix; matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_prefix( subject_alt_names_matchers[i])); } else if (envoy_type_matcher_v3_StringMatcher_has_suffix( subject_alt_names_matchers[i])) { - type = XdsApi::StringMatcher::StringMatcherType::SUFFIX; + type = StringMatcher::Type::kSuffix; matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_suffix( subject_alt_names_matchers[i])); } else if (envoy_type_matcher_v3_StringMatcher_has_contains( subject_alt_names_matchers[i])) { - type = XdsApi::StringMatcher::StringMatcherType::CONTAINS; + type = StringMatcher::Type::kContains; matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_contains( subject_alt_names_matchers[i])); } else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex( subject_alt_names_matchers[i])) { - type = XdsApi::StringMatcher::StringMatcherType::SAFE_REGEX; + type = StringMatcher::Type::kSafeRegex; auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex( subject_alt_names_matchers[i]); matcher = UpbStringToStdString( @@ -1651,20 +1835,22 @@ grpc_error* CommonTlsContextParse( } bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case( subject_alt_names_matchers[i]); - XdsApi::StringMatcher string_matcher(type, matcher, ignore_case); - if (type == XdsApi::StringMatcher::StringMatcherType::SAFE_REGEX) { - if (!string_matcher.regex_matcher()->ok()) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Invalid regex string specified in string matcher."); - } - if (ignore_case) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "StringMatcher: ignore_case has no effect for SAFE_REGEX."); - } + absl::StatusOr<StringMatcher> string_matcher = + StringMatcher::Create(type, matcher, + /*case_sensitive=*/!ignore_case); + if (!string_matcher.ok()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("string matcher: ", + string_matcher.status().message()) + .c_str()); + } + if (type == StringMatcher::Type::kSafeRegex && ignore_case) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "StringMatcher: ignore_case has no effect for SAFE_REGEX."); } common_tls_context->combined_validation_context .default_validation_context.match_subject_alt_names.push_back( - std::move(string_matcher)); + std::move(string_matcher.value())); } } auto* validation_context_certificate_provider_instance = @@ -1688,11 +1874,797 @@ grpc_error* CommonTlsContextParse( return GRPC_ERROR_NONE; } -grpc_error* CdsResponseParse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, +grpc_error_handle HttpConnectionManagerParse( + bool is_client, const EncodingContext& context, + const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* + http_connection_manager_proto, + bool is_v2, + XdsApi::LdsUpdate::HttpConnectionManager* http_connection_manager) { + MaybeLogHttpConnectionManager(context, http_connection_manager_proto); + // 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.seconds = + google_protobuf_Duration_seconds(duration); + http_connection_manager->http_max_stream_duration.nanos = + google_protobuf_Duration_nanos(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<absl::string_view> 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_COPIED_STRING( + absl::StrCat("empty filter name at index ", i).c_str()); + } + if (names_seen.find(name) != names_seen.end()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("duplicate HTTP filter name: ", name).c_str()); + } + 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_COPIED_STRING( + absl::StrCat("no filter config specified for filter name ", name) + .c_str()); + } + absl::string_view filter_type; + grpc_error_handle error = + ExtractHttpFilterTypeName(context, any, &filter_type); + if (error != GRPC_ERROR_NONE) return error; + const XdsHttpFilterImpl* filter_impl = + XdsHttpFilterRegistry::GetFilterForType(filter_type); + if (filter_impl == nullptr) { + if (is_optional) continue; + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("no filter registered for config type ", filter_type) + .c_str()); + } + if ((is_client && !filter_impl->IsSupportedOnClients()) || + (!is_client && !filter_impl->IsSupportedOnServers())) { + if (is_optional) continue; + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrFormat("Filter %s is not supported on %s", filter_type, + is_client ? "clients" : "servers") + .c_str()); + } + absl::StatusOr<XdsHttpFilterImpl::FilterConfig> filter_config = + filter_impl->GenerateFilterConfig(google_protobuf_Any_value(any), + context.arena); + if (!filter_config.ok()) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat( + "filter config for type ", filter_type, + " failed to parse: ", filter_config.status().ToString()) + .c_str()); + } + http_connection_manager->http_filters.emplace_back( + XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter{ + std::string(name), std::move(*filter_config)}); + } + } 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( + XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter{ + "router", {kXdsHttpRouterFilterConfigName, Json()}}); + } + if (is_client) { + // 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); + XdsApi::RdsUpdate rds_update; + grpc_error_handle error = + RouteConfigParse(context, route_config, &rds_update); + if (error != GRPC_ERROR_NONE) 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)) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "HttpConnectionManager ConfigSource for RDS does not specify ADS."); + } + // 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 LdsResponseParseClient( + const EncodingContext& context, + const envoy_config_listener_v3_ApiListener* api_listener, bool is_v2, + XdsApi::LdsUpdate* lds_update) { + lds_update->type = XdsApi::LdsUpdate::ListenerType::kHttpApiListener; + const upb_strview 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 EncodingContext& context, + const envoy_config_core_v3_TransportSocket* transport_socket, + XdsApi::DownstreamTlsContext* downstream_tls_context) { + absl::string_view name = UpbStringToAbsl( + envoy_config_core_v3_TransportSocket_name(transport_socket)); + if (name == "envoy.transport_sockets.tls") { + auto* typed_config = + envoy_config_core_v3_TransportSocket_typed_config(transport_socket); + if (typed_config != nullptr) { + const upb_strview 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 = CommonTlsContextParse( + common_tls_context, &downstream_tls_context->common_tls_context); + if (error != GRPC_ERROR_NONE) return 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); + } + } + if (downstream_tls_context->common_tls_context + .tls_certificate_certificate_provider_instance.instance_name + .empty()) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "TLS configuration provided but no " + "tls_certificate_certificate_provider_instance found."); + } + } + return GRPC_ERROR_NONE; +} + +grpc_error_handle CidrRangeParse( + const envoy_config_core_v3_CidrRange* cidr_range_proto, + XdsApi::LdsUpdate::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 (error != GRPC_ERROR_NONE) 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<const grpc_sockaddr*>(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++) { + XdsApi::LdsUpdate::FilterChainMap::CidrRange cidr_range; + grpc_error_handle error = CidrRangeParse(prefix_ranges[i], &cidr_range); + if (error != GRPC_ERROR_NONE) return error; + filter_chain_match->prefix_ranges.push_back(cidr_range); + } + filter_chain_match->source_type = + static_cast<XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType>( + 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++) { + XdsApi::LdsUpdate::FilterChainMap::CidrRange cidr_range; + grpc_error_handle error = + CidrRangeParse(source_prefix_ranges[i], &cidr_range); + if (error != GRPC_ERROR_NONE) 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 EncodingContext& context, + const envoy_config_listener_v3_FilterChain* filter_chain_proto, bool is_v2, + FilterChain* filter_chain) { + grpc_error_handle error = GRPC_ERROR_NONE; + auto* filter_chain_match = + envoy_config_listener_v3_FilterChain_filter_chain_match( + filter_chain_proto); + if (filter_chain_match != nullptr) { + error = FilterChainMatchParse(filter_chain_match, + &filter_chain->filter_chain_match); + if (error != GRPC_ERROR_NONE) return error; + } + // 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) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "FilterChain should have exactly one filter: HttpConnectionManager; no " + "other filter is supported at the moment"); + } + auto* typed_config = envoy_config_listener_v3_Filter_typed_config(filters[0]); + if (typed_config == nullptr) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No typed_config found in filter."); + } + 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") { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("Unsupported filter type ", type_url).c_str()); + } + const upb_strview 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) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Could not parse HttpConnectionManager config from filter " + "typed_config"); + } + filter_chain->filter_chain_data = + std::make_shared<XdsApi::LdsUpdate::FilterChainData>(); + error = HttpConnectionManagerParse( + false /* is_client */, context, http_connection_manager, is_v2, + &filter_chain->filter_chain_data->http_connection_manager); + if (error != GRPC_ERROR_NONE) return error; + // Get the DownstreamTlsContext for the filter chain + if (XdsSecurityEnabled()) { + auto* transport_socket = + envoy_config_listener_v3_FilterChain_transport_socket( + filter_chain_proto); + if (transport_socket != nullptr) { + error = DownstreamTlsContextParse( + context, transport_socket, + &filter_chain->filter_chain_data->downstream_tls_context); + } + } + return error; +} + +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_COPIED_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 +// XdsApi::LdsUpdate::FilterChainMap +struct InternalFilterChainMap { + using SourceIpMap = + std::map<std::string, XdsApi::LdsUpdate::FilterChainMap::SourceIp>; + using ConnectionSourceTypesArray = std::array<SourceIpMap, 3>; + struct DestinationIp { + absl::optional<XdsApi::LdsUpdate::FilterChainMap::CidrRange> prefix_range; + bool transport_protocol_raw_buffer_provided = false; + ConnectionSourceTypesArray source_types_array; + }; + using DestinationIpMap = std::map<std::string, DestinationIp>; + DestinationIpMap destination_ip_map; +}; + +grpc_error_handle AddFilterChainDataForSourcePort( + const FilterChain& filter_chain, + XdsApi::LdsUpdate::FilterChainMap::SourcePortsMap* ports_map, + uint32_t port) { + auto insert_result = ports_map->emplace( + port, XdsApi::LdsUpdate::FilterChainMap::FilterChainDataSharedPtr{ + filter_chain.filter_chain_data}); + if (!insert_result.second) { + return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat( + "Duplicate matching rules detected when adding filter chain: ", + filter_chain.filter_chain_match.ToString()) + .c_str()); + } + return GRPC_ERROR_NONE; +} + +grpc_error_handle AddFilterChainDataForSourcePorts( + const FilterChain& filter_chain, + XdsApi::LdsUpdate::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 (error != GRPC_ERROR_NONE) 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( + "", XdsApi::LdsUpdate::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 insert_result = source_ip_map->emplace( + absl::StrCat(grpc_sockaddr_to_string(&prefix_range.address, false), + "/", prefix_range.prefix_len), + XdsApi::LdsUpdate::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 (error != GRPC_ERROR_NONE) return error; + } + } + return GRPC_ERROR_NONE; +} + +grpc_error_handle AddFilterChainDataForSourceType( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIp* destination_ip) { + GPR_ASSERT(static_cast<unsigned int>( + filter_chain.filter_chain_match.source_type) < 3); + return AddFilterChainDataForSourceIpRange( + filter_chain, &destination_ip->source_types_array[static_cast<int>( + 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 insert_result = destination_ip_map->emplace( + absl::StrCat(grpc_sockaddr_to_string(&prefix_range.address, false), + "/", 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 (error != GRPC_ERROR_NONE) return error; + } + } + return GRPC_ERROR_NONE; +} + +XdsApi::LdsUpdate::FilterChainMap BuildFromInternalFilterChainMap( + InternalFilterChainMap* internal_filter_chain_map) { + XdsApi::LdsUpdate::FilterChainMap filter_chain_map; + for (auto& destination_ip_pair : + internal_filter_chain_map->destination_ip_map) { + XdsApi::LdsUpdate::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<FilterChain>& filter_chains, + XdsApi::LdsUpdate::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 (error != GRPC_ERROR_NONE) return error; + } + *filter_chain_map = + BuildFromInternalFilterChainMap(&internal_filter_chain_map); + return GRPC_ERROR_NONE; +} + +grpc_error_handle LdsResponseParseServer( + const EncodingContext& context, + const envoy_config_listener_v3_Listener* listener, bool is_v2, + XdsApi::LdsUpdate* lds_update) { + lds_update->type = XdsApi::LdsUpdate::ListenerType::kTcpListener; + grpc_error_handle error = + AddressParse(envoy_config_listener_v3_Listener_address(listener), + &lds_update->address); + if (error != GRPC_ERROR_NONE) 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<FilterChain> 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 (error != GRPC_ERROR_NONE) return error; + parsed_filter_chains.push_back(std::move(filter_chain)); + } + error = + BuildFilterChainMap(parsed_filter_chains, &lds_update->filter_chain_map); + if (error != GRPC_ERROR_NONE) 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 (error != GRPC_ERROR_NONE) 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 LdsResponseParse( + const EncodingContext& context, + const envoy_service_discovery_v3_DiscoveryResponse* response, + const std::set<absl::string_view>& expected_listener_names, + XdsApi::LdsUpdateMap* lds_update_map, + std::set<std::string>* resource_names_failed) { + std::vector<grpc_error_handle> errors; + // Get the resources from the response. + size_t size; + const google_protobuf_Any* const* resources = + envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size); + for (size_t i = 0; i < size; ++i) { + // Check the type_url of the resource. + absl::string_view type_url = + UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); + bool is_v2 = false; + if (!IsLds(type_url, &is_v2)) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Resource is not LDS.") + .c_str())); + continue; + } + // Decode the listener. + const upb_strview encoded_listener = + google_protobuf_Any_value(resources[i]); + const envoy_config_listener_v3_Listener* listener = + envoy_config_listener_v3_Listener_parse( + encoded_listener.data, encoded_listener.size, context.arena); + if (listener == nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Can't decode listener.") + .c_str())); + continue; + } + // Check listener name. Ignore unexpected listeners. + std::string listener_name = + UpbStringToStdString(envoy_config_listener_v3_Listener_name(listener)); + if (expected_listener_names.find(listener_name) == + expected_listener_names.end()) { + continue; + } + // Fail if listener name is duplicated. + if (lds_update_map->find(listener_name) != lds_update_map->end()) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("duplicate listener name \"", listener_name, "\"") + .c_str())); + resource_names_failed->insert(listener_name); + continue; + } + // Serialize into JSON and store it in the LdsUpdateMap + XdsApi::LdsResourceData& lds_resource_data = + (*lds_update_map)[listener_name]; + XdsApi::LdsUpdate& lds_update = lds_resource_data.resource; + lds_resource_data.serialized_proto = UpbStringToStdString(encoded_listener); + // 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); + if (api_listener != nullptr && address != nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(listener_name, + ": Listener has both address and ApiListener") + .c_str())); + resource_names_failed->insert(listener_name); + continue; + } + if (api_listener == nullptr && address == nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(listener_name, + ": Listener has neither address nor ApiListener") + .c_str())); + resource_names_failed->insert(listener_name); + continue; + } + grpc_error_handle error = GRPC_ERROR_NONE; + if (api_listener != nullptr) { + error = LdsResponseParseClient(context, api_listener, is_v2, &lds_update); + } else { + error = LdsResponseParseServer(context, listener, is_v2, &lds_update); + } + if (error != GRPC_ERROR_NONE) { + errors.push_back(grpc_error_add_child( + GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(listener_name, ": validation error").c_str()), + error)); + resource_names_failed->insert(listener_name); + } + } + return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing LDS response", &errors); +} + +grpc_error_handle RdsResponseParse( + const EncodingContext& context, + const envoy_service_discovery_v3_DiscoveryResponse* response, + const std::set<absl::string_view>& expected_route_configuration_names, + XdsApi::RdsUpdateMap* rds_update_map, + std::set<std::string>* resource_names_failed) { + std::vector<grpc_error_handle> errors; + // Get the resources from the response. + size_t size; + const google_protobuf_Any* const* resources = + envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size); + for (size_t i = 0; i < size; ++i) { + // Check the type_url of the resource. + absl::string_view type_url = + UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); + if (!IsRds(type_url)) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Resource is not RDS.") + .c_str())); + continue; + } + // Decode the route_config. + const upb_strview encoded_route_config = + google_protobuf_Any_value(resources[i]); + const envoy_config_route_v3_RouteConfiguration* route_config = + envoy_config_route_v3_RouteConfiguration_parse( + encoded_route_config.data, encoded_route_config.size, + context.arena); + if (route_config == nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Can't decode route_config.") + .c_str())); + continue; + } + // Check route_config_name. Ignore unexpected route_config. + std::string route_config_name = UpbStringToStdString( + envoy_config_route_v3_RouteConfiguration_name(route_config)); + if (expected_route_configuration_names.find(route_config_name) == + expected_route_configuration_names.end()) { + continue; + } + // Fail if route config name is duplicated. + if (rds_update_map->find(route_config_name) != rds_update_map->end()) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("duplicate route config name \"", route_config_name, + "\"") + .c_str())); + resource_names_failed->insert(route_config_name); + continue; + } + // Serialize into JSON and store it in the RdsUpdateMap + XdsApi::RdsResourceData& rds_resource_data = + (*rds_update_map)[route_config_name]; + XdsApi::RdsUpdate& rds_update = rds_resource_data.resource; + rds_resource_data.serialized_proto = + UpbStringToStdString(encoded_route_config); + // Parse the route_config. + grpc_error_handle error = + RouteConfigParse(context, route_config, &rds_update); + if (error != GRPC_ERROR_NONE) { + errors.push_back(grpc_error_add_child( + GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(route_config_name, ": validation error").c_str()), + error)); + resource_names_failed->insert(route_config_name); + } + } + return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing RDS response", &errors); +} + +grpc_error_handle CdsResponseParse( + const EncodingContext& context, const envoy_service_discovery_v3_DiscoveryResponse* response, const std::set<absl::string_view>& expected_cluster_names, - XdsApi::CdsUpdateMap* cds_update_map, upb_arena* arena) { + XdsApi::CdsUpdateMap* cds_update_map, + std::set<std::string>* resource_names_failed) { + std::vector<grpc_error_handle> errors; // Get the resources from the response. size_t size; const google_protobuf_Any* const* resources = @@ -1703,17 +2675,23 @@ grpc_error* CdsResponseParse( absl::string_view type_url = UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); if (!IsCds(type_url)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not CDS."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Resource is not CDS.") + .c_str())); + continue; } // Decode the cluster. const upb_strview encoded_cluster = google_protobuf_Any_value(resources[i]); const envoy_config_cluster_v3_Cluster* cluster = - envoy_config_cluster_v3_Cluster_parse(encoded_cluster.data, - encoded_cluster.size, arena); + envoy_config_cluster_v3_Cluster_parse( + encoded_cluster.data, encoded_cluster.size, context.arena); if (cluster == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode cluster."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Can't decode cluster.") + .c_str())); + continue; } - MaybeLogCluster(client, tracer, symtab, cluster); + MaybeLogCluster(context, cluster); // Ignore unexpected cluster names. std::string cluster_name = UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(cluster)); @@ -1723,41 +2701,194 @@ grpc_error* CdsResponseParse( } // Fail on duplicate resources. if (cds_update_map->find(cluster_name) != cds_update_map->end()) { - return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( absl::StrCat("duplicate resource name \"", cluster_name, "\"") - .c_str()); + .c_str())); + resource_names_failed->insert(cluster_name); + continue; } - XdsApi::CdsUpdate& cds_update = (*cds_update_map)[std::move(cluster_name)]; + // Serialize into JSON and store it in the CdsUpdateMap + XdsApi::CdsResourceData& cds_resource_data = + (*cds_update_map)[cluster_name]; + XdsApi::CdsUpdate& cds_update = cds_resource_data.resource; + cds_resource_data.serialized_proto = UpbStringToStdString(encoded_cluster); // Check the cluster_discovery_type. - if (!envoy_config_cluster_v3_Cluster_has_type(cluster)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType not found."); + if (!envoy_config_cluster_v3_Cluster_has_type(cluster) && + !envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": DiscoveryType not found.").c_str())); + resource_names_failed->insert(cluster_name); + continue; } - if (envoy_config_cluster_v3_Cluster_type(cluster) != + if (envoy_config_cluster_v3_Cluster_type(cluster) == envoy_config_cluster_v3_Cluster_EDS) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType is not EDS."); - } - // Check the EDS config source. - const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config = - envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); - const envoy_config_core_v3_ConfigSource* eds_config = - envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( - eds_cluster_config); - if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "EDS ConfigSource is not ADS."); - } - // Record EDS service_name (if any). - upb_strview service_name = - envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( - eds_cluster_config); - if (service_name.size != 0) { - cds_update.eds_service_name = UpbStringToStdString(service_name); + cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::EDS; + // Check the EDS config source. + const envoy_config_cluster_v3_Cluster_EdsClusterConfig* + eds_cluster_config = + envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); + const envoy_config_core_v3_ConfigSource* eds_config = + envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( + eds_cluster_config); + if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config)) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": EDS ConfigSource is not ADS.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + // Record EDS service_name (if any). + upb_strview service_name = + envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( + eds_cluster_config); + if (service_name.size != 0) { + cds_update.eds_service_name = UpbStringToStdString(service_name); + } + } else if (!XdsAggregateAndLogicalDnsClusterEnabled()) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": DiscoveryType is not valid.").c_str())); + resource_names_failed->insert(cluster_name); + continue; + } else if (envoy_config_cluster_v3_Cluster_type(cluster) == + envoy_config_cluster_v3_Cluster_LOGICAL_DNS) { + cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::LOGICAL_DNS; + } else { + if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { + const envoy_config_cluster_v3_Cluster_CustomClusterType* + custom_cluster_type = + envoy_config_cluster_v3_Cluster_cluster_type(cluster); + upb_strview type_name = + envoy_config_cluster_v3_Cluster_CustomClusterType_name( + custom_cluster_type); + if (UpbStringToAbsl(type_name) == "envoy.clusters.aggregate") { + cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::AGGREGATE; + // Retrieve aggregate clusters. + const google_protobuf_Any* typed_config = + envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config( + custom_cluster_type); + const upb_strview aggregate_cluster_config_upb_strview = + google_protobuf_Any_value(typed_config); + const envoy_extensions_clusters_aggregate_v3_ClusterConfig* + aggregate_cluster_config = + envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse( + aggregate_cluster_config_upb_strview.data, + aggregate_cluster_config_upb_strview.size, context.arena); + if (aggregate_cluster_config == nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": Can't parse aggregate cluster.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + size_t size; + const upb_strview* clusters = + envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters( + aggregate_cluster_config, &size); + for (size_t i = 0; i < size; ++i) { + const upb_strview cluster = clusters[i]; + cds_update.prioritized_cluster_names.emplace_back( + UpbStringToStdString(cluster)); + } + } else { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": DiscoveryType is not valid.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + } else { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": DiscoveryType is not valid.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } } // Check the LB policy. - if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) != + if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == envoy_config_cluster_v3_Cluster_ROUND_ROBIN) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "LB policy is not ROUND_ROBIN."); + cds_update.lb_policy = "ROUND_ROBIN"; + } else if (XdsRingHashEnabled() && + envoy_config_cluster_v3_Cluster_lb_policy(cluster) == + envoy_config_cluster_v3_Cluster_RING_HASH) { + cds_update.lb_policy = "RING_HASH"; + // Record ring hash lb config + auto* ring_hash_config = + envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster); + if (ring_hash_config == nullptr) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, + ": ring hash lb config required but not present.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + const google_protobuf_UInt64Value* max_ring_size = + envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size( + ring_hash_config); + if (max_ring_size != nullptr) { + cds_update.max_ring_size = + google_protobuf_UInt64Value_value(max_ring_size); + if (cds_update.max_ring_size > 8388608 || + cds_update.max_ring_size == 0) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat( + cluster_name, + ": max_ring_size is not in the range of 1 to 8388608.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + } + const google_protobuf_UInt64Value* min_ring_size = + envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size( + ring_hash_config); + if (min_ring_size != nullptr) { + cds_update.min_ring_size = + google_protobuf_UInt64Value_value(min_ring_size); + if (cds_update.min_ring_size > 8388608 || + cds_update.min_ring_size == 0) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat( + cluster_name, + ": min_ring_size is not in the range of 1 to 8388608.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + if (cds_update.min_ring_size > cds_update.max_ring_size) { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat( + cluster_name, + ": min_ring_size cannot be greater than max_ring_size.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + } + if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( + ring_hash_config) == + envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) { + cds_update.hash_function = XdsApi::CdsUpdate::HashFunction::XX_HASH; + } else if ( + envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( + ring_hash_config) == + envoy_config_cluster_v3_Cluster_RingHashLbConfig_MURMUR_HASH_2) { + cds_update.hash_function = + XdsApi::CdsUpdate::HashFunction::MURMUR_HASH_2; + } else { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, + ": ring hash lb config has invalid hash function.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; + } + } else { + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": LB policy is not supported.").c_str())); + resource_names_failed->insert(cluster_name); + continue; } if (XdsSecurityEnabled()) { // Record Upstream tls context @@ -1776,26 +2907,43 @@ grpc_error* CdsResponseParse( auto* upstream_tls_context = envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( encoded_upstream_tls_context.data, - encoded_upstream_tls_context.size, arena); + encoded_upstream_tls_context.size, context.arena); if (upstream_tls_context == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Can't decode upstream tls context."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, + ": Can't decode upstream tls context.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; } auto* common_tls_context = envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context( upstream_tls_context); if (common_tls_context != nullptr) { - grpc_error* error = CommonTlsContextParse( + grpc_error_handle error = CommonTlsContextParse( common_tls_context, &cds_update.common_tls_context); - if (error != GRPC_ERROR_NONE) return error; + if (error != GRPC_ERROR_NONE) { + errors.push_back(grpc_error_add_child( + GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": error in TLS context") + .c_str()), + error)); + resource_names_failed->insert(cluster_name); + continue; + } } } if (cds_update.common_tls_context.combined_validation_context .validation_context_certificate_provider_instance .instance_name.empty()) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "TLS configuration provided but no " - "validation_context_certificate_provider_instance found."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, + "TLS configuration provided but no " + "validation_context_certificate_provider_instance " + "found.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; } } } @@ -1805,8 +2953,11 @@ grpc_error* CdsResponseParse( envoy_config_cluster_v3_Cluster_lrs_server(cluster); if (lrs_server != nullptr) { if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "LRS ConfigSource is not self."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(cluster_name, ": LRS ConfigSource is not self.") + .c_str())); + resource_names_failed->insert(cluster_name); + continue; } cds_update.lrs_load_reporting_server_name.emplace(""); } @@ -1837,10 +2988,10 @@ grpc_error* CdsResponseParse( } } } - return GRPC_ERROR_NONE; + return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing CDS response", &errors); } -grpc_error* ServerAddressParseAndAppend( +grpc_error_handle ServerAddressParseAndAppend( const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, ServerAddressList* list) { // If health_status is not HEALTHY or UNKNOWN, skip this endpoint. @@ -1865,13 +3016,15 @@ grpc_error* ServerAddressParseAndAppend( } // Populate grpc_resolved_address. grpc_resolved_address addr; - grpc_string_to_sockaddr(&addr, address_str.c_str(), port); + grpc_error_handle error = + grpc_string_to_sockaddr(&addr, address_str.c_str(), port); + if (error != GRPC_ERROR_NONE) return error; // Append the address to the list. list->emplace_back(addr, nullptr); return GRPC_ERROR_NONE; } -grpc_error* LocalityParse( +grpc_error_handle LocalityParse( const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, XdsApi::EdsUpdate::Priority::Locality* output_locality, size_t* priority) { // Parse LB weight. @@ -1888,6 +3041,9 @@ grpc_error* LocalityParse( const envoy_config_core_v3_Locality* locality = envoy_config_endpoint_v3_LocalityLbEndpoints_locality( locality_lb_endpoints); + if (locality == nullptr) { + return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty locality."); + } std::string region = UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); std::string zone = @@ -1902,7 +3058,7 @@ grpc_error* LocalityParse( envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints( locality_lb_endpoints, &size); for (size_t i = 0; i < size; ++i) { - grpc_error* error = ServerAddressParseAndAppend( + grpc_error_handle error = ServerAddressParseAndAppend( lb_endpoints[i], &output_locality->endpoints); if (error != GRPC_ERROR_NONE) return error; } @@ -1912,7 +3068,7 @@ grpc_error* LocalityParse( return GRPC_ERROR_NONE; } -grpc_error* DropParseAndAppend( +grpc_error_handle DropParseAndAppend( const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* drop_overload, XdsApi::EdsUpdate::DropConfig* drop_config) { @@ -1951,11 +3107,13 @@ grpc_error* DropParseAndAppend( return GRPC_ERROR_NONE; } -grpc_error* EdsResponseParse( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, +grpc_error_handle EdsResponseParse( + const EncodingContext& context, const envoy_service_discovery_v3_DiscoveryResponse* response, const std::set<absl::string_view>& expected_eds_service_names, - XdsApi::EdsUpdateMap* eds_update_map, upb_arena* arena) { + XdsApi::EdsUpdateMap* eds_update_map, + std::set<std::string>* resource_names_failed) { + std::vector<grpc_error_handle> errors; // Get the resources from the response. size_t size; const google_protobuf_Any* const* resources = @@ -1965,7 +3123,10 @@ grpc_error* EdsResponseParse( absl::string_view type_url = UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])); if (!IsEds(type_url)) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not EDS."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, ": Resource is not EDS.") + .c_str())); + continue; } // Get the cluster_load_assignment. upb_strview encoded_cluster_load_assignment = @@ -1973,13 +3134,15 @@ grpc_error* EdsResponseParse( envoy_config_endpoint_v3_ClusterLoadAssignment* cluster_load_assignment = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( encoded_cluster_load_assignment.data, - encoded_cluster_load_assignment.size, arena); + encoded_cluster_load_assignment.size, context.arena); if (cluster_load_assignment == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Can't parse cluster_load_assignment."); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat("resource index ", i, + ": Can't parse cluster_load_assignment.") + .c_str())); + continue; } - MaybeLogClusterLoadAssignment(client, tracer, symtab, - cluster_load_assignment); + MaybeLogClusterLoadAssignment(context, cluster_load_assignment); // Check the EDS service name. Ignore unexpected names. std::string eds_service_name = UpbStringToStdString( envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name( @@ -1990,22 +3153,29 @@ grpc_error* EdsResponseParse( } // Fail on duplicate resources. if (eds_update_map->find(eds_service_name) != eds_update_map->end()) { - return GRPC_ERROR_CREATE_FROM_COPIED_STRING( + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( absl::StrCat("duplicate resource name \"", eds_service_name, "\"") - .c_str()); + .c_str())); + resource_names_failed->insert(eds_service_name); + continue; } - XdsApi::EdsUpdate& eds_update = - (*eds_update_map)[std::move(eds_service_name)]; + // Serialize into JSON and store it in the EdsUpdateMap + XdsApi::EdsResourceData& eds_resource_data = + (*eds_update_map)[eds_service_name]; + XdsApi::EdsUpdate& eds_update = eds_resource_data.resource; + eds_resource_data.serialized_proto = + UpbStringToStdString(encoded_cluster_load_assignment); // Get the endpoints. size_t locality_size; const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( cluster_load_assignment, &locality_size); + grpc_error_handle error = GRPC_ERROR_NONE; for (size_t j = 0; j < locality_size; ++j) { size_t priority; XdsApi::EdsUpdate::Priority::Locality locality; - grpc_error* error = LocalityParse(endpoints[j], &locality, &priority); - if (error != GRPC_ERROR_NONE) return error; + error = LocalityParse(endpoints[j], &locality, &priority); + if (error != GRPC_ERROR_NONE) break; // Filter out locality with weight 0. if (locality.lb_weight == 0) continue; // Make sure prorities is big enough. Note that they might not @@ -2016,10 +3186,21 @@ grpc_error* EdsResponseParse( eds_update.priorities[priority].localities.emplace(locality.name.get(), std::move(locality)); } + if (error != GRPC_ERROR_NONE) { + errors.push_back(grpc_error_add_child( + GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(eds_service_name, ": locality validation error") + .c_str()), + error)); + resource_names_failed->insert(eds_service_name); + continue; + } for (const auto& priority : eds_update.priorities) { if (priority.localities.empty()) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "EDS update includes sparse priority list"); + errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(eds_service_name, ": sparse priority list").c_str())); + resource_names_failed->insert(eds_service_name); + continue; } } // Get the drop config. @@ -2034,13 +3215,22 @@ grpc_error* EdsResponseParse( envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( policy, &drop_size); for (size_t j = 0; j < drop_size; ++j) { - grpc_error* error = + error = DropParseAndAppend(drop_overload[j], eds_update.drop_config.get()); - if (error != GRPC_ERROR_NONE) return error; + if (error != GRPC_ERROR_NONE) break; + } + if (error != GRPC_ERROR_NONE) { + errors.push_back(grpc_error_add_child( + GRPC_ERROR_CREATE_FROM_COPIED_STRING( + absl::StrCat(eds_service_name, ": drop config validation error") + .c_str()), + error)); + resource_names_failed->insert(eds_service_name); + continue; } } } - return GRPC_ERROR_NONE; + return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing EDS response", &errors); } std::string TypeUrlInternalToExternal(absl::string_view type_url) { @@ -2056,16 +3246,27 @@ std::string TypeUrlInternalToExternal(absl::string_view type_url) { return std::string(type_url); } +template <typename UpdateMap> +void MoveUpdatesToFailedSet(UpdateMap* update_map, + std::set<std::string>* resource_names_failed) { + for (const auto& p : *update_map) { + resource_names_failed->insert(p.first); + } + update_map->clear(); +} + } // namespace XdsApi::AdsParseResult XdsApi::ParseAdsResponse( - const grpc_slice& encoded_response, + const XdsBootstrap::XdsServer& server, const grpc_slice& encoded_response, const std::set<absl::string_view>& expected_listener_names, const std::set<absl::string_view>& expected_route_configuration_names, const std::set<absl::string_view>& expected_cluster_names, const std::set<absl::string_view>& expected_eds_service_names) { AdsParseResult result; upb::Arena arena; + const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(), + server.ShouldUseV3()}; // Decode the response. const envoy_service_discovery_v3_DiscoveryResponse* response = envoy_service_discovery_v3_DiscoveryResponse_parse( @@ -2077,7 +3278,7 @@ XdsApi::AdsParseResult XdsApi::ParseAdsResponse( GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode DiscoveryResponse."); return result; } - MaybeLogDiscoveryResponse(client_, tracer_, symtab_.ptr(), response); + MaybeLogDiscoveryResponse(context, response); // Record the type_url, the version_info, and the nonce of the response. result.type_url = TypeUrlInternalToExternal(UpbStringToAbsl( envoy_service_discovery_v3_DiscoveryResponse_type_url(response))); @@ -2087,22 +3288,37 @@ XdsApi::AdsParseResult XdsApi::ParseAdsResponse( envoy_service_discovery_v3_DiscoveryResponse_nonce(response)); // Parse the response according to the resource type. if (IsLds(result.type_url)) { - result.parse_error = LdsResponseParse(client_, tracer_, symtab_.ptr(), - response, expected_listener_names, - &result.lds_update_map, arena.ptr()); + result.parse_error = + LdsResponseParse(context, response, expected_listener_names, + &result.lds_update_map, &result.resource_names_failed); + if (result.parse_error != GRPC_ERROR_NONE) { + MoveUpdatesToFailedSet(&result.lds_update_map, + &result.resource_names_failed); + } } else if (IsRds(result.type_url)) { result.parse_error = - RdsResponseParse(client_, tracer_, symtab_.ptr(), response, - expected_route_configuration_names, - &result.rds_update_map, arena.ptr()); + RdsResponseParse(context, response, expected_route_configuration_names, + &result.rds_update_map, &result.resource_names_failed); + if (result.parse_error != GRPC_ERROR_NONE) { + MoveUpdatesToFailedSet(&result.rds_update_map, + &result.resource_names_failed); + } } else if (IsCds(result.type_url)) { - result.parse_error = CdsResponseParse(client_, tracer_, symtab_.ptr(), - response, expected_cluster_names, - &result.cds_update_map, arena.ptr()); + result.parse_error = + CdsResponseParse(context, response, expected_cluster_names, + &result.cds_update_map, &result.resource_names_failed); + if (result.parse_error != GRPC_ERROR_NONE) { + MoveUpdatesToFailedSet(&result.cds_update_map, + &result.resource_names_failed); + } } else if (IsEds(result.type_url)) { - result.parse_error = EdsResponseParse(client_, tracer_, symtab_.ptr(), - response, expected_eds_service_names, - &result.eds_update_map, arena.ptr()); + result.parse_error = + EdsResponseParse(context, response, expected_eds_service_names, + &result.eds_update_map, &result.resource_names_failed); + if (result.parse_error != GRPC_ERROR_NONE) { + MoveUpdatesToFailedSet(&result.eds_update_map, + &result.resource_names_failed); + } } return result; } @@ -2110,25 +3326,25 @@ XdsApi::AdsParseResult XdsApi::ParseAdsResponse( namespace { void MaybeLogLrsRequest( - XdsClient* client, TraceFlag* tracer, upb_symtab* symtab, + const EncodingContext& context, const envoy_service_load_stats_v3_LoadStatsRequest* request) { - if (GRPC_TRACE_FLAG_ENABLED(*tracer) && + if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { const upb_msgdef* msg_type = - envoy_service_load_stats_v3_LoadStatsRequest_getmsgdef(symtab); + envoy_service_load_stats_v3_LoadStatsRequest_getmsgdef(context.symtab); char buf[10240]; upb_text_encode(request, msg_type, nullptr, 0, buf, sizeof(buf)); - gpr_log(GPR_DEBUG, "[xds_client %p] constructed LRS request: %s", client, - buf); + gpr_log(GPR_DEBUG, "[xds_client %p] constructed LRS request: %s", + context.client, buf); } } grpc_slice SerializeLrsRequest( - const envoy_service_load_stats_v3_LoadStatsRequest* request, - upb_arena* arena) { + const EncodingContext& context, + const envoy_service_load_stats_v3_LoadStatsRequest* request) { size_t output_length; char* output = envoy_service_load_stats_v3_LoadStatsRequest_serialize( - request, arena, &output_length); + request, context.arena, &output_length); return grpc_slice_from_copied_buffer(output, output_length); } @@ -2137,6 +3353,8 @@ grpc_slice SerializeLrsRequest( grpc_slice XdsApi::CreateLrsInitialRequest( const XdsBootstrap::XdsServer& server) { upb::Arena arena; + const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(), + server.ShouldUseV3()}; // Create a request. envoy_service_load_stats_v3_LoadStatsRequest* request = envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr()); @@ -2144,25 +3362,25 @@ grpc_slice XdsApi::CreateLrsInitialRequest( envoy_config_core_v3_Node* node_msg = envoy_service_load_stats_v3_LoadStatsRequest_mutable_node(request, arena.ptr()); - PopulateNode(arena.ptr(), node_, server.ShouldUseV3(), build_version_, - user_agent_name_, node_msg); + PopulateNode(context, node_, build_version_, user_agent_name_, node_msg); envoy_config_core_v3_Node_add_client_features( node_msg, upb_strview_makez("envoy.lrs.supports_send_all_clusters"), arena.ptr()); - MaybeLogLrsRequest(client_, tracer_, symtab_.ptr(), request); - return SerializeLrsRequest(request, arena.ptr()); + MaybeLogLrsRequest(context, request); + return SerializeLrsRequest(context, request); } namespace { void LocalityStatsPopulate( + const EncodingContext& context, envoy_config_endpoint_v3_UpstreamLocalityStats* output, const XdsLocalityName& locality_name, - const XdsClusterLocalityStats::Snapshot& snapshot, upb_arena* arena) { + const XdsClusterLocalityStats::Snapshot& snapshot) { // Set locality. envoy_config_core_v3_Locality* locality = - envoy_config_endpoint_v3_UpstreamLocalityStats_mutable_locality(output, - arena); + envoy_config_endpoint_v3_UpstreamLocalityStats_mutable_locality( + output, context.arena); if (!locality_name.region().empty()) { envoy_config_core_v3_Locality_set_region( locality, StdStringToUpbString(locality_name.region())); @@ -2190,7 +3408,7 @@ void LocalityStatsPopulate( const XdsClusterLocalityStats::BackendMetric& metric_value = p.second; envoy_config_endpoint_v3_EndpointLoadMetricStats* load_metric = envoy_config_endpoint_v3_UpstreamLocalityStats_add_load_metric_stats( - output, arena); + output, context.arena); envoy_config_endpoint_v3_EndpointLoadMetricStats_set_metric_name( load_metric, StdStringToUpbString(metric_name)); envoy_config_endpoint_v3_EndpointLoadMetricStats_set_num_requests_finished_with_metric( @@ -2205,6 +3423,8 @@ void LocalityStatsPopulate( grpc_slice XdsApi::CreateLrsRequest( ClusterLoadReportMap cluster_load_report_map) { upb::Arena arena; + const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(), + false}; // Create a request. envoy_service_load_stats_v3_LoadStatsRequest* request = envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr()); @@ -2231,8 +3451,7 @@ grpc_slice XdsApi::CreateLrsRequest( envoy_config_endpoint_v3_UpstreamLocalityStats* locality_stats = envoy_config_endpoint_v3_ClusterStats_add_upstream_locality_stats( cluster_stats, arena.ptr()); - LocalityStatsPopulate(locality_stats, locality_name, snapshot, - arena.ptr()); + LocalityStatsPopulate(context, locality_stats, locality_name, snapshot); } // Add dropped requests. uint64_t total_dropped_requests = 0; @@ -2261,14 +3480,14 @@ grpc_slice XdsApi::CreateLrsRequest( google_protobuf_Duration_set_seconds(load_report_interval, timespec.tv_sec); google_protobuf_Duration_set_nanos(load_report_interval, timespec.tv_nsec); } - MaybeLogLrsRequest(client_, tracer_, symtab_.ptr(), request); - return SerializeLrsRequest(request, arena.ptr()); + MaybeLogLrsRequest(context, request); + return SerializeLrsRequest(context, request); } -grpc_error* XdsApi::ParseLrsResponse(const grpc_slice& encoded_response, - bool* send_all_clusters, - std::set<std::string>* cluster_names, - grpc_millis* load_reporting_interval) { +grpc_error_handle XdsApi::ParseLrsResponse( + const grpc_slice& encoded_response, bool* send_all_clusters, + std::set<std::string>* cluster_names, + grpc_millis* load_reporting_interval) { upb::Arena arena; // Decode the response. const envoy_service_load_stats_v3_LoadStatsResponse* decoded_response = @@ -2305,4 +3524,276 @@ grpc_error* XdsApi::ParseLrsResponse(const grpc_slice& encoded_response, return GRPC_ERROR_NONE; } +namespace { +google_protobuf_Timestamp* GrpcMillisToTimestamp(const EncodingContext& context, + grpc_millis value) { + google_protobuf_Timestamp* timestamp = + google_protobuf_Timestamp_new(context.arena); + gpr_timespec timespec = grpc_millis_to_timespec(value, GPR_CLOCK_REALTIME); + google_protobuf_Timestamp_set_seconds(timestamp, timespec.tv_sec); + google_protobuf_Timestamp_set_nanos(timestamp, timespec.tv_nsec); + return timestamp; +} + +envoy_admin_v3_UpdateFailureState* CreateUpdateFailureStateUpb( + const EncodingContext& context, + const XdsApi::ResourceMetadata* resource_metadata) { + auto* update_failure_state = + envoy_admin_v3_UpdateFailureState_new(context.arena); + envoy_admin_v3_UpdateFailureState_set_details( + update_failure_state, + StdStringToUpbString(resource_metadata->failed_details)); + envoy_admin_v3_UpdateFailureState_set_version_info( + update_failure_state, + StdStringToUpbString(resource_metadata->failed_version)); + envoy_admin_v3_UpdateFailureState_set_last_update_attempt( + update_failure_state, + GrpcMillisToTimestamp(context, resource_metadata->failed_update_time)); + return update_failure_state; +} + +void DumpLdsConfig(const EncodingContext& context, + const XdsApi::ResourceTypeMetadata& resource_type_metadata, + envoy_service_status_v3_PerXdsConfig* per_xds_config) { + upb_strview kLdsTypeUrlUpb = upb_strview_makez(XdsApi::kLdsTypeUrl); + auto* listener_config_dump = + envoy_service_status_v3_PerXdsConfig_mutable_listener_config( + per_xds_config, context.arena); + envoy_admin_v3_ListenersConfigDump_set_version_info( + listener_config_dump, + StdStringToUpbString(resource_type_metadata.version)); + for (auto& p : resource_type_metadata.resource_metadata_map) { + absl::string_view name = p.first; + const XdsApi::ResourceMetadata* meta = p.second; + const upb_strview name_upb = StdStringToUpbString(name); + auto* dynamic_listener = + envoy_admin_v3_ListenersConfigDump_add_dynamic_listeners( + listener_config_dump, context.arena); + envoy_admin_v3_ListenersConfigDump_DynamicListener_set_name( + dynamic_listener, name_upb); + envoy_admin_v3_ListenersConfigDump_DynamicListener_set_client_status( + dynamic_listener, meta->client_status); + if (!meta->serialized_proto.empty()) { + // Set in-effective listeners + auto* dynamic_listener_state = + envoy_admin_v3_ListenersConfigDump_DynamicListener_mutable_active_state( + dynamic_listener, context.arena); + envoy_admin_v3_ListenersConfigDump_DynamicListenerState_set_version_info( + dynamic_listener_state, StdStringToUpbString(meta->version)); + envoy_admin_v3_ListenersConfigDump_DynamicListenerState_set_last_updated( + dynamic_listener_state, + GrpcMillisToTimestamp(context, meta->update_time)); + auto* listener_any = + envoy_admin_v3_ListenersConfigDump_DynamicListenerState_mutable_listener( + dynamic_listener_state, context.arena); + google_protobuf_Any_set_type_url(listener_any, kLdsTypeUrlUpb); + google_protobuf_Any_set_value( + listener_any, StdStringToUpbString(meta->serialized_proto)); + } + if (meta->client_status == XdsApi::ResourceMetadata::NACKED) { + // Set error_state if NACKED + envoy_admin_v3_ListenersConfigDump_DynamicListener_set_error_state( + dynamic_listener, CreateUpdateFailureStateUpb(context, meta)); + } + } +} + +void DumpRdsConfig(const EncodingContext& context, + const XdsApi::ResourceTypeMetadata& resource_type_metadata, + envoy_service_status_v3_PerXdsConfig* per_xds_config) { + upb_strview kRdsTypeUrlUpb = upb_strview_makez(XdsApi::kRdsTypeUrl); + auto* route_config_dump = + envoy_service_status_v3_PerXdsConfig_mutable_route_config(per_xds_config, + context.arena); + for (auto& p : resource_type_metadata.resource_metadata_map) { + absl::string_view name = p.first; + const XdsApi::ResourceMetadata* meta = p.second; + const upb_strview name_upb = StdStringToUpbString(name); + auto* dynamic_route_config = + envoy_admin_v3_RoutesConfigDump_add_dynamic_route_configs( + route_config_dump, context.arena); + envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_client_status( + dynamic_route_config, meta->client_status); + auto* route_config_any = + envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_mutable_route_config( + dynamic_route_config, context.arena); + if (!meta->serialized_proto.empty()) { + // Set in-effective route configs + envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_version_info( + dynamic_route_config, StdStringToUpbString(meta->version)); + envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_last_updated( + dynamic_route_config, + GrpcMillisToTimestamp(context, meta->update_time)); + google_protobuf_Any_set_type_url(route_config_any, kRdsTypeUrlUpb); + google_protobuf_Any_set_value( + route_config_any, StdStringToUpbString(meta->serialized_proto)); + } else { + // If there isn't a working route config, we still need to print the + // name. + auto* route_config = + envoy_config_route_v3_RouteConfiguration_new(context.arena); + envoy_config_route_v3_RouteConfiguration_set_name(route_config, name_upb); + size_t length; + char* bytes = envoy_config_route_v3_RouteConfiguration_serialize( + route_config, context.arena, &length); + google_protobuf_Any_set_type_url(route_config_any, kRdsTypeUrlUpb); + google_protobuf_Any_set_value(route_config_any, + upb_strview_make(bytes, length)); + } + if (meta->client_status == XdsApi::ResourceMetadata::NACKED) { + // Set error_state if NACKED + envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_error_state( + dynamic_route_config, CreateUpdateFailureStateUpb(context, meta)); + } + } +} + +void DumpCdsConfig(const EncodingContext& context, + const XdsApi::ResourceTypeMetadata& resource_type_metadata, + envoy_service_status_v3_PerXdsConfig* per_xds_config) { + upb_strview kCdsTypeUrlUpb = upb_strview_makez(XdsApi::kCdsTypeUrl); + auto* cluster_config_dump = + envoy_service_status_v3_PerXdsConfig_mutable_cluster_config( + per_xds_config, context.arena); + envoy_admin_v3_ClustersConfigDump_set_version_info( + cluster_config_dump, + StdStringToUpbString(resource_type_metadata.version)); + for (auto& p : resource_type_metadata.resource_metadata_map) { + absl::string_view name = p.first; + const XdsApi::ResourceMetadata* meta = p.second; + const upb_strview name_upb = StdStringToUpbString(name); + auto* dynamic_cluster = + envoy_admin_v3_ClustersConfigDump_add_dynamic_active_clusters( + cluster_config_dump, context.arena); + envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_client_status( + dynamic_cluster, meta->client_status); + auto* cluster_any = + envoy_admin_v3_ClustersConfigDump_DynamicCluster_mutable_cluster( + dynamic_cluster, context.arena); + if (!meta->serialized_proto.empty()) { + // Set in-effective clusters + envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_version_info( + dynamic_cluster, StdStringToUpbString(meta->version)); + envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_last_updated( + dynamic_cluster, GrpcMillisToTimestamp(context, meta->update_time)); + google_protobuf_Any_set_type_url(cluster_any, kCdsTypeUrlUpb); + google_protobuf_Any_set_value( + cluster_any, StdStringToUpbString(meta->serialized_proto)); + } else { + // If there isn't a working cluster, we still need to print the name. + auto* cluster = envoy_config_cluster_v3_Cluster_new(context.arena); + envoy_config_cluster_v3_Cluster_set_name(cluster, name_upb); + size_t length; + char* bytes = envoy_config_cluster_v3_Cluster_serialize( + cluster, context.arena, &length); + google_protobuf_Any_set_type_url(cluster_any, kCdsTypeUrlUpb); + google_protobuf_Any_set_value(cluster_any, + upb_strview_make(bytes, length)); + } + if (meta->client_status == XdsApi::ResourceMetadata::NACKED) { + // Set error_state if NACKED + envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_error_state( + dynamic_cluster, CreateUpdateFailureStateUpb(context, meta)); + } + } +} + +void DumpEdsConfig(const EncodingContext& context, + const XdsApi::ResourceTypeMetadata& resource_type_metadata, + envoy_service_status_v3_PerXdsConfig* per_xds_config) { + upb_strview kEdsTypeUrlUpb = upb_strview_makez(XdsApi::kEdsTypeUrl); + auto* endpoint_config_dump = + envoy_service_status_v3_PerXdsConfig_mutable_endpoint_config( + per_xds_config, context.arena); + for (auto& p : resource_type_metadata.resource_metadata_map) { + absl::string_view name = p.first; + const XdsApi::ResourceMetadata* meta = p.second; + const upb_strview name_upb = StdStringToUpbString(name); + auto* dynamic_endpoint = + envoy_admin_v3_EndpointsConfigDump_add_dynamic_endpoint_configs( + endpoint_config_dump, context.arena); + envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_client_status( + dynamic_endpoint, meta->client_status); + auto* endpoint_any = + envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_mutable_endpoint_config( + dynamic_endpoint, context.arena); + if (!meta->serialized_proto.empty()) { + // Set in-effective endpoints + envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_version_info( + dynamic_endpoint, StdStringToUpbString(meta->version)); + envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_last_updated( + dynamic_endpoint, GrpcMillisToTimestamp(context, meta->update_time)); + google_protobuf_Any_set_type_url(endpoint_any, kEdsTypeUrlUpb); + google_protobuf_Any_set_value( + endpoint_any, StdStringToUpbString(meta->serialized_proto)); + } else { + // If there isn't a working endpoint, we still need to print the name. + auto* cluster_load_assignment = + envoy_config_endpoint_v3_ClusterLoadAssignment_new(context.arena); + envoy_config_endpoint_v3_ClusterLoadAssignment_set_cluster_name( + cluster_load_assignment, name_upb); + size_t length; + char* bytes = envoy_config_endpoint_v3_ClusterLoadAssignment_serialize( + cluster_load_assignment, context.arena, &length); + google_protobuf_Any_set_type_url(endpoint_any, kEdsTypeUrlUpb); + google_protobuf_Any_set_value(endpoint_any, + upb_strview_make(bytes, length)); + } + if (meta->client_status == XdsApi::ResourceMetadata::NACKED) { + // Set error_state if NACKED + envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_error_state( + dynamic_endpoint, CreateUpdateFailureStateUpb(context, meta)); + } + } +} + +} // namespace + +std::string XdsApi::AssembleClientConfig( + const ResourceTypeMetadataMap& resource_type_metadata_map) { + upb::Arena arena; + // Create the ClientConfig for resource metadata from XdsClient + auto* client_config = envoy_service_status_v3_ClientConfig_new(arena.ptr()); + // Fill-in the node information + auto* node = envoy_service_status_v3_ClientConfig_mutable_node(client_config, + arena.ptr()); + const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(), + true}; + PopulateNode(context, node_, build_version_, user_agent_name_, node); + // Dump each xDS-type config into PerXdsConfig + for (auto& p : resource_type_metadata_map) { + absl::string_view type_url = p.first; + const ResourceTypeMetadata& resource_type_metadata = p.second; + if (type_url == kLdsTypeUrl) { + auto* per_xds_config = + envoy_service_status_v3_ClientConfig_add_xds_config(client_config, + context.arena); + DumpLdsConfig(context, resource_type_metadata, per_xds_config); + } else if (type_url == kRdsTypeUrl) { + auto* per_xds_config = + envoy_service_status_v3_ClientConfig_add_xds_config(client_config, + context.arena); + DumpRdsConfig(context, resource_type_metadata, per_xds_config); + } else if (type_url == kCdsTypeUrl) { + auto* per_xds_config = + envoy_service_status_v3_ClientConfig_add_xds_config(client_config, + context.arena); + DumpCdsConfig(context, resource_type_metadata, per_xds_config); + } else if (type_url == kEdsTypeUrl) { + auto* per_xds_config = + envoy_service_status_v3_ClientConfig_add_xds_config(client_config, + context.arena); + DumpEdsConfig(context, resource_type_metadata, per_xds_config); + } else { + gpr_log(GPR_ERROR, "invalid type_url %s", std::string(type_url).c_str()); + return ""; + } + } + // Serialize the upb message to bytes + size_t output_length; + char* output = envoy_service_status_v3_ClientConfig_serialize( + client_config, arena.ptr(), &output_length); + return std::string(output, output_length); +} + } // namespace grpc_core |