diff --git a/CMakeLists.txt b/CMakeLists.txt index 05fa556d4b5..10e5589b360 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2657,18 +2657,24 @@ add_library(grpc src/core/xds/grpc/xds_certificate_provider.cc src/core/xds/grpc/xds_client_grpc.cc src/core/xds/grpc/xds_cluster.cc + src/core/xds/grpc/xds_cluster_parser.cc src/core/xds/grpc/xds_cluster_specifier_plugin.cc src/core/xds/grpc/xds_common_types.cc + src/core/xds/grpc/xds_common_types_parser.cc src/core/xds/grpc/xds_endpoint.cc + src/core/xds/grpc/xds_endpoint_parser.cc src/core/xds/grpc/xds_health_status.cc src/core/xds/grpc/xds_http_fault_filter.cc - src/core/xds/grpc/xds_http_filters.cc + src/core/xds/grpc/xds_http_filter_registry.cc src/core/xds/grpc/xds_http_rbac_filter.cc src/core/xds/grpc/xds_http_stateful_session_filter.cc src/core/xds/grpc/xds_lb_policy_registry.cc src/core/xds/grpc/xds_listener.cc + src/core/xds/grpc/xds_listener_parser.cc src/core/xds/grpc/xds_route_config.cc + src/core/xds/grpc/xds_route_config_parser.cc src/core/xds/grpc/xds_routing.cc + src/core/xds/grpc/xds_server_grpc.cc src/core/xds/grpc/xds_transport_grpc.cc src/core/xds/xds_client/xds_api.cc src/core/xds/xds_client/xds_bootstrap.cc diff --git a/Makefile b/Makefile index b7daf196bc9..8db5503b558 100644 --- a/Makefile +++ b/Makefile @@ -1491,18 +1491,24 @@ LIBGRPC_SRC = \ src/core/xds/grpc/xds_certificate_provider.cc \ src/core/xds/grpc/xds_client_grpc.cc \ src/core/xds/grpc/xds_cluster.cc \ + src/core/xds/grpc/xds_cluster_parser.cc \ src/core/xds/grpc/xds_cluster_specifier_plugin.cc \ src/core/xds/grpc/xds_common_types.cc \ + src/core/xds/grpc/xds_common_types_parser.cc \ src/core/xds/grpc/xds_endpoint.cc \ + src/core/xds/grpc/xds_endpoint_parser.cc \ src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_http_fault_filter.cc \ - src/core/xds/grpc/xds_http_filters.cc \ + src/core/xds/grpc/xds_http_filter_registry.cc \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ src/core/xds/grpc/xds_lb_policy_registry.cc \ src/core/xds/grpc/xds_listener.cc \ + src/core/xds/grpc/xds_listener_parser.cc \ src/core/xds/grpc/xds_route_config.cc \ + src/core/xds/grpc/xds_route_config_parser.cc \ src/core/xds/grpc/xds_routing.cc \ + src/core/xds/grpc/xds_server_grpc.cc \ src/core/xds/grpc/xds_transport_grpc.cc \ src/core/xds/xds_client/xds_api.cc \ src/core/xds/xds_client/xds_bootstrap.cc \ diff --git a/Package.swift b/Package.swift index 1b5e8ceccfe..ddceef95c22 100644 --- a/Package.swift +++ b/Package.swift @@ -1975,18 +1975,25 @@ let package = Package( "src/core/xds/grpc/xds_client_grpc.h", "src/core/xds/grpc/xds_cluster.cc", "src/core/xds/grpc/xds_cluster.h", + "src/core/xds/grpc/xds_cluster_parser.cc", + "src/core/xds/grpc/xds_cluster_parser.h", "src/core/xds/grpc/xds_cluster_specifier_plugin.cc", "src/core/xds/grpc/xds_cluster_specifier_plugin.h", "src/core/xds/grpc/xds_common_types.cc", "src/core/xds/grpc/xds_common_types.h", + "src/core/xds/grpc/xds_common_types_parser.cc", + "src/core/xds/grpc/xds_common_types_parser.h", "src/core/xds/grpc/xds_endpoint.cc", "src/core/xds/grpc/xds_endpoint.h", + "src/core/xds/grpc/xds_endpoint_parser.cc", + "src/core/xds/grpc/xds_endpoint_parser.h", "src/core/xds/grpc/xds_health_status.cc", "src/core/xds/grpc/xds_health_status.h", "src/core/xds/grpc/xds_http_fault_filter.cc", "src/core/xds/grpc/xds_http_fault_filter.h", - "src/core/xds/grpc/xds_http_filters.cc", - "src/core/xds/grpc/xds_http_filters.h", + "src/core/xds/grpc/xds_http_filter.h", + "src/core/xds/grpc/xds_http_filter_registry.cc", + "src/core/xds/grpc/xds_http_filter_registry.h", "src/core/xds/grpc/xds_http_rbac_filter.cc", "src/core/xds/grpc/xds_http_rbac_filter.h", "src/core/xds/grpc/xds_http_stateful_session_filter.cc", @@ -1995,10 +2002,16 @@ let package = Package( "src/core/xds/grpc/xds_lb_policy_registry.h", "src/core/xds/grpc/xds_listener.cc", "src/core/xds/grpc/xds_listener.h", + "src/core/xds/grpc/xds_listener_parser.cc", + "src/core/xds/grpc/xds_listener_parser.h", "src/core/xds/grpc/xds_route_config.cc", "src/core/xds/grpc/xds_route_config.h", + "src/core/xds/grpc/xds_route_config_parser.cc", + "src/core/xds/grpc/xds_route_config_parser.h", "src/core/xds/grpc/xds_routing.cc", "src/core/xds/grpc/xds_routing.h", + "src/core/xds/grpc/xds_server_grpc.cc", + "src/core/xds/grpc/xds_server_grpc.h", "src/core/xds/grpc/xds_transport_grpc.cc", "src/core/xds/grpc/xds_transport_grpc.h", "src/core/xds/xds_client/xds_api.cc", diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index f90854833e9..02edb7c33f3 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -1228,18 +1228,25 @@ libs: - src/core/xds/grpc/xds_certificate_provider.h - src/core/xds/grpc/xds_client_grpc.h - src/core/xds/grpc/xds_cluster.h + - src/core/xds/grpc/xds_cluster_parser.h - src/core/xds/grpc/xds_cluster_specifier_plugin.h - src/core/xds/grpc/xds_common_types.h + - src/core/xds/grpc/xds_common_types_parser.h - src/core/xds/grpc/xds_endpoint.h + - src/core/xds/grpc/xds_endpoint_parser.h - src/core/xds/grpc/xds_health_status.h - src/core/xds/grpc/xds_http_fault_filter.h - - src/core/xds/grpc/xds_http_filters.h + - src/core/xds/grpc/xds_http_filter.h + - src/core/xds/grpc/xds_http_filter_registry.h - src/core/xds/grpc/xds_http_rbac_filter.h - src/core/xds/grpc/xds_http_stateful_session_filter.h - src/core/xds/grpc/xds_lb_policy_registry.h - src/core/xds/grpc/xds_listener.h + - src/core/xds/grpc/xds_listener_parser.h - src/core/xds/grpc/xds_route_config.h + - src/core/xds/grpc/xds_route_config_parser.h - src/core/xds/grpc/xds_routing.h + - src/core/xds/grpc/xds_server_grpc.h - src/core/xds/grpc/xds_transport_grpc.h - src/core/xds/xds_client/xds_api.h - src/core/xds/xds_client/xds_bootstrap.h @@ -2033,18 +2040,24 @@ libs: - src/core/xds/grpc/xds_certificate_provider.cc - src/core/xds/grpc/xds_client_grpc.cc - src/core/xds/grpc/xds_cluster.cc + - src/core/xds/grpc/xds_cluster_parser.cc - src/core/xds/grpc/xds_cluster_specifier_plugin.cc - src/core/xds/grpc/xds_common_types.cc + - src/core/xds/grpc/xds_common_types_parser.cc - src/core/xds/grpc/xds_endpoint.cc + - src/core/xds/grpc/xds_endpoint_parser.cc - src/core/xds/grpc/xds_health_status.cc - src/core/xds/grpc/xds_http_fault_filter.cc - - src/core/xds/grpc/xds_http_filters.cc + - src/core/xds/grpc/xds_http_filter_registry.cc - src/core/xds/grpc/xds_http_rbac_filter.cc - src/core/xds/grpc/xds_http_stateful_session_filter.cc - src/core/xds/grpc/xds_lb_policy_registry.cc - src/core/xds/grpc/xds_listener.cc + - src/core/xds/grpc/xds_listener_parser.cc - src/core/xds/grpc/xds_route_config.cc + - src/core/xds/grpc/xds_route_config_parser.cc - src/core/xds/grpc/xds_routing.cc + - src/core/xds/grpc/xds_server_grpc.cc - src/core/xds/grpc/xds_transport_grpc.cc - src/core/xds/xds_client/xds_api.cc - src/core/xds/xds_client/xds_bootstrap.cc diff --git a/config.m4 b/config.m4 index 81c63cb189c..619d9761de4 100644 --- a/config.m4 +++ b/config.m4 @@ -866,18 +866,24 @@ if test "$PHP_GRPC" != "no"; then src/core/xds/grpc/xds_certificate_provider.cc \ src/core/xds/grpc/xds_client_grpc.cc \ src/core/xds/grpc/xds_cluster.cc \ + src/core/xds/grpc/xds_cluster_parser.cc \ src/core/xds/grpc/xds_cluster_specifier_plugin.cc \ src/core/xds/grpc/xds_common_types.cc \ + src/core/xds/grpc/xds_common_types_parser.cc \ src/core/xds/grpc/xds_endpoint.cc \ + src/core/xds/grpc/xds_endpoint_parser.cc \ src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_http_fault_filter.cc \ - src/core/xds/grpc/xds_http_filters.cc \ + src/core/xds/grpc/xds_http_filter_registry.cc \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ src/core/xds/grpc/xds_lb_policy_registry.cc \ src/core/xds/grpc/xds_listener.cc \ + src/core/xds/grpc/xds_listener_parser.cc \ src/core/xds/grpc/xds_route_config.cc \ + src/core/xds/grpc/xds_route_config_parser.cc \ src/core/xds/grpc/xds_routing.cc \ + src/core/xds/grpc/xds_server_grpc.cc \ src/core/xds/grpc/xds_transport_grpc.cc \ src/core/xds/xds_client/xds_api.cc \ src/core/xds/xds_client/xds_bootstrap.cc \ diff --git a/config.w32 b/config.w32 index f45e109de2c..0384064082d 100644 --- a/config.w32 +++ b/config.w32 @@ -831,18 +831,24 @@ if (PHP_GRPC != "no") { "src\\core\\xds\\grpc\\xds_certificate_provider.cc " + "src\\core\\xds\\grpc\\xds_client_grpc.cc " + "src\\core\\xds\\grpc\\xds_cluster.cc " + + "src\\core\\xds\\grpc\\xds_cluster_parser.cc " + "src\\core\\xds\\grpc\\xds_cluster_specifier_plugin.cc " + "src\\core\\xds\\grpc\\xds_common_types.cc " + + "src\\core\\xds\\grpc\\xds_common_types_parser.cc " + "src\\core\\xds\\grpc\\xds_endpoint.cc " + + "src\\core\\xds\\grpc\\xds_endpoint_parser.cc " + "src\\core\\xds\\grpc\\xds_health_status.cc " + "src\\core\\xds\\grpc\\xds_http_fault_filter.cc " + - "src\\core\\xds\\grpc\\xds_http_filters.cc " + + "src\\core\\xds\\grpc\\xds_http_filter_registry.cc " + "src\\core\\xds\\grpc\\xds_http_rbac_filter.cc " + "src\\core\\xds\\grpc\\xds_http_stateful_session_filter.cc " + "src\\core\\xds\\grpc\\xds_lb_policy_registry.cc " + "src\\core\\xds\\grpc\\xds_listener.cc " + + "src\\core\\xds\\grpc\\xds_listener_parser.cc " + "src\\core\\xds\\grpc\\xds_route_config.cc " + + "src\\core\\xds\\grpc\\xds_route_config_parser.cc " + "src\\core\\xds\\grpc\\xds_routing.cc " + + "src\\core\\xds\\grpc\\xds_server_grpc.cc " + "src\\core\\xds\\grpc\\xds_transport_grpc.cc " + "src\\core\\xds\\xds_client\\xds_api.cc " + "src\\core\\xds\\xds_client\\xds_bootstrap.cc " + diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 137e0723b6d..2063af27118 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -1336,19 +1336,26 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_certificate_provider.h', 'src/core/xds/grpc/xds_client_grpc.h', 'src/core/xds/grpc/xds_cluster.h', + 'src/core/xds/grpc/xds_cluster_parser.h', 'src/core/xds/grpc/xds_cluster_specifier_plugin.h', 'src/core/xds/grpc/xds_common_types.h', + 'src/core/xds/grpc/xds_common_types_parser.h', 'src/core/xds/grpc/xds_enabled_server.h', 'src/core/xds/grpc/xds_endpoint.h', + 'src/core/xds/grpc/xds_endpoint_parser.h', 'src/core/xds/grpc/xds_health_status.h', 'src/core/xds/grpc/xds_http_fault_filter.h', - 'src/core/xds/grpc/xds_http_filters.h', + 'src/core/xds/grpc/xds_http_filter.h', + 'src/core/xds/grpc/xds_http_filter_registry.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', 'src/core/xds/grpc/xds_listener.h', + 'src/core/xds/grpc/xds_listener_parser.h', 'src/core/xds/grpc/xds_route_config.h', + 'src/core/xds/grpc/xds_route_config_parser.h', 'src/core/xds/grpc/xds_routing.h', + 'src/core/xds/grpc/xds_server_grpc.h', 'src/core/xds/grpc/xds_transport_grpc.h', 'src/core/xds/xds_client/xds_api.h', 'src/core/xds/xds_client/xds_bootstrap.h', @@ -2613,19 +2620,26 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_certificate_provider.h', 'src/core/xds/grpc/xds_client_grpc.h', 'src/core/xds/grpc/xds_cluster.h', + 'src/core/xds/grpc/xds_cluster_parser.h', 'src/core/xds/grpc/xds_cluster_specifier_plugin.h', 'src/core/xds/grpc/xds_common_types.h', + 'src/core/xds/grpc/xds_common_types_parser.h', 'src/core/xds/grpc/xds_enabled_server.h', 'src/core/xds/grpc/xds_endpoint.h', + 'src/core/xds/grpc/xds_endpoint_parser.h', 'src/core/xds/grpc/xds_health_status.h', 'src/core/xds/grpc/xds_http_fault_filter.h', - 'src/core/xds/grpc/xds_http_filters.h', + 'src/core/xds/grpc/xds_http_filter.h', + 'src/core/xds/grpc/xds_http_filter_registry.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', 'src/core/xds/grpc/xds_listener.h', + 'src/core/xds/grpc/xds_listener_parser.h', 'src/core/xds/grpc/xds_route_config.h', + 'src/core/xds/grpc/xds_route_config_parser.h', 'src/core/xds/grpc/xds_routing.h', + 'src/core/xds/grpc/xds_server_grpc.h', 'src/core/xds/grpc/xds_transport_grpc.h', 'src/core/xds/xds_client/xds_api.h', 'src/core/xds/xds_client/xds_bootstrap.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 580ce29b707..a0900b009c6 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -2091,18 +2091,25 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_client_grpc.h', 'src/core/xds/grpc/xds_cluster.cc', 'src/core/xds/grpc/xds_cluster.h', + 'src/core/xds/grpc/xds_cluster_parser.cc', + 'src/core/xds/grpc/xds_cluster_parser.h', 'src/core/xds/grpc/xds_cluster_specifier_plugin.cc', 'src/core/xds/grpc/xds_cluster_specifier_plugin.h', 'src/core/xds/grpc/xds_common_types.cc', 'src/core/xds/grpc/xds_common_types.h', + 'src/core/xds/grpc/xds_common_types_parser.cc', + 'src/core/xds/grpc/xds_common_types_parser.h', 'src/core/xds/grpc/xds_endpoint.cc', 'src/core/xds/grpc/xds_endpoint.h', + 'src/core/xds/grpc/xds_endpoint_parser.cc', + 'src/core/xds/grpc/xds_endpoint_parser.h', 'src/core/xds/grpc/xds_health_status.cc', 'src/core/xds/grpc/xds_health_status.h', 'src/core/xds/grpc/xds_http_fault_filter.cc', 'src/core/xds/grpc/xds_http_fault_filter.h', - 'src/core/xds/grpc/xds_http_filters.cc', - 'src/core/xds/grpc/xds_http_filters.h', + 'src/core/xds/grpc/xds_http_filter.h', + 'src/core/xds/grpc/xds_http_filter_registry.cc', + 'src/core/xds/grpc/xds_http_filter_registry.h', 'src/core/xds/grpc/xds_http_rbac_filter.cc', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.cc', @@ -2111,10 +2118,16 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_lb_policy_registry.h', 'src/core/xds/grpc/xds_listener.cc', 'src/core/xds/grpc/xds_listener.h', + 'src/core/xds/grpc/xds_listener_parser.cc', + 'src/core/xds/grpc/xds_listener_parser.h', 'src/core/xds/grpc/xds_route_config.cc', 'src/core/xds/grpc/xds_route_config.h', + 'src/core/xds/grpc/xds_route_config_parser.cc', + 'src/core/xds/grpc/xds_route_config_parser.h', 'src/core/xds/grpc/xds_routing.cc', 'src/core/xds/grpc/xds_routing.h', + 'src/core/xds/grpc/xds_server_grpc.cc', + 'src/core/xds/grpc/xds_server_grpc.h', 'src/core/xds/grpc/xds_transport_grpc.cc', 'src/core/xds/grpc/xds_transport_grpc.h', 'src/core/xds/xds_client/xds_api.cc', @@ -3388,18 +3401,25 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_certificate_provider.h', 'src/core/xds/grpc/xds_client_grpc.h', 'src/core/xds/grpc/xds_cluster.h', + 'src/core/xds/grpc/xds_cluster_parser.h', 'src/core/xds/grpc/xds_cluster_specifier_plugin.h', 'src/core/xds/grpc/xds_common_types.h', + 'src/core/xds/grpc/xds_common_types_parser.h', 'src/core/xds/grpc/xds_endpoint.h', + 'src/core/xds/grpc/xds_endpoint_parser.h', 'src/core/xds/grpc/xds_health_status.h', 'src/core/xds/grpc/xds_http_fault_filter.h', - 'src/core/xds/grpc/xds_http_filters.h', + 'src/core/xds/grpc/xds_http_filter.h', + 'src/core/xds/grpc/xds_http_filter_registry.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', 'src/core/xds/grpc/xds_listener.h', + 'src/core/xds/grpc/xds_listener_parser.h', 'src/core/xds/grpc/xds_route_config.h', + 'src/core/xds/grpc/xds_route_config_parser.h', 'src/core/xds/grpc/xds_routing.h', + 'src/core/xds/grpc/xds_server_grpc.h', 'src/core/xds/grpc/xds_transport_grpc.h', 'src/core/xds/xds_client/xds_api.h', 'src/core/xds/xds_client/xds_bootstrap.h', diff --git a/grpc.gemspec b/grpc.gemspec index c4fc9e0c34c..0608a1907de 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -1977,18 +1977,25 @@ Gem::Specification.new do |s| s.files += %w( src/core/xds/grpc/xds_client_grpc.h ) s.files += %w( src/core/xds/grpc/xds_cluster.cc ) s.files += %w( src/core/xds/grpc/xds_cluster.h ) + s.files += %w( src/core/xds/grpc/xds_cluster_parser.cc ) + s.files += %w( src/core/xds/grpc/xds_cluster_parser.h ) s.files += %w( src/core/xds/grpc/xds_cluster_specifier_plugin.cc ) s.files += %w( src/core/xds/grpc/xds_cluster_specifier_plugin.h ) s.files += %w( src/core/xds/grpc/xds_common_types.cc ) s.files += %w( src/core/xds/grpc/xds_common_types.h ) + s.files += %w( src/core/xds/grpc/xds_common_types_parser.cc ) + s.files += %w( src/core/xds/grpc/xds_common_types_parser.h ) s.files += %w( src/core/xds/grpc/xds_endpoint.cc ) s.files += %w( src/core/xds/grpc/xds_endpoint.h ) + s.files += %w( src/core/xds/grpc/xds_endpoint_parser.cc ) + s.files += %w( src/core/xds/grpc/xds_endpoint_parser.h ) s.files += %w( src/core/xds/grpc/xds_health_status.cc ) s.files += %w( src/core/xds/grpc/xds_health_status.h ) s.files += %w( src/core/xds/grpc/xds_http_fault_filter.cc ) s.files += %w( src/core/xds/grpc/xds_http_fault_filter.h ) - s.files += %w( src/core/xds/grpc/xds_http_filters.cc ) - s.files += %w( src/core/xds/grpc/xds_http_filters.h ) + s.files += %w( src/core/xds/grpc/xds_http_filter.h ) + s.files += %w( src/core/xds/grpc/xds_http_filter_registry.cc ) + s.files += %w( src/core/xds/grpc/xds_http_filter_registry.h ) s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.cc ) s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.h ) s.files += %w( src/core/xds/grpc/xds_http_stateful_session_filter.cc ) @@ -1997,10 +2004,16 @@ Gem::Specification.new do |s| s.files += %w( src/core/xds/grpc/xds_lb_policy_registry.h ) s.files += %w( src/core/xds/grpc/xds_listener.cc ) s.files += %w( src/core/xds/grpc/xds_listener.h ) + s.files += %w( src/core/xds/grpc/xds_listener_parser.cc ) + s.files += %w( src/core/xds/grpc/xds_listener_parser.h ) s.files += %w( src/core/xds/grpc/xds_route_config.cc ) s.files += %w( src/core/xds/grpc/xds_route_config.h ) + s.files += %w( src/core/xds/grpc/xds_route_config_parser.cc ) + s.files += %w( src/core/xds/grpc/xds_route_config_parser.h ) s.files += %w( src/core/xds/grpc/xds_routing.cc ) s.files += %w( src/core/xds/grpc/xds_routing.h ) + s.files += %w( src/core/xds/grpc/xds_server_grpc.cc ) + s.files += %w( src/core/xds/grpc/xds_server_grpc.h ) s.files += %w( src/core/xds/grpc/xds_transport_grpc.cc ) s.files += %w( src/core/xds/grpc/xds_transport_grpc.h ) s.files += %w( src/core/xds/xds_client/xds_api.cc ) diff --git a/package.xml b/package.xml index ff3a77d0382..339828a27b3 100644 --- a/package.xml +++ b/package.xml @@ -1959,18 +1959,25 @@ + + + + + + - - + + + @@ -1979,10 +1986,16 @@ + + + + + + diff --git a/src/core/BUILD b/src/core/BUILD index 4c91a2d7f2b..e1b6c0ac465 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -5229,24 +5229,226 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "xds_common_types", + srcs = [ + "xds/grpc/xds_common_types.cc", + ], + hdrs = [ + "xds/grpc/xds_common_types.h", + ], + external_deps = [ + "absl/strings", + "absl/strings:str_format", + "absl/types:variant", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "grpc_matchers", + "json", + "validation_errors", + ], +) + +grpc_cc_library( + name = "xds_http_filter", + hdrs = [ + "xds/grpc/xds_http_filter.h", + ], + external_deps = [ + "absl/status:statusor", + "absl/strings", + "absl/types:optional", + "@com_google_protobuf//upb:reflection", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "channel_args", + "channel_fwd", + "interception_chain", + "json", + "json_writer", + "validation_errors", + "xds_common_types", + "//:xds_client", + ], +) + +grpc_cc_library( + name = "xds_route_config", + srcs = [ + "xds/grpc/xds_route_config.cc", + ], + hdrs = [ + "xds/grpc/xds_route_config.h", + ], + external_deps = [ + "absl/strings", + "absl/strings:str_format", + "absl/types:optional", + "absl/types:variant", + "re2", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "grpc_matchers", + "match", + "time", + "xds_http_filter", + "//:grpc_base", + "//:xds_client", + ], +) + +grpc_cc_library( + name = "xds_listener", + srcs = [ + "xds/grpc/xds_listener.cc", + ], + hdrs = [ + "xds/grpc/xds_listener.h", + ], + external_deps = [ + "absl/strings", + "absl/strings:str_format", + "absl/types:optional", + "absl/types:variant", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "match", + "resolved_address", + "time", + "xds_common_types", + "xds_http_filter", + "xds_route_config", + "//:sockaddr_utils", + "//:xds_client", + ], +) + +grpc_cc_library( + name = "xds_health_status", + srcs = [ + "xds/grpc/xds_health_status.cc", + ], + hdrs = [ + "xds/grpc/xds_health_status.h", + ], + external_deps = [ + "absl/strings", + "absl/types:optional", + "absl/types:span", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "envoy_config_core_upb", + "//:endpoint_addresses", + ], +) + +grpc_cc_library( + name = "xds_server_grpc", + srcs = [ + "xds/grpc/xds_server_grpc.cc", + ], + hdrs = [ + "xds/grpc/xds_server_grpc.h", + ], + external_deps = [ + "absl/strings", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "channel_creds_registry", + "json", + "json_args", + "json_object_loader", + "json_reader", + "json_writer", + "validation_errors", + "//:config", + "//:ref_counted_ptr", + "//:xds_client", + ], +) + +grpc_cc_library( + name = "xds_cluster", + srcs = [ + "xds/grpc/xds_cluster.cc", + ], + hdrs = [ + "xds/grpc/xds_cluster.h", + ], + external_deps = [ + "absl/strings", + "absl/types:optional", + "absl/types:variant", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "grpc_outlier_detection_header", + "json", + "json_writer", + "match", + "time", + "xds_common_types", + "xds_health_status", + "xds_server_grpc", + "//:xds_client", + ], +) + +grpc_cc_library( + name = "xds_endpoint", + srcs = [ + "xds/grpc/xds_endpoint.cc", + ], + hdrs = [ + "xds/grpc/xds_endpoint.h", + ], + external_deps = [ + "absl/base:core_headers", + "absl/random", + "absl/strings", + ], + language = "c++", + tags = ["nofixdeps"], + deps = [ + "ref_counted", + "//:endpoint_addresses", + "//:gpr", + "//:ref_counted_ptr", + "//:xds_client", + ], +) + +# TODO(roth): Split this up into individual targets. grpc_cc_library( name = "grpc_xds_client", srcs = [ "xds/grpc/xds_audit_logger_registry.cc", "xds/grpc/xds_bootstrap_grpc.cc", "xds/grpc/xds_client_grpc.cc", - "xds/grpc/xds_cluster.cc", + "xds/grpc/xds_cluster_parser.cc", "xds/grpc/xds_cluster_specifier_plugin.cc", - "xds/grpc/xds_common_types.cc", - "xds/grpc/xds_endpoint.cc", - "xds/grpc/xds_health_status.cc", + "xds/grpc/xds_common_types_parser.cc", + "xds/grpc/xds_endpoint_parser.cc", "xds/grpc/xds_http_fault_filter.cc", - "xds/grpc/xds_http_filters.cc", + "xds/grpc/xds_http_filter_registry.cc", "xds/grpc/xds_http_rbac_filter.cc", "xds/grpc/xds_http_stateful_session_filter.cc", "xds/grpc/xds_lb_policy_registry.cc", - "xds/grpc/xds_listener.cc", - "xds/grpc/xds_route_config.cc", + "xds/grpc/xds_listener_parser.cc", + "xds/grpc/xds_route_config_parser.cc", "xds/grpc/xds_routing.cc", "xds/grpc/xds_transport_grpc.cc", ], @@ -5254,18 +5456,17 @@ grpc_cc_library( "xds/grpc/xds_audit_logger_registry.h", "xds/grpc/xds_bootstrap_grpc.h", "xds/grpc/xds_client_grpc.h", - "xds/grpc/xds_cluster.h", + "xds/grpc/xds_cluster_parser.h", "xds/grpc/xds_cluster_specifier_plugin.h", - "xds/grpc/xds_common_types.h", - "xds/grpc/xds_endpoint.h", - "xds/grpc/xds_health_status.h", + "xds/grpc/xds_common_types_parser.h", + "xds/grpc/xds_endpoint_parser.h", "xds/grpc/xds_http_fault_filter.h", - "xds/grpc/xds_http_filters.h", + "xds/grpc/xds_http_filter_registry.h", "xds/grpc/xds_http_rbac_filter.h", "xds/grpc/xds_http_stateful_session_filter.h", "xds/grpc/xds_lb_policy_registry.h", - "xds/grpc/xds_listener.h", - "xds/grpc/xds_route_config.h", + "xds/grpc/xds_listener_parser.h", + "xds/grpc/xds_route_config_parser.h", "xds/grpc/xds_routing.h", "xds/grpc/xds_transport_grpc.h", ], @@ -5400,8 +5601,16 @@ grpc_cc_library( "validation_errors", "xds_certificate_provider", "xds_certificate_provider_store", + "xds_cluster", + "xds_common_types", "xds_credentials", + "xds_endpoint", "xds_file_watcher_certificate_provider_factory", + "xds_health_status", + "xds_http_filter", + "xds_listener", + "xds_route_config", + "xds_server_grpc", "xds_type_upb", "xds_type_upbdefs", "//:channel", @@ -5492,7 +5701,11 @@ grpc_cc_library( "unique_type_name", "xds_certificate_provider", "xds_certificate_provider_store", + "xds_common_types", "xds_credentials", + "xds_http_filter", + "xds_listener", + "xds_route_config", "//:api_trace", "//:config", "//:debug_location", @@ -5574,7 +5787,10 @@ grpc_cc_library( "pollset_set", "time", "unique_type_name", + "xds_cluster", + "xds_common_types", "xds_dependency_manager", + "xds_health_status", "//:config", "//:debug_location", "//:gpr", @@ -5639,6 +5855,7 @@ grpc_cc_library( "validation_errors", "xds_credentials", "xds_dependency_manager", + "xds_endpoint", "//:call_tracer", "//:config", "//:debug_location", @@ -6350,6 +6567,7 @@ grpc_cc_library( "subchannel_interface", "validation_errors", "xds_dependency_manager", + "xds_health_status", "//:config", "//:debug_location", "//:endpoint_addresses", @@ -6713,6 +6931,10 @@ grpc_cc_library( "grpc_xds_client", "match", "ref_counted", + "xds_cluster", + "xds_endpoint", + "xds_listener", + "xds_route_config", "//:config", "//:gpr", "//:grpc_resolver", @@ -6761,6 +6983,9 @@ grpc_cc_library( "slice", "time", "xds_dependency_manager", + "xds_http_filter", + "xds_listener", + "xds_route_config", "xxhash_inline", "//:channel_arg_names", "//:config", diff --git a/src/core/resolver/xds/xds_dependency_manager.cc b/src/core/resolver/xds/xds_dependency_manager.cc index 0197984143a..61bc5418ac2 100644 --- a/src/core/resolver/xds/xds_dependency_manager.cc +++ b/src/core/resolver/xds/xds_dependency_manager.cc @@ -28,6 +28,10 @@ #include "src/core/lib/gprpp/match.h" #include "src/core/load_balancing/xds/xds_channel_args.h" #include "src/core/resolver/fake/fake_resolver.h" +#include "src/core/xds/grpc/xds_cluster_parser.h" +#include "src/core/xds/grpc/xds_endpoint_parser.h" +#include "src/core/xds/grpc/xds_listener_parser.h" +#include "src/core/xds/grpc/xds_route_config_parser.h" #include "src/core/xds/grpc/xds_routing.h" namespace grpc_core { diff --git a/src/core/resolver/xds/xds_resolver.cc b/src/core/resolver/xds/xds_resolver.cc index a91708c67f4..4933d5f1954 100644 --- a/src/core/resolver/xds/xds_resolver.cc +++ b/src/core/resolver/xds/xds_resolver.cc @@ -86,7 +86,7 @@ #include "src/core/service_config/service_config_impl.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_client_grpc.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/grpc/xds_listener.h" #include "src/core/xds/grpc/xds_route_config.h" #include "src/core/xds/grpc/xds_routing.h" diff --git a/src/core/server/xds_server_config_fetcher.cc b/src/core/server/xds_server_config_fetcher.cc index ddf2273176b..1366b0702fa 100644 --- a/src/core/server/xds_server_config_fetcher.cc +++ b/src/core/server/xds_server_config_fetcher.cc @@ -84,9 +84,12 @@ #include "src/core/xds/grpc/xds_certificate_provider.h" #include "src/core/xds/grpc/xds_client_grpc.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" #include "src/core/xds/grpc/xds_listener.h" +#include "src/core/xds/grpc/xds_listener_parser.h" #include "src/core/xds/grpc/xds_route_config.h" +#include "src/core/xds/grpc/xds_route_config_parser.h" #include "src/core/xds/grpc/xds_routing.h" #include "src/core/xds/xds_client/xds_client.h" diff --git a/src/core/xds/grpc/xds_audit_logger_registry.cc b/src/core/xds/grpc/xds_audit_logger_registry.cc index 328849808ce..7ec3a98edf1 100644 --- a/src/core/xds/grpc/xds_audit_logger_registry.cc +++ b/src/core/xds/grpc/xds_audit_logger_registry.cc @@ -32,6 +32,7 @@ #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/lib/security/authorization/audit_logging.h" #include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_bootstrap_grpc.cc b/src/core/xds/grpc/xds_bootstrap_grpc.cc index 9ab26b38238..58b958cb994 100644 --- a/src/core/xds/grpc/xds_bootstrap_grpc.cc +++ b/src/core/xds/grpc/xds_bootstrap_grpc.cc @@ -90,131 +90,6 @@ const JsonLoaderInterface* GrpcXdsBootstrap::GrpcNode::JsonLoader( return loader; } -// -// GrpcXdsBootstrap::GrpcXdsServer -// - -namespace { - -constexpr absl::string_view kServerFeatureIgnoreResourceDeletion = - "ignore_resource_deletion"; - -} // namespace - -bool GrpcXdsBootstrap::GrpcXdsServer::IgnoreResourceDeletion() const { - return server_features_.find(std::string( - kServerFeatureIgnoreResourceDeletion)) != server_features_.end(); -} - -bool GrpcXdsBootstrap::GrpcXdsServer::Equals(const XdsServer& other) const { - const auto& o = static_cast(other); - return (server_uri_ == o.server_uri_ && - channel_creds_config_->type() == o.channel_creds_config_->type() && - channel_creds_config_->Equals(*o.channel_creds_config_) && - server_features_ == o.server_features_); -} - -std::string GrpcXdsBootstrap::GrpcXdsServer::Key() const { - return JsonDump(ToJson()); -} - -const JsonLoaderInterface* GrpcXdsBootstrap::GrpcXdsServer::JsonLoader( - const JsonArgs&) { - static const auto* loader = - JsonObjectLoader() - .Field("server_uri", &GrpcXdsServer::server_uri_) - .Finish(); - return loader; -} - -namespace { - -struct ChannelCreds { - std::string type; - Json::Object config; - - static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { - static const auto* loader = - JsonObjectLoader() - .Field("type", &ChannelCreds::type) - .OptionalField("config", &ChannelCreds::config) - .Finish(); - return loader; - } -}; - -} // namespace - -void GrpcXdsBootstrap::GrpcXdsServer::JsonPostLoad(const Json& json, - const JsonArgs& args, - ValidationErrors* errors) { - // Parse "channel_creds". - auto channel_creds_list = LoadJsonObjectField>( - json.object(), args, "channel_creds", errors); - if (channel_creds_list.has_value()) { - ValidationErrors::ScopedField field(errors, ".channel_creds"); - for (size_t i = 0; i < channel_creds_list->size(); ++i) { - ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); - auto& creds = (*channel_creds_list)[i]; - // Select the first channel creds type that we support, but - // validate all entries. - if (CoreConfiguration::Get().channel_creds_registry().IsSupported( - creds.type)) { - ValidationErrors::ScopedField field(errors, ".config"); - auto config = - CoreConfiguration::Get().channel_creds_registry().ParseConfig( - creds.type, Json::FromObject(creds.config), args, errors); - if (channel_creds_config_ == nullptr) { - channel_creds_config_ = std::move(config); - } - } - } - if (channel_creds_config_ == nullptr) { - errors->AddError("no known creds type found"); - } - } - // Parse "server_features". - { - ValidationErrors::ScopedField field(errors, ".server_features"); - auto it = json.object().find("server_features"); - if (it != json.object().end()) { - if (it->second.type() != Json::Type::kArray) { - errors->AddError("is not an array"); - } else { - const Json::Array& array = it->second.array(); - for (const Json& feature_json : array) { - if (feature_json.type() == Json::Type::kString && - (feature_json.string() == kServerFeatureIgnoreResourceDeletion)) { - server_features_.insert(feature_json.string()); - } - } - } - } - } -} - -Json GrpcXdsBootstrap::GrpcXdsServer::ToJson() const { - Json::Object channel_creds_json{ - {"type", Json::FromString(std::string(channel_creds_config_->type()))}, - }; - if (channel_creds_config_ != nullptr) { - channel_creds_json["config"] = channel_creds_config_->ToJson(); - } - Json::Object json{ - {"server_uri", Json::FromString(server_uri_)}, - {"channel_creds", - Json::FromArray({Json::FromObject(std::move(channel_creds_json))})}, - }; - if (!server_features_.empty()) { - Json::Array server_features_json; - for (auto& feature : server_features_) { - server_features_json.emplace_back(Json::FromString(feature)); - } - json["server_features"] = Json::FromArray(std::move(server_features_json)); - } - return Json::FromObject(std::move(json)); -} - // // GrpcXdsBootstrap::GrpcAuthority // diff --git a/src/core/xds/grpc/xds_bootstrap_grpc.h b/src/core/xds/grpc/xds_bootstrap_grpc.h index 00c08f9e2e2..a21f0017aa2 100644 --- a/src/core/xds/grpc/xds_bootstrap_grpc.h +++ b/src/core/xds/grpc/xds_bootstrap_grpc.h @@ -38,8 +38,9 @@ #include "src/core/xds/grpc/certificate_provider_store.h" #include "src/core/xds/grpc/xds_audit_logger_registry.h" #include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" #include "src/core/xds/grpc/xds_lb_policy_registry.h" +#include "src/core/xds/grpc/xds_server_grpc.h" #include "src/core/xds/xds_client/xds_bootstrap.h" namespace grpc_core { @@ -76,32 +77,6 @@ class GrpcXdsBootstrap final : public XdsBootstrap { Json::Object metadata_; }; - class GrpcXdsServer final : public XdsServer { - public: - const std::string& server_uri() const override { return server_uri_; } - - bool IgnoreResourceDeletion() const override; - - bool Equals(const XdsServer& other) const override; - - std::string Key() const override; - - RefCountedPtr channel_creds_config() const { - return channel_creds_config_; - } - - static const JsonLoaderInterface* JsonLoader(const JsonArgs&); - void JsonPostLoad(const Json& json, const JsonArgs& args, - ValidationErrors* errors); - - Json ToJson() const; - - private: - std::string server_uri_; - RefCountedPtr channel_creds_config_; - std::set server_features_; - }; - class GrpcAuthority final : public Authority { public: std::vector servers() const override { diff --git a/src/core/xds/grpc/xds_cluster.cc b/src/core/xds/grpc/xds_cluster.cc index 7b232fa0ef8..300528f5a16 100644 --- a/src/core/xds/grpc/xds_cluster.cc +++ b/src/core/xds/grpc/xds_cluster.cc @@ -16,68 +16,16 @@ #include "src/core/xds/grpc/xds_cluster.h" -#include - -#include -#include -#include - -#include "absl/log/check.h" -#include "absl/log/log.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" -#include "absl/strings/strip.h" -#include "absl/types/variant.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/cluster/v3/outlier_detection.upb.h" -#include "envoy/config/core/v3/address.upb.h" -#include "envoy/config/core/v3/base.upb.h" -#include "envoy/config/core/v3/config_source.upb.h" -#include "envoy/config/core/v3/extension.upb.h" -#include "envoy/config/core/v3/health_check.upb.h" -#include "envoy/config/core/v3/protocol.upb.h" -#include "envoy/config/endpoint/v3/endpoint.upb.h" -#include "envoy/config/endpoint/v3/endpoint_components.upb.h" -#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h" -#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" -#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h" -#include "google/protobuf/any.upb.h" -#include "google/protobuf/duration.upb.h" -#include "google/protobuf/struct.upb.h" -#include "google/protobuf/wrappers.upb.h" -#include "upb/base/string_view.h" -#include "upb/text/encode.h" - -#include -#include -#include "src/core/lib/config/core_configuration.h" -#include "src/core/lib/debug/trace.h" -#include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/gprpp/match.h" -#include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/gprpp/time.h" -#include "src/core/lib/gprpp/validation_errors.h" -#include "src/core/lib/matchers/matchers.h" -#include "src/core/load_balancing/lb_policy_registry.h" #include "src/core/util/json/json_writer.h" -#include "src/core/util/upb_utils.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_lb_policy_registry.h" -#include "src/core/xds/xds_client/xds_client.h" -#include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { -// -// XdsClusterResource -// - std::string XdsClusterResource::ToString() const { std::vector contents; Match( @@ -129,674 +77,4 @@ std::string XdsClusterResource::ToString() const { return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } -// -// XdsClusterResourceType -// - -namespace { - -CommonTlsContext UpstreamTlsContextParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_core_v3_TransportSocket* transport_socket, - ValidationErrors* errors) { - ValidationErrors::ScopedField field(errors, ".typed_config"); - const auto* typed_config = - envoy_config_core_v3_TransportSocket_typed_config(transport_socket); - auto extension = ExtractXdsExtension(context, typed_config, errors); - if (!extension.has_value()) return {}; - if (extension->type != - "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") { - ValidationErrors::ScopedField field(errors, ".type_url"); - errors->AddError("unsupported transport socket type"); - return {}; - } - absl::string_view* serialized_upstream_tls_context = - absl::get_if(&extension->value); - if (serialized_upstream_tls_context == nullptr) { - errors->AddError("can't decode UpstreamTlsContext"); - return {}; - } - const auto* upstream_tls_context = - envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( - serialized_upstream_tls_context->data(), - serialized_upstream_tls_context->size(), context.arena); - if (upstream_tls_context == nullptr) { - errors->AddError("can't decode UpstreamTlsContext"); - return {}; - } - ValidationErrors::ScopedField field3(errors, ".common_tls_context"); - const auto* common_tls_context_proto = - envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context( - upstream_tls_context); - CommonTlsContext common_tls_context; - if (common_tls_context_proto != nullptr) { - common_tls_context = - CommonTlsContext::Parse(context, common_tls_context_proto, errors); - } - if (common_tls_context.certificate_validation_context - .ca_certificate_provider_instance.instance_name.empty()) { - errors->AddError("no CA certificate provider instance configured"); - } - return common_tls_context; -} - -XdsClusterResource::Eds EdsConfigParse( - const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { - XdsClusterResource::Eds eds; - ValidationErrors::ScopedField field(errors, ".eds_cluster_config"); - const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config = - envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); - if (eds_cluster_config == nullptr) { - errors->AddError("field not present"); - } else { - // Validate ConfigSource. - { - ValidationErrors::ScopedField field(errors, ".eds_config"); - const envoy_config_core_v3_ConfigSource* eds_config = - envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( - eds_cluster_config); - if (eds_config == nullptr) { - errors->AddError("field not present"); - } else { - if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) && - !envoy_config_core_v3_ConfigSource_has_self(eds_config)) { - errors->AddError("ConfigSource is not ads or self"); - } - } - } - // Record EDS service_name (if any). - // This field is required if the CDS resource has an xdstp name. - eds.eds_service_name = UpbStringToStdString( - envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( - eds_cluster_config)); - if (eds.eds_service_name.empty()) { - absl::string_view cluster_name = - UpbStringToAbsl(envoy_config_cluster_v3_Cluster_name(cluster)); - if (absl::StartsWith(cluster_name, "xdstp:")) { - ValidationErrors::ScopedField field(errors, ".service_name"); - errors->AddError("must be set if Cluster resource has an xdstp name"); - } - } - } - return eds; -} - -XdsClusterResource::LogicalDns LogicalDnsParse( - const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { - XdsClusterResource::LogicalDns logical_dns; - ValidationErrors::ScopedField field(errors, ".load_assignment"); - const auto* load_assignment = - envoy_config_cluster_v3_Cluster_load_assignment(cluster); - if (load_assignment == nullptr) { - errors->AddError("field not present for LOGICAL_DNS cluster"); - return logical_dns; - } - ValidationErrors::ScopedField field2(errors, ".endpoints"); - size_t num_localities; - const auto* const* localities = - envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment, - &num_localities); - if (num_localities != 1) { - errors->AddError(absl::StrCat( - "must contain exactly one locality for LOGICAL_DNS cluster, found ", - num_localities)); - return logical_dns; - } - ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints"); - size_t num_endpoints; - const auto* const* endpoints = - envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0], - &num_endpoints); - if (num_endpoints != 1) { - errors->AddError(absl::StrCat( - "must contain exactly one endpoint for LOGICAL_DNS cluster, found ", - num_endpoints)); - return logical_dns; - } - ValidationErrors::ScopedField field4(errors, "[0].endpoint"); - const auto* endpoint = - envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]); - if (endpoint == nullptr) { - errors->AddError("field not present"); - return logical_dns; - } - ValidationErrors::ScopedField field5(errors, ".address"); - const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint); - if (address == nullptr) { - errors->AddError("field not present"); - return logical_dns; - } - ValidationErrors::ScopedField field6(errors, ".socket_address"); - const auto* socket_address = - envoy_config_core_v3_Address_socket_address(address); - if (socket_address == nullptr) { - errors->AddError("field not present"); - return logical_dns; - } - if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size != - 0) { - ValidationErrors::ScopedField field(errors, ".resolver_name"); - errors->AddError( - "LOGICAL_DNS clusters must NOT have a custom resolver name set"); - } - absl::string_view address_str = UpbStringToAbsl( - envoy_config_core_v3_SocketAddress_address(socket_address)); - if (address_str.empty()) { - ValidationErrors::ScopedField field(errors, ".address"); - errors->AddError("field not present"); - } - if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) { - ValidationErrors::ScopedField field(errors, ".port_value"); - errors->AddError("field not present"); - } - logical_dns.hostname = JoinHostPort( - address_str, - envoy_config_core_v3_SocketAddress_port_value(socket_address)); - return logical_dns; -} - -XdsClusterResource::Aggregate AggregateClusterParse( - const XdsResourceType::DecodeContext& context, - absl::string_view serialized_config, ValidationErrors* errors) { - XdsClusterResource::Aggregate aggregate; - const auto* aggregate_cluster_config = - envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse( - serialized_config.data(), serialized_config.size(), context.arena); - if (aggregate_cluster_config == nullptr) { - errors->AddError("can't parse aggregate cluster config"); - return aggregate; - } - size_t size; - const upb_StringView* clusters = - envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters( - aggregate_cluster_config, &size); - if (size == 0) { - ValidationErrors::ScopedField field(errors, ".clusters"); - errors->AddError("must be non-empty"); - } - for (size_t i = 0; i < size; ++i) { - aggregate.prioritized_cluster_names.emplace_back( - UpbStringToStdString(clusters[i])); - } - return aggregate; -} - -void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context, - const envoy_config_cluster_v3_Cluster* cluster, - XdsClusterResource* cds_update, - ValidationErrors* errors) { - // First, check the new load_balancing_policy field. - const auto* load_balancing_policy = - envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster); - if (load_balancing_policy != nullptr) { - const auto& registry = - static_cast(context.client->bootstrap()) - .lb_policy_registry(); - ValidationErrors::ScopedField field(errors, ".load_balancing_policy"); - const size_t original_error_count = errors->size(); - cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig( - context, load_balancing_policy, errors); - // If there were no conversion errors, validate that the converted config - // parses with the gRPC LB policy registry. - if (original_error_count == errors->size()) { - auto config = CoreConfiguration::Get() - .lb_policy_registry() - .ParseLoadBalancingConfig( - Json::FromArray(cds_update->lb_policy_config)); - if (!config.ok()) errors->AddError(config.status().message()); - } - return; - } - // Didn't find load_balancing_policy field, so fall back to the old - // lb_policy enum field. - if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == - envoy_config_cluster_v3_Cluster_ROUND_ROBIN) { - cds_update->lb_policy_config = { - Json::FromObject({ - {"xds_wrr_locality_experimental", - Json::FromObject({ - {"childPolicy", Json::FromArray({ - Json::FromObject({ - {"round_robin", Json::FromObject({})}, - }), - })}, - })}, - }), - }; - } else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == - envoy_config_cluster_v3_Cluster_RING_HASH) { - // Record ring hash lb config - auto* ring_hash_config = - envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster); - uint64_t min_ring_size = 1024; - uint64_t max_ring_size = 8388608; - if (ring_hash_config != nullptr) { - ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config"); - const google_protobuf_UInt64Value* uint64_value = - envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size( - ring_hash_config); - if (uint64_value != nullptr) { - ValidationErrors::ScopedField field(errors, ".maximum_ring_size"); - max_ring_size = google_protobuf_UInt64Value_value(uint64_value); - if (max_ring_size > 8388608 || max_ring_size == 0) { - errors->AddError("must be in the range of 1 to 8388608"); - } - } - uint64_value = - envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size( - ring_hash_config); - if (uint64_value != nullptr) { - ValidationErrors::ScopedField field(errors, ".minimum_ring_size"); - min_ring_size = google_protobuf_UInt64Value_value(uint64_value); - if (min_ring_size > 8388608 || min_ring_size == 0) { - errors->AddError("must be in the range of 1 to 8388608"); - } - if (min_ring_size > max_ring_size) { - errors->AddError("cannot be greater than maximum_ring_size"); - } - } - if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( - ring_hash_config) != - envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) { - ValidationErrors::ScopedField field(errors, ".hash_function"); - errors->AddError("invalid hash function"); - } - } - cds_update->lb_policy_config = { - Json::FromObject({ - {"ring_hash_experimental", - Json::FromObject({ - {"minRingSize", Json::FromNumber(min_ring_size)}, - {"maxRingSize", Json::FromNumber(max_ring_size)}, - })}, - }), - }; - } else { - ValidationErrors::ScopedField field(errors, ".lb_policy"); - errors->AddError("LB policy is not supported"); - } -} - -void ParseUpstreamConfig( - const XdsResourceType::DecodeContext& context, - const envoy_config_core_v3_TypedExtensionConfig* upstream_config, - XdsClusterResource* cds_update, ValidationErrors* errors) { - ValidationErrors::ScopedField field(errors, ".typed_config"); - const auto* typed_config = - envoy_config_core_v3_TypedExtensionConfig_typed_config(upstream_config); - auto extension = ExtractXdsExtension(context, typed_config, errors); - if (!extension.has_value()) return; - if (extension->type != - "envoy.extensions.upstreams.http.v3.HttpProtocolOptions") { - ValidationErrors::ScopedField field(errors, ".type_url"); - errors->AddError("unsupported upstream config type"); - return; - } - absl::string_view* serialized_http_protocol_options = - absl::get_if(&extension->value); - if (serialized_http_protocol_options == nullptr) { - errors->AddError("can't decode HttpProtocolOptions"); - return; - } - const auto* http_protocol_options = - envoy_extensions_upstreams_http_v3_HttpProtocolOptions_parse( - serialized_http_protocol_options->data(), - serialized_http_protocol_options->size(), context.arena); - if (http_protocol_options == nullptr) { - errors->AddError("can't decode HttpProtocolOptions"); - return; - } - ValidationErrors::ScopedField field2(errors, ".common_http_protocol_options"); - const auto* common_http_protocol_options = - envoy_extensions_upstreams_http_v3_HttpProtocolOptions_common_http_protocol_options( - http_protocol_options); - if (common_http_protocol_options != nullptr) { - const auto* idle_timeout = - envoy_config_core_v3_HttpProtocolOptions_idle_timeout( - common_http_protocol_options); - if (idle_timeout != nullptr) { - ValidationErrors::ScopedField field(errors, ".idle_timeout"); - cds_update->connection_idle_timeout = ParseDuration(idle_timeout, errors); - } - } -} - -absl::StatusOr> CdsResourceParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_cluster_v3_Cluster* cluster) { - auto cds_update = std::make_shared(); - ValidationErrors errors; - // Check the cluster discovery type. - if (envoy_config_cluster_v3_Cluster_type(cluster) == - envoy_config_cluster_v3_Cluster_EDS) { - cds_update->type = EdsConfigParse(cluster, &errors); - } else if (envoy_config_cluster_v3_Cluster_type(cluster) == - envoy_config_cluster_v3_Cluster_LOGICAL_DNS) { - cds_update->type = LogicalDnsParse(cluster, &errors); - } else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { - ValidationErrors::ScopedField field(&errors, ".cluster_type"); - const auto* custom_cluster_type = - envoy_config_cluster_v3_Cluster_cluster_type(cluster); - CHECK_NE(custom_cluster_type, nullptr); - ValidationErrors::ScopedField field2(&errors, ".typed_config"); - const auto* typed_config = - envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config( - custom_cluster_type); - if (typed_config == nullptr) { - errors.AddError("field not present"); - } else { - absl::string_view type_url = absl::StripPrefix( - UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), - "type.googleapis.com/"); - if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") { - ValidationErrors::ScopedField field(&errors, ".type_url"); - errors.AddError( - absl::StrCat("unknown cluster_type extension: ", type_url)); - } else { - // Retrieve aggregate clusters. - ValidationErrors::ScopedField field( - &errors, - ".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]"); - absl::string_view serialized_config = - UpbStringToAbsl(google_protobuf_Any_value(typed_config)); - cds_update->type = - AggregateClusterParse(context, serialized_config, &errors); - } - } - } else { - ValidationErrors::ScopedField field(&errors, ".type"); - errors.AddError("unknown discovery type"); - } - // Check the LB policy. - ParseLbPolicyConfig(context, cluster, cds_update.get(), &errors); - // transport_socket - auto* transport_socket = - envoy_config_cluster_v3_Cluster_transport_socket(cluster); - if (transport_socket != nullptr) { - ValidationErrors::ScopedField field(&errors, ".transport_socket"); - cds_update->common_tls_context = - UpstreamTlsContextParse(context, transport_socket, &errors); - } - // Record LRS server name (if any). - const envoy_config_core_v3_ConfigSource* lrs_server = - envoy_config_cluster_v3_Cluster_lrs_server(cluster); - if (lrs_server != nullptr) { - if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) { - ValidationErrors::ScopedField field(&errors, ".lrs_server"); - errors.AddError("ConfigSource is not self"); - } - cds_update->lrs_load_reporting_server.emplace( - static_cast(context.server)); - } - // Protocol options. - auto* upstream_config = - envoy_config_cluster_v3_Cluster_upstream_config(cluster); - if (upstream_config != nullptr) { - ValidationErrors::ScopedField field(&errors, ".upstream_config"); - ParseUpstreamConfig(context, upstream_config, cds_update.get(), &errors); - } - // The Cluster resource encodes the circuit breaking parameters in a list of - // Thresholds messages, where each message specifies the parameters for a - // particular RoutingPriority. we will look only at the first entry in the - // list for priority DEFAULT and default to 1024 if not found. - if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) { - const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers = - envoy_config_cluster_v3_Cluster_circuit_breakers(cluster); - size_t num_thresholds; - const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const* - thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds( - circuit_breakers, &num_thresholds); - for (size_t i = 0; i < num_thresholds; ++i) { - const auto* threshold = thresholds[i]; - if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority( - threshold) == envoy_config_core_v3_DEFAULT) { - const google_protobuf_UInt32Value* max_requests = - envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests( - threshold); - if (max_requests != nullptr) { - cds_update->max_concurrent_requests = - google_protobuf_UInt32Value_value(max_requests); - } - break; - } - } - } - // Outlier detection config. - if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) { - ValidationErrors::ScopedField field(&errors, ".outlier_detection"); - OutlierDetectionConfig outlier_detection_update; - const envoy_config_cluster_v3_OutlierDetection* outlier_detection = - envoy_config_cluster_v3_Cluster_outlier_detection(cluster); - const google_protobuf_Duration* duration = - envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection); - if (duration != nullptr) { - ValidationErrors::ScopedField field(&errors, ".interval"); - outlier_detection_update.interval = ParseDuration(duration, &errors); - } - duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time( - outlier_detection); - if (duration != nullptr) { - ValidationErrors::ScopedField field(&errors, ".base_ejection_time"); - outlier_detection_update.base_ejection_time = - ParseDuration(duration, &errors); - } - duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time( - outlier_detection); - if (duration != nullptr) { - ValidationErrors::ScopedField field(&errors, ".max_ejection_time"); - outlier_detection_update.max_ejection_time = - ParseDuration(duration, &errors); - } - const google_protobuf_UInt32Value* max_ejection_percent = - envoy_config_cluster_v3_OutlierDetection_max_ejection_percent( - outlier_detection); - if (max_ejection_percent != nullptr) { - outlier_detection_update.max_ejection_percent = - google_protobuf_UInt32Value_value(max_ejection_percent); - if (outlier_detection_update.max_ejection_percent > 100) { - ValidationErrors::ScopedField field(&errors, ".max_ejection_percent"); - errors.AddError("value must be <= 100"); - } - } - const google_protobuf_UInt32Value* enforcing_success_rate = - envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate( - outlier_detection); - if (enforcing_success_rate != nullptr) { - uint32_t enforcement_percentage = - google_protobuf_UInt32Value_value(enforcing_success_rate); - if (enforcement_percentage > 100) { - ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate"); - errors.AddError("value must be <= 100"); - } - if (enforcement_percentage != 0) { - OutlierDetectionConfig::SuccessRateEjection success_rate_ejection; - success_rate_ejection.enforcement_percentage = enforcement_percentage; - const google_protobuf_UInt32Value* minimum_hosts = - envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts( - outlier_detection); - if (minimum_hosts != nullptr) { - success_rate_ejection.minimum_hosts = - google_protobuf_UInt32Value_value(minimum_hosts); - } - const google_protobuf_UInt32Value* request_volume = - envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume( - outlier_detection); - if (request_volume != nullptr) { - success_rate_ejection.request_volume = - google_protobuf_UInt32Value_value(request_volume); - } - const google_protobuf_UInt32Value* stdev_factor = - envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor( - outlier_detection); - if (stdev_factor != nullptr) { - success_rate_ejection.stdev_factor = - google_protobuf_UInt32Value_value(stdev_factor); - } - outlier_detection_update.success_rate_ejection = success_rate_ejection; - } - } - const google_protobuf_UInt32Value* enforcing_failure_percentage = - envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage( - outlier_detection); - if (enforcing_failure_percentage != nullptr) { - uint32_t enforcement_percentage = - google_protobuf_UInt32Value_value(enforcing_failure_percentage); - if (enforcement_percentage > 100) { - ValidationErrors::ScopedField field(&errors, - ".enforcing_failure_percentage"); - errors.AddError("value must be <= 100"); - } - if (enforcement_percentage != 0) { - OutlierDetectionConfig::FailurePercentageEjection - failure_percentage_ejection; - failure_percentage_ejection.enforcement_percentage = - enforcement_percentage; - const google_protobuf_UInt32Value* minimum_hosts = - envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts( - outlier_detection); - if (minimum_hosts != nullptr) { - failure_percentage_ejection.minimum_hosts = - google_protobuf_UInt32Value_value(minimum_hosts); - } - const google_protobuf_UInt32Value* request_volume = - envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume( - outlier_detection); - if (request_volume != nullptr) { - failure_percentage_ejection.request_volume = - google_protobuf_UInt32Value_value(request_volume); - } - const google_protobuf_UInt32Value* threshold = - envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold( - outlier_detection); - if (threshold != nullptr) { - failure_percentage_ejection.threshold = - google_protobuf_UInt32Value_value(threshold); - if (enforcement_percentage > 100) { - ValidationErrors::ScopedField field( - &errors, ".failure_percentage_threshold"); - errors.AddError("value must be <= 100"); - } - } - outlier_detection_update.failure_percentage_ejection = - failure_percentage_ejection; - } - } - cds_update->outlier_detection = outlier_detection_update; - } - // Validate override host status. - const auto* common_lb_config = - envoy_config_cluster_v3_Cluster_common_lb_config(cluster); - bool override_host_status_found = false; - if (common_lb_config != nullptr) { - ValidationErrors::ScopedField field(&errors, ".common_lb_config"); - const auto* override_host_status = - envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status( - common_lb_config); - if (override_host_status != nullptr) { - ValidationErrors::ScopedField field(&errors, ".override_host_status"); - size_t size; - const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses( - override_host_status, &size); - for (size_t i = 0; i < size; ++i) { - auto status = XdsHealthStatus::FromUpb(statuses[i]); - if (status.has_value()) { - cds_update->override_host_statuses.Add(*status); - } - } - override_host_status_found = true; - } - } - // If the field is not set, we default to [UNKNOWN, HEALTHY]. - if (!override_host_status_found) { - cds_update->override_host_statuses.Add( - XdsHealthStatus(XdsHealthStatus::kUnknown)); - cds_update->override_host_statuses.Add( - XdsHealthStatus(XdsHealthStatus::kHealthy)); - } - // Record telemetry labels (if any). - const envoy_config_core_v3_Metadata* metadata = - envoy_config_cluster_v3_Cluster_metadata(cluster); - if (metadata != nullptr) { - google_protobuf_Struct* telemetry_labels_struct; - if (envoy_config_core_v3_Metadata_filter_metadata_get( - metadata, - StdStringToUpbString( - absl::string_view("com.google.csm.telemetry_labels")), - &telemetry_labels_struct)) { - size_t iter = kUpb_Map_Begin; - const google_protobuf_Struct_FieldsEntry* fields_entry; - while ((fields_entry = google_protobuf_Struct_fields_next( - telemetry_labels_struct, &iter)) != nullptr) { - // Adds any entry whose value is a string to telemetry_labels. - const google_protobuf_Value* value = - google_protobuf_Struct_FieldsEntry_value(fields_entry); - if (google_protobuf_Value_has_string_value(value)) { - if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( - fields_entry)) == "service_name") { - cds_update->service_telemetry_label = RefCountedStringValue( - UpbStringToAbsl(google_protobuf_Value_string_value(value))); - } else if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( - fields_entry)) == "service_namespace") { - cds_update->namespace_telemetry_label = RefCountedStringValue( - UpbStringToAbsl(google_protobuf_Value_string_value(value))); - } - } - } - } - } - // Return result. - if (!errors.ok()) { - return errors.status(absl::StatusCode::kInvalidArgument, - "errors validating Cluster resource"); - } - return cds_update; -} - -void MaybeLogCluster(const XdsResourceType::DecodeContext& context, - const envoy_config_cluster_v3_Cluster* cluster) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { - const upb_MessageDef* msg_type = - envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab); - char buf[10240]; - upb_TextEncode(reinterpret_cast(cluster), msg_type, - nullptr, 0, buf, sizeof(buf)); - VLOG(2) << "[xds_client " << context.client << "] Cluster: " << buf; - } -} - -} // namespace - -XdsResourceType::DecodeResult XdsClusterResourceType::Decode( - const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const { - DecodeResult result; - // Parse serialized proto. - auto* resource = envoy_config_cluster_v3_Cluster_parse( - serialized_resource.data(), serialized_resource.size(), context.arena); - if (resource == nullptr) { - result.resource = - absl::InvalidArgumentError("Can't parse Cluster resource."); - return result; - } - MaybeLogCluster(context, resource); - // Validate resource. - result.name = - UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource)); - auto cds_resource = CdsResourceParse(context, resource); - if (!cds_resource.ok()) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(ERROR) << "[xds_client " << context.client << "] invalid Cluster " - << *result.name << ": " << cds_resource.status(); - } - result.resource = cds_resource.status(); - } else { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(INFO) << "[xds_client " << context.client << "] parsed Cluster " - << *result.name << ": " << (*cds_resource)->ToString(); - } - result.resource = std::move(*cds_resource); - } - return result; -} - } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_cluster.h b/src/core/xds/grpc/xds_cluster.h index 5eb7fca6aa9..601593541e5 100644 --- a/src/core/xds/grpc/xds_cluster.h +++ b/src/core/xds/grpc/xds_cluster.h @@ -17,31 +17,17 @@ #ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H #define GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H -#include - -#include #include #include -#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" -#include "envoy/config/cluster/v3/cluster.upbdefs.h" -#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h" -#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h" -#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h" -#include "upb/reflection/def.h" - -#include -#include #include "src/core/load_balancing/outlier_detection/outlier_detection.h" #include "src/core/util/json/json.h" -#include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_common_types.h" #include "src/core/xds/grpc/xds_health_status.h" -#include "src/core/xds/xds_client/xds_bootstrap.h" -#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/grpc/xds_server_grpc.h" #include "src/core/xds/xds_client/xds_resource_type.h" #include "src/core/xds/xds_client/xds_resource_type_impl.h" @@ -84,7 +70,7 @@ struct XdsClusterResource : public XdsResourceType::ResourceData { // The LRS server to use for load reporting. // If not set, load reporting will be disabled. - absl::optional lrs_load_reporting_server; + absl::optional lrs_load_reporting_server; // Tls Context used by clients CommonTlsContext common_tls_context; @@ -118,27 +104,6 @@ struct XdsClusterResource : public XdsResourceType::ResourceData { std::string ToString() const; }; -class XdsClusterResourceType - : public XdsResourceTypeImpl { - public: - absl::string_view type_url() const override { - return "envoy.config.cluster.v3.Cluster"; - } - - DecodeResult Decode(const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const override; - - bool AllResourcesRequiredInSotW() const override { return true; } - - void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { - envoy_config_cluster_v3_Cluster_getmsgdef(symtab); - envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab); - envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef( - symtab); - envoy_extensions_upstreams_http_v3_HttpProtocolOptions_getmsgdef(symtab); - } -}; - } // namespace grpc_core #endif // GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_H diff --git a/src/core/xds/grpc/xds_cluster_parser.cc b/src/core/xds/grpc/xds_cluster_parser.cc new file mode 100644 index 00000000000..50e3807b9b9 --- /dev/null +++ b/src/core/xds/grpc/xds_cluster_parser.cc @@ -0,0 +1,730 @@ +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_cluster_parser.h" + +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.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/cluster/v3/outlier_detection.upb.h" +#include "envoy/config/core/v3/address.upb.h" +#include "envoy/config/core/v3/base.upb.h" +#include "envoy/config/core/v3/config_source.upb.h" +#include "envoy/config/core/v3/extension.upb.h" +#include "envoy/config/core/v3/health_check.upb.h" +#include "envoy/config/core/v3/protocol.upb.h" +#include "envoy/config/endpoint/v3/endpoint.upb.h" +#include "envoy/config/endpoint/v3/endpoint_components.upb.h" +#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/struct.upb.h" +#include "google/protobuf/wrappers.upb.h" +#include "upb/base/string_view.h" +#include "upb/text/encode.h" + +#include "src/core/lib/config/core_configuration.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/host_port.h" +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/load_balancing/lb_policy_registry.h" +#include "src/core/util/upb_utils.h" +#include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_lb_policy_registry.h" + +namespace grpc_core { + +namespace { + +CommonTlsContext UpstreamTlsContextParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_core_v3_TransportSocket* transport_socket, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".typed_config"); + const auto* typed_config = + envoy_config_core_v3_TransportSocket_typed_config(transport_socket); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) return {}; + if (extension->type != + "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") { + ValidationErrors::ScopedField field(errors, ".type_url"); + errors->AddError("unsupported transport socket type"); + return {}; + } + absl::string_view* serialized_upstream_tls_context = + absl::get_if(&extension->value); + if (serialized_upstream_tls_context == nullptr) { + errors->AddError("can't decode UpstreamTlsContext"); + return {}; + } + const auto* upstream_tls_context = + envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( + serialized_upstream_tls_context->data(), + serialized_upstream_tls_context->size(), context.arena); + if (upstream_tls_context == nullptr) { + errors->AddError("can't decode UpstreamTlsContext"); + return {}; + } + ValidationErrors::ScopedField field3(errors, ".common_tls_context"); + const auto* common_tls_context_proto = + envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context( + upstream_tls_context); + CommonTlsContext common_tls_context; + if (common_tls_context_proto != nullptr) { + common_tls_context = + CommonTlsContextParse(context, common_tls_context_proto, errors); + } + if (common_tls_context.certificate_validation_context + .ca_certificate_provider_instance.instance_name.empty()) { + errors->AddError("no CA certificate provider instance configured"); + } + return common_tls_context; +} + +XdsClusterResource::Eds EdsConfigParse( + const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { + XdsClusterResource::Eds eds; + ValidationErrors::ScopedField field(errors, ".eds_cluster_config"); + const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config = + envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); + if (eds_cluster_config == nullptr) { + errors->AddError("field not present"); + } else { + // Validate ConfigSource. + { + ValidationErrors::ScopedField field(errors, ".eds_config"); + const envoy_config_core_v3_ConfigSource* eds_config = + envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( + eds_cluster_config); + if (eds_config == nullptr) { + errors->AddError("field not present"); + } else { + if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) && + !envoy_config_core_v3_ConfigSource_has_self(eds_config)) { + errors->AddError("ConfigSource is not ads or self"); + } + } + } + // Record EDS service_name (if any). + // This field is required if the CDS resource has an xdstp name. + eds.eds_service_name = UpbStringToStdString( + envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( + eds_cluster_config)); + if (eds.eds_service_name.empty()) { + absl::string_view cluster_name = + UpbStringToAbsl(envoy_config_cluster_v3_Cluster_name(cluster)); + if (absl::StartsWith(cluster_name, "xdstp:")) { + ValidationErrors::ScopedField field(errors, ".service_name"); + errors->AddError("must be set if Cluster resource has an xdstp name"); + } + } + } + return eds; +} + +XdsClusterResource::LogicalDns LogicalDnsParse( + const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { + XdsClusterResource::LogicalDns logical_dns; + ValidationErrors::ScopedField field(errors, ".load_assignment"); + const auto* load_assignment = + envoy_config_cluster_v3_Cluster_load_assignment(cluster); + if (load_assignment == nullptr) { + errors->AddError("field not present for LOGICAL_DNS cluster"); + return logical_dns; + } + ValidationErrors::ScopedField field2(errors, ".endpoints"); + size_t num_localities; + const auto* const* localities = + envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment, + &num_localities); + if (num_localities != 1) { + errors->AddError(absl::StrCat( + "must contain exactly one locality for LOGICAL_DNS cluster, found ", + num_localities)); + return logical_dns; + } + ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints"); + size_t num_endpoints; + const auto* const* endpoints = + envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0], + &num_endpoints); + if (num_endpoints != 1) { + errors->AddError(absl::StrCat( + "must contain exactly one endpoint for LOGICAL_DNS cluster, found ", + num_endpoints)); + return logical_dns; + } + ValidationErrors::ScopedField field4(errors, "[0].endpoint"); + const auto* endpoint = + envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]); + if (endpoint == nullptr) { + errors->AddError("field not present"); + return logical_dns; + } + ValidationErrors::ScopedField field5(errors, ".address"); + const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint); + if (address == nullptr) { + errors->AddError("field not present"); + return logical_dns; + } + ValidationErrors::ScopedField field6(errors, ".socket_address"); + const auto* socket_address = + envoy_config_core_v3_Address_socket_address(address); + if (socket_address == nullptr) { + errors->AddError("field not present"); + return logical_dns; + } + if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size != + 0) { + ValidationErrors::ScopedField field(errors, ".resolver_name"); + errors->AddError( + "LOGICAL_DNS clusters must NOT have a custom resolver name set"); + } + absl::string_view address_str = UpbStringToAbsl( + envoy_config_core_v3_SocketAddress_address(socket_address)); + if (address_str.empty()) { + ValidationErrors::ScopedField field(errors, ".address"); + errors->AddError("field not present"); + } + if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) { + ValidationErrors::ScopedField field(errors, ".port_value"); + errors->AddError("field not present"); + } + logical_dns.hostname = JoinHostPort( + address_str, + envoy_config_core_v3_SocketAddress_port_value(socket_address)); + return logical_dns; +} + +XdsClusterResource::Aggregate AggregateClusterParse( + const XdsResourceType::DecodeContext& context, + absl::string_view serialized_config, ValidationErrors* errors) { + XdsClusterResource::Aggregate aggregate; + const auto* aggregate_cluster_config = + envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse( + serialized_config.data(), serialized_config.size(), context.arena); + if (aggregate_cluster_config == nullptr) { + errors->AddError("can't parse aggregate cluster config"); + return aggregate; + } + size_t size; + const upb_StringView* clusters = + envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters( + aggregate_cluster_config, &size); + if (size == 0) { + ValidationErrors::ScopedField field(errors, ".clusters"); + errors->AddError("must be non-empty"); + } + for (size_t i = 0; i < size; ++i) { + aggregate.prioritized_cluster_names.emplace_back( + UpbStringToStdString(clusters[i])); + } + return aggregate; +} + +void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context, + const envoy_config_cluster_v3_Cluster* cluster, + XdsClusterResource* cds_update, + ValidationErrors* errors) { + // First, check the new load_balancing_policy field. + const auto* load_balancing_policy = + envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster); + if (load_balancing_policy != nullptr) { + const auto& registry = + static_cast(context.client->bootstrap()) + .lb_policy_registry(); + ValidationErrors::ScopedField field(errors, ".load_balancing_policy"); + const size_t original_error_count = errors->size(); + cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig( + context, load_balancing_policy, errors); + // If there were no conversion errors, validate that the converted config + // parses with the gRPC LB policy registry. + if (original_error_count == errors->size()) { + auto config = CoreConfiguration::Get() + .lb_policy_registry() + .ParseLoadBalancingConfig( + Json::FromArray(cds_update->lb_policy_config)); + if (!config.ok()) errors->AddError(config.status().message()); + } + return; + } + // Didn't find load_balancing_policy field, so fall back to the old + // lb_policy enum field. + if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == + envoy_config_cluster_v3_Cluster_ROUND_ROBIN) { + cds_update->lb_policy_config = { + Json::FromObject({ + {"xds_wrr_locality_experimental", + Json::FromObject({ + {"childPolicy", Json::FromArray({ + Json::FromObject({ + {"round_robin", Json::FromObject({})}, + }), + })}, + })}, + }), + }; + } else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == + envoy_config_cluster_v3_Cluster_RING_HASH) { + // Record ring hash lb config + auto* ring_hash_config = + envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster); + uint64_t min_ring_size = 1024; + uint64_t max_ring_size = 8388608; + if (ring_hash_config != nullptr) { + ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config"); + const google_protobuf_UInt64Value* uint64_value = + envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size( + ring_hash_config); + if (uint64_value != nullptr) { + ValidationErrors::ScopedField field(errors, ".maximum_ring_size"); + max_ring_size = google_protobuf_UInt64Value_value(uint64_value); + if (max_ring_size > 8388608 || max_ring_size == 0) { + errors->AddError("must be in the range of 1 to 8388608"); + } + } + uint64_value = + envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size( + ring_hash_config); + if (uint64_value != nullptr) { + ValidationErrors::ScopedField field(errors, ".minimum_ring_size"); + min_ring_size = google_protobuf_UInt64Value_value(uint64_value); + if (min_ring_size > 8388608 || min_ring_size == 0) { + errors->AddError("must be in the range of 1 to 8388608"); + } + if (min_ring_size > max_ring_size) { + errors->AddError("cannot be greater than maximum_ring_size"); + } + } + if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( + ring_hash_config) != + envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) { + ValidationErrors::ScopedField field(errors, ".hash_function"); + errors->AddError("invalid hash function"); + } + } + cds_update->lb_policy_config = { + Json::FromObject({ + {"ring_hash_experimental", + Json::FromObject({ + {"minRingSize", Json::FromNumber(min_ring_size)}, + {"maxRingSize", Json::FromNumber(max_ring_size)}, + })}, + }), + }; + } else { + ValidationErrors::ScopedField field(errors, ".lb_policy"); + errors->AddError("LB policy is not supported"); + } +} + +void ParseUpstreamConfig( + const XdsResourceType::DecodeContext& context, + const envoy_config_core_v3_TypedExtensionConfig* upstream_config, + XdsClusterResource* cds_update, ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".typed_config"); + const auto* typed_config = + envoy_config_core_v3_TypedExtensionConfig_typed_config(upstream_config); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) return; + if (extension->type != + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions") { + ValidationErrors::ScopedField field(errors, ".type_url"); + errors->AddError("unsupported upstream config type"); + return; + } + absl::string_view* serialized_http_protocol_options = + absl::get_if(&extension->value); + if (serialized_http_protocol_options == nullptr) { + errors->AddError("can't decode HttpProtocolOptions"); + return; + } + const auto* http_protocol_options = + envoy_extensions_upstreams_http_v3_HttpProtocolOptions_parse( + serialized_http_protocol_options->data(), + serialized_http_protocol_options->size(), context.arena); + if (http_protocol_options == nullptr) { + errors->AddError("can't decode HttpProtocolOptions"); + return; + } + ValidationErrors::ScopedField field2(errors, ".common_http_protocol_options"); + const auto* common_http_protocol_options = + envoy_extensions_upstreams_http_v3_HttpProtocolOptions_common_http_protocol_options( + http_protocol_options); + if (common_http_protocol_options != nullptr) { + const auto* idle_timeout = + envoy_config_core_v3_HttpProtocolOptions_idle_timeout( + common_http_protocol_options); + if (idle_timeout != nullptr) { + ValidationErrors::ScopedField field(errors, ".idle_timeout"); + cds_update->connection_idle_timeout = ParseDuration(idle_timeout, errors); + } + } +} + +absl::StatusOr> CdsResourceParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_cluster_v3_Cluster* cluster) { + auto cds_update = std::make_shared(); + ValidationErrors errors; + // Check the cluster discovery type. + if (envoy_config_cluster_v3_Cluster_type(cluster) == + envoy_config_cluster_v3_Cluster_EDS) { + cds_update->type = EdsConfigParse(cluster, &errors); + } else if (envoy_config_cluster_v3_Cluster_type(cluster) == + envoy_config_cluster_v3_Cluster_LOGICAL_DNS) { + cds_update->type = LogicalDnsParse(cluster, &errors); + } else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { + ValidationErrors::ScopedField field(&errors, ".cluster_type"); + const auto* custom_cluster_type = + envoy_config_cluster_v3_Cluster_cluster_type(cluster); + CHECK_NE(custom_cluster_type, nullptr); + ValidationErrors::ScopedField field2(&errors, ".typed_config"); + const auto* typed_config = + envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config( + custom_cluster_type); + if (typed_config == nullptr) { + errors.AddError("field not present"); + } else { + absl::string_view type_url = absl::StripPrefix( + UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), + "type.googleapis.com/"); + if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") { + ValidationErrors::ScopedField field(&errors, ".type_url"); + errors.AddError( + absl::StrCat("unknown cluster_type extension: ", type_url)); + } else { + // Retrieve aggregate clusters. + ValidationErrors::ScopedField field( + &errors, + ".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]"); + absl::string_view serialized_config = + UpbStringToAbsl(google_protobuf_Any_value(typed_config)); + cds_update->type = + AggregateClusterParse(context, serialized_config, &errors); + } + } + } else { + ValidationErrors::ScopedField field(&errors, ".type"); + errors.AddError("unknown discovery type"); + } + // Check the LB policy. + ParseLbPolicyConfig(context, cluster, cds_update.get(), &errors); + // transport_socket + auto* transport_socket = + envoy_config_cluster_v3_Cluster_transport_socket(cluster); + if (transport_socket != nullptr) { + ValidationErrors::ScopedField field(&errors, ".transport_socket"); + cds_update->common_tls_context = + UpstreamTlsContextParse(context, transport_socket, &errors); + } + // Record LRS server name (if any). + const envoy_config_core_v3_ConfigSource* lrs_server = + envoy_config_cluster_v3_Cluster_lrs_server(cluster); + if (lrs_server != nullptr) { + if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) { + ValidationErrors::ScopedField field(&errors, ".lrs_server"); + errors.AddError("ConfigSource is not self"); + } + cds_update->lrs_load_reporting_server.emplace( + static_cast(context.server)); + } + // Protocol options. + auto* upstream_config = + envoy_config_cluster_v3_Cluster_upstream_config(cluster); + if (upstream_config != nullptr) { + ValidationErrors::ScopedField field(&errors, ".upstream_config"); + ParseUpstreamConfig(context, upstream_config, cds_update.get(), &errors); + } + // The Cluster resource encodes the circuit breaking parameters in a list of + // Thresholds messages, where each message specifies the parameters for a + // particular RoutingPriority. we will look only at the first entry in the + // list for priority DEFAULT and default to 1024 if not found. + if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) { + const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers = + envoy_config_cluster_v3_Cluster_circuit_breakers(cluster); + size_t num_thresholds; + const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const* + thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds( + circuit_breakers, &num_thresholds); + for (size_t i = 0; i < num_thresholds; ++i) { + const auto* threshold = thresholds[i]; + if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority( + threshold) == envoy_config_core_v3_DEFAULT) { + const google_protobuf_UInt32Value* max_requests = + envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests( + threshold); + if (max_requests != nullptr) { + cds_update->max_concurrent_requests = + google_protobuf_UInt32Value_value(max_requests); + } + break; + } + } + } + // Outlier detection config. + if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) { + ValidationErrors::ScopedField field(&errors, ".outlier_detection"); + OutlierDetectionConfig outlier_detection_update; + const envoy_config_cluster_v3_OutlierDetection* outlier_detection = + envoy_config_cluster_v3_Cluster_outlier_detection(cluster); + const google_protobuf_Duration* duration = + envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection); + if (duration != nullptr) { + ValidationErrors::ScopedField field(&errors, ".interval"); + outlier_detection_update.interval = ParseDuration(duration, &errors); + } + duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time( + outlier_detection); + if (duration != nullptr) { + ValidationErrors::ScopedField field(&errors, ".base_ejection_time"); + outlier_detection_update.base_ejection_time = + ParseDuration(duration, &errors); + } + duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time( + outlier_detection); + if (duration != nullptr) { + ValidationErrors::ScopedField field(&errors, ".max_ejection_time"); + outlier_detection_update.max_ejection_time = + ParseDuration(duration, &errors); + } + const google_protobuf_UInt32Value* max_ejection_percent = + envoy_config_cluster_v3_OutlierDetection_max_ejection_percent( + outlier_detection); + if (max_ejection_percent != nullptr) { + outlier_detection_update.max_ejection_percent = + google_protobuf_UInt32Value_value(max_ejection_percent); + if (outlier_detection_update.max_ejection_percent > 100) { + ValidationErrors::ScopedField field(&errors, ".max_ejection_percent"); + errors.AddError("value must be <= 100"); + } + } + const google_protobuf_UInt32Value* enforcing_success_rate = + envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate( + outlier_detection); + if (enforcing_success_rate != nullptr) { + uint32_t enforcement_percentage = + google_protobuf_UInt32Value_value(enforcing_success_rate); + if (enforcement_percentage > 100) { + ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate"); + errors.AddError("value must be <= 100"); + } + if (enforcement_percentage != 0) { + OutlierDetectionConfig::SuccessRateEjection success_rate_ejection; + success_rate_ejection.enforcement_percentage = enforcement_percentage; + const google_protobuf_UInt32Value* minimum_hosts = + envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts( + outlier_detection); + if (minimum_hosts != nullptr) { + success_rate_ejection.minimum_hosts = + google_protobuf_UInt32Value_value(minimum_hosts); + } + const google_protobuf_UInt32Value* request_volume = + envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume( + outlier_detection); + if (request_volume != nullptr) { + success_rate_ejection.request_volume = + google_protobuf_UInt32Value_value(request_volume); + } + const google_protobuf_UInt32Value* stdev_factor = + envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor( + outlier_detection); + if (stdev_factor != nullptr) { + success_rate_ejection.stdev_factor = + google_protobuf_UInt32Value_value(stdev_factor); + } + outlier_detection_update.success_rate_ejection = success_rate_ejection; + } + } + const google_protobuf_UInt32Value* enforcing_failure_percentage = + envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage( + outlier_detection); + if (enforcing_failure_percentage != nullptr) { + uint32_t enforcement_percentage = + google_protobuf_UInt32Value_value(enforcing_failure_percentage); + if (enforcement_percentage > 100) { + ValidationErrors::ScopedField field(&errors, + ".enforcing_failure_percentage"); + errors.AddError("value must be <= 100"); + } + if (enforcement_percentage != 0) { + OutlierDetectionConfig::FailurePercentageEjection + failure_percentage_ejection; + failure_percentage_ejection.enforcement_percentage = + enforcement_percentage; + const google_protobuf_UInt32Value* minimum_hosts = + envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts( + outlier_detection); + if (minimum_hosts != nullptr) { + failure_percentage_ejection.minimum_hosts = + google_protobuf_UInt32Value_value(minimum_hosts); + } + const google_protobuf_UInt32Value* request_volume = + envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume( + outlier_detection); + if (request_volume != nullptr) { + failure_percentage_ejection.request_volume = + google_protobuf_UInt32Value_value(request_volume); + } + const google_protobuf_UInt32Value* threshold = + envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold( + outlier_detection); + if (threshold != nullptr) { + failure_percentage_ejection.threshold = + google_protobuf_UInt32Value_value(threshold); + if (enforcement_percentage > 100) { + ValidationErrors::ScopedField field( + &errors, ".failure_percentage_threshold"); + errors.AddError("value must be <= 100"); + } + } + outlier_detection_update.failure_percentage_ejection = + failure_percentage_ejection; + } + } + cds_update->outlier_detection = outlier_detection_update; + } + // Validate override host status. + const auto* common_lb_config = + envoy_config_cluster_v3_Cluster_common_lb_config(cluster); + bool override_host_status_found = false; + if (common_lb_config != nullptr) { + ValidationErrors::ScopedField field(&errors, ".common_lb_config"); + const auto* override_host_status = + envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status( + common_lb_config); + if (override_host_status != nullptr) { + ValidationErrors::ScopedField field(&errors, ".override_host_status"); + size_t size; + const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses( + override_host_status, &size); + for (size_t i = 0; i < size; ++i) { + auto status = XdsHealthStatus::FromUpb(statuses[i]); + if (status.has_value()) { + cds_update->override_host_statuses.Add(*status); + } + } + override_host_status_found = true; + } + } + // If the field is not set, we default to [UNKNOWN, HEALTHY]. + if (!override_host_status_found) { + cds_update->override_host_statuses.Add( + XdsHealthStatus(XdsHealthStatus::kUnknown)); + cds_update->override_host_statuses.Add( + XdsHealthStatus(XdsHealthStatus::kHealthy)); + } + // Record telemetry labels (if any). + const envoy_config_core_v3_Metadata* metadata = + envoy_config_cluster_v3_Cluster_metadata(cluster); + if (metadata != nullptr) { + google_protobuf_Struct* telemetry_labels_struct; + if (envoy_config_core_v3_Metadata_filter_metadata_get( + metadata, + StdStringToUpbString( + absl::string_view("com.google.csm.telemetry_labels")), + &telemetry_labels_struct)) { + size_t iter = kUpb_Map_Begin; + const google_protobuf_Struct_FieldsEntry* fields_entry; + while ((fields_entry = google_protobuf_Struct_fields_next( + telemetry_labels_struct, &iter)) != nullptr) { + // Adds any entry whose value is a string to telemetry_labels. + const google_protobuf_Value* value = + google_protobuf_Struct_FieldsEntry_value(fields_entry); + if (google_protobuf_Value_has_string_value(value)) { + if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( + fields_entry)) == "service_name") { + cds_update->service_telemetry_label = RefCountedStringValue( + UpbStringToAbsl(google_protobuf_Value_string_value(value))); + } else if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( + fields_entry)) == "service_namespace") { + cds_update->namespace_telemetry_label = RefCountedStringValue( + UpbStringToAbsl(google_protobuf_Value_string_value(value))); + } + } + } + } + } + // Return result. + if (!errors.ok()) { + return errors.status(absl::StatusCode::kInvalidArgument, + "errors validating Cluster resource"); + } + return cds_update; +} + +void MaybeLogCluster(const XdsResourceType::DecodeContext& context, + const envoy_config_cluster_v3_Cluster* cluster) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { + const upb_MessageDef* msg_type = + envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab); + char buf[10240]; + upb_TextEncode(reinterpret_cast(cluster), msg_type, + nullptr, 0, buf, sizeof(buf)); + VLOG(2) << "[xds_client " << context.client << "] Cluster: " << buf; + } +} + +} // namespace + +XdsResourceType::DecodeResult XdsClusterResourceType::Decode( + const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const { + DecodeResult result; + // Parse serialized proto. + auto* resource = envoy_config_cluster_v3_Cluster_parse( + serialized_resource.data(), serialized_resource.size(), context.arena); + if (resource == nullptr) { + result.resource = + absl::InvalidArgumentError("Can't parse Cluster resource."); + return result; + } + MaybeLogCluster(context, resource); + // Validate resource. + result.name = + UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource)); + auto cds_resource = CdsResourceParse(context, resource); + if (!cds_resource.ok()) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(ERROR) << "[xds_client " << context.client << "] invalid Cluster " + << *result.name << ": " << cds_resource.status(); + } + result.resource = cds_resource.status(); + } else { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(INFO) << "[xds_client " << context.client << "] parsed Cluster " + << *result.name << ": " << (*cds_resource)->ToString(); + } + result.resource = std::move(*cds_resource); + } + return result; +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_cluster_parser.h b/src/core/xds/grpc/xds_cluster_parser.h new file mode 100644 index 00000000000..109818a079f --- /dev/null +++ b/src/core/xds/grpc/xds_cluster_parser.h @@ -0,0 +1,57 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H + +#include "absl/strings/string_view.h" +#include "envoy/config/cluster/v3/cluster.upbdefs.h" +#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h" +#include "upb/reflection/def.h" + +#include "src/core/xds/grpc/xds_cluster.h" +#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/xds_client/xds_resource_type.h" +#include "src/core/xds/xds_client/xds_resource_type_impl.h" + +namespace grpc_core { + +class XdsClusterResourceType + : public XdsResourceTypeImpl { + public: + absl::string_view type_url() const override { + return "envoy.config.cluster.v3.Cluster"; + } + + DecodeResult Decode(const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const override; + + bool AllResourcesRequiredInSotW() const override { return true; } + + void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { + envoy_config_cluster_v3_Cluster_getmsgdef(symtab); + envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab); + envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef( + symtab); + envoy_extensions_upstreams_http_v3_HttpProtocolOptions_getmsgdef(symtab); + } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H diff --git a/src/core/xds/grpc/xds_common_types.cc b/src/core/xds/grpc/xds_common_types.cc index 2e8fafda588..4617334dddb 100644 --- a/src/core/xds/grpc/xds_common_types.cc +++ b/src/core/xds/grpc/xds_common_types.cc @@ -16,60 +16,12 @@ #include "src/core/xds/grpc/xds_common_types.h" -#include -#include - -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "envoy/extensions/transport_sockets/tls/v3/common.upb.h" -#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" -#include "envoy/type/matcher/v3/regex.upb.h" -#include "envoy/type/matcher/v3/string.upb.h" -#include "google/protobuf/any.upb.h" -#include "google/protobuf/struct.upb.h" -#include "google/protobuf/struct.upbdefs.h" -#include "google/protobuf/wrappers.upb.h" -#include "upb/base/status.hpp" -#include "upb/json/encode.h" -#include "upb/mem/arena.h" -#include "xds/type/v3/typed_struct.upb.h" - -#include -#include - -#include "src/core/util/json/json_reader.h" -#include "src/core/util/upb_utils.h" -#include "src/core/xds/grpc/xds_bootstrap_grpc.h" -#include "src/core/xds/xds_client/xds_client.h" namespace grpc_core { -// -// ParseDuration() -// - -Duration ParseDuration(const google_protobuf_Duration* proto_duration, - ValidationErrors* errors) { - int64_t seconds = google_protobuf_Duration_seconds(proto_duration); - if (seconds < 0 || seconds > 315576000000) { - ValidationErrors::ScopedField field(errors, ".seconds"); - errors->AddError("value must be in the range [0, 315576000000]"); - } - int32_t nanos = google_protobuf_Duration_nanos(proto_duration); - if (nanos < 0 || nanos > 999999999) { - ValidationErrors::ScopedField field(errors, ".nanos"); - errors->AddError("value must be in the range [0, 999999999]"); - } - return Duration::FromSecondsAndNanoseconds(seconds, nanos); -} - // // CommonTlsContext::CertificateValidationContext // @@ -133,376 +85,4 @@ bool CommonTlsContext::Empty() const { certificate_validation_context.Empty(); } -namespace { - -// CertificateProviderInstance is deprecated but we are still supporting it for -// backward compatibility reasons. Note that we still parse the data into the -// same CertificateProviderPluginInstance struct since the fields are the same. -// TODO(yashykt): Remove this once we stop supporting the old way of fetching -// certificate provider instances. -CommonTlsContext::CertificateProviderPluginInstance -CertificateProviderInstanceParse( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance* - certificate_provider_instance_proto, - ValidationErrors* errors) { - CommonTlsContext::CertificateProviderPluginInstance cert_provider; - cert_provider.instance_name = UpbStringToStdString( - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name( - certificate_provider_instance_proto)); - const auto& bootstrap = - static_cast(context.client->bootstrap()); - if (bootstrap.certificate_providers().find(cert_provider.instance_name) == - bootstrap.certificate_providers().end()) { - ValidationErrors::ScopedField field(errors, ".instance_name"); - errors->AddError( - absl::StrCat("unrecognized certificate provider instance name: ", - cert_provider.instance_name)); - } - cert_provider.certificate_name = UpbStringToStdString( - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name( - certificate_provider_instance_proto)); - return cert_provider; -} - -CommonTlsContext::CertificateProviderPluginInstance -CertificateProviderPluginInstanceParse( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance* - certificate_provider_plugin_instance_proto, - ValidationErrors* errors) { - CommonTlsContext::CertificateProviderPluginInstance cert_provider; - cert_provider.instance_name = UpbStringToStdString( - envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name( - certificate_provider_plugin_instance_proto)); - const auto& bootstrap = - static_cast(context.client->bootstrap()); - if (bootstrap.certificate_providers().find(cert_provider.instance_name) == - bootstrap.certificate_providers().end()) { - ValidationErrors::ScopedField field(errors, ".instance_name"); - errors->AddError( - absl::StrCat("unrecognized certificate provider instance name: ", - cert_provider.instance_name)); - } - cert_provider.certificate_name = UpbStringToStdString( - envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name( - certificate_provider_plugin_instance_proto)); - return cert_provider; -} - -CommonTlsContext::CertificateValidationContext -CertificateValidationContextParse( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext* - certificate_validation_context_proto, - ValidationErrors* errors) { - CommonTlsContext::CertificateValidationContext certificate_validation_context; - size_t len = 0; - auto* subject_alt_names_matchers = - envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names( - certificate_validation_context_proto, &len); - for (size_t i = 0; i < len; ++i) { - ValidationErrors::ScopedField field( - errors, absl::StrCat(".match_subject_alt_names[", i, "]")); - StringMatcher::Type type; - std::string matcher; - if (envoy_type_matcher_v3_StringMatcher_has_exact( - subject_alt_names_matchers[i])) { - 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 = 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 = 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 = 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 = StringMatcher::Type::kSafeRegex; - auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex( - subject_alt_names_matchers[i]); - matcher = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - } else { - errors->AddError("invalid StringMatcher specified"); - continue; - } - bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case( - subject_alt_names_matchers[i]); - absl::StatusOr string_matcher = - StringMatcher::Create(type, matcher, - /*case_sensitive=*/!ignore_case); - if (!string_matcher.ok()) { - errors->AddError(string_matcher.status().message()); - continue; - } - if (type == StringMatcher::Type::kSafeRegex && ignore_case) { - ValidationErrors::ScopedField field(errors, ".ignore_case"); - errors->AddError("not supported for regex matcher"); - continue; - } - certificate_validation_context.match_subject_alt_names.push_back( - std::move(string_matcher.value())); - } - auto* ca_certificate_provider_instance = - envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_ca_certificate_provider_instance( - certificate_validation_context_proto); - if (ca_certificate_provider_instance != nullptr) { - ValidationErrors::ScopedField field(errors, - ".ca_certificate_provider_instance"); - certificate_validation_context.ca_certificate_provider_instance = - CertificateProviderPluginInstanceParse( - context, ca_certificate_provider_instance, errors); - } - if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki( - certificate_validation_context_proto, nullptr) != nullptr) { - ValidationErrors::ScopedField field(errors, ".verify_certificate_spki"); - errors->AddError("feature unsupported"); - } - if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash( - certificate_validation_context_proto, nullptr) != nullptr) { - ValidationErrors::ScopedField field(errors, ".verify_certificate_hash"); - errors->AddError("feature unsupported"); - } - auto* require_signed_certificate_timestamp = - envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp( - certificate_validation_context_proto); - if (require_signed_certificate_timestamp != nullptr && - google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) { - ValidationErrors::ScopedField field( - errors, ".require_signed_certificate_timestamp"); - errors->AddError("feature unsupported"); - } - if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl( - certificate_validation_context_proto)) { - ValidationErrors::ScopedField field(errors, ".crl"); - errors->AddError("feature unsupported"); - } - if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config( - certificate_validation_context_proto)) { - ValidationErrors::ScopedField field(errors, ".custom_validator_config"); - errors->AddError("feature unsupported"); - } - return certificate_validation_context; -} - -} // namespace - -CommonTlsContext CommonTlsContext::Parse( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* - common_tls_context_proto, - ValidationErrors* errors) { - CommonTlsContext common_tls_context; - // The validation context is derived from the oneof in - // 'validation_context_type'. 'validation_context_sds_secret_config' is not - // supported. - auto* combined_validation_context = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context( - common_tls_context_proto); - if (combined_validation_context != nullptr) { - ValidationErrors::ScopedField field(errors, ".combined_validation_context"); - auto* default_validation_context = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context( - combined_validation_context); - if (default_validation_context != nullptr) { - ValidationErrors::ScopedField field(errors, - ".default_validation_context"); - common_tls_context.certificate_validation_context = - CertificateValidationContextParse(context, default_validation_context, - errors); - } - // If after parsing default_validation_context, - // common_tls_context->certificate_validation_context.ca_certificate_provider_instance - // is empty, fall back onto - // 'validation_context_certificate_provider_instance' inside - // 'combined_validation_context'. Note that this way of fetching root - // certificates is deprecated and will be removed in the future. - // TODO(yashykt): Remove this once it's no longer needed. - const auto* validation_context_certificate_provider_instance = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance( - combined_validation_context); - if (common_tls_context.certificate_validation_context - .ca_certificate_provider_instance.Empty() && - validation_context_certificate_provider_instance != nullptr) { - ValidationErrors::ScopedField field( - errors, ".validation_context_certificate_provider_instance"); - common_tls_context.certificate_validation_context - .ca_certificate_provider_instance = CertificateProviderInstanceParse( - context, validation_context_certificate_provider_instance, errors); - } - } else { - auto* validation_context = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context( - common_tls_context_proto); - if (validation_context != nullptr) { - ValidationErrors::ScopedField field(errors, ".validation_context"); - common_tls_context.certificate_validation_context = - CertificateValidationContextParse(context, validation_context, - errors); - } else if ( - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config( - common_tls_context_proto)) { - ValidationErrors::ScopedField field( - errors, ".validation_context_sds_secret_config"); - errors->AddError("feature unsupported"); - } - } - auto* tls_certificate_provider_instance = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_provider_instance( - common_tls_context_proto); - if (tls_certificate_provider_instance != nullptr) { - ValidationErrors::ScopedField field(errors, - ".tls_certificate_provider_instance"); - common_tls_context.tls_certificate_provider_instance = - CertificateProviderPluginInstanceParse( - context, tls_certificate_provider_instance, errors); - } else { - // Fall back onto 'tls_certificate_certificate_provider_instance'. Note that - // this way of fetching identity certificates is deprecated and will be - // removed in the future. - // TODO(yashykt): Remove this once it's no longer needed. - auto* tls_certificate_certificate_provider_instance = - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_certificate_provider_instance( - common_tls_context_proto); - if (tls_certificate_certificate_provider_instance != nullptr) { - ValidationErrors::ScopedField field( - errors, ".tls_certificate_certificate_provider_instance"); - common_tls_context.tls_certificate_provider_instance = - CertificateProviderInstanceParse( - context, tls_certificate_certificate_provider_instance, errors); - } else { - size_t size; - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificates( - common_tls_context_proto, &size); - if (size != 0) { - ValidationErrors::ScopedField field(errors, ".tls_certificates"); - errors->AddError("feature unsupported"); - } - envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_sds_secret_configs( - common_tls_context_proto, &size); - if (size != 0) { - ValidationErrors::ScopedField field( - errors, ".tls_certificate_sds_secret_configs"); - errors->AddError("feature unsupported"); - } - } - } - if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params( - common_tls_context_proto)) { - ValidationErrors::ScopedField field(errors, ".tls_params"); - errors->AddError("feature unsupported"); - } - if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker( - common_tls_context_proto)) { - ValidationErrors::ScopedField field(errors, ".custom_handshaker"); - errors->AddError("feature unsupported"); - } - return common_tls_context; -} - -// -// ExtractXdsExtension -// - -namespace { - -absl::StatusOr ParseProtobufStructToJson( - const XdsResourceType::DecodeContext& context, - const google_protobuf_Struct* resource) { - upb::Status status; - const auto* msg_def = google_protobuf_Struct_getmsgdef(context.symtab); - size_t json_size = - upb_JsonEncode(reinterpret_cast(resource), msg_def, - context.symtab, 0, nullptr, 0, status.ptr()); - if (json_size == static_cast(-1)) { - return absl::InvalidArgumentError( - absl::StrCat("error encoding google::Protobuf::Struct as JSON: ", - upb_Status_ErrorMessage(status.ptr()))); - } - void* buf = upb_Arena_Malloc(context.arena, json_size + 1); - upb_JsonEncode(reinterpret_cast(resource), msg_def, - context.symtab, 0, reinterpret_cast(buf), json_size + 1, - status.ptr()); - auto json = JsonParse(reinterpret_cast(buf)); - if (!json.ok()) { - // This should never happen. - return absl::InternalError( - absl::StrCat("error parsing JSON form of google::Protobuf::Struct " - "produced by upb library: ", - json.status().ToString())); - } - return std::move(*json); -} - -} // namespace - -absl::optional ExtractXdsExtension( - const XdsResourceType::DecodeContext& context, - const google_protobuf_Any* any, ValidationErrors* errors) { - if (any == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - XdsExtension extension; - auto strip_type_prefix = [&]() { - ValidationErrors::ScopedField field(errors, ".type_url"); - if (extension.type.empty()) { - errors->AddError("field not present"); - return false; - } - size_t pos = extension.type.rfind('/'); - if (pos == absl::string_view::npos || pos == extension.type.size() - 1) { - errors->AddError(absl::StrCat("invalid value \"", extension.type, "\"")); - } else { - extension.type = extension.type.substr(pos + 1); - } - return true; - }; - extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any)); - if (!strip_type_prefix()) return absl::nullopt; - extension.validation_fields.emplace_back( - errors, absl::StrCat(".value[", extension.type, "]")); - absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any)); - if (extension.type == "xds.type.v3.TypedStruct" || - extension.type == "udpa.type.v1.TypedStruct") { - const auto* typed_struct = xds_type_v3_TypedStruct_parse( - any_value.data(), any_value.size(), context.arena); - if (typed_struct == nullptr) { - errors->AddError("could not parse"); - return absl::nullopt; - } - extension.type = - UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct)); - if (!strip_type_prefix()) return absl::nullopt; - extension.validation_fields.emplace_back( - errors, absl::StrCat(".value[", extension.type, "]")); - auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct); - if (protobuf_struct == nullptr) { - extension.value = Json::FromObject({}); // Default to empty object. - } else { - auto json = ParseProtobufStructToJson(context, protobuf_struct); - if (!json.ok()) { - errors->AddError(json.status().message()); - return absl::nullopt; - } - extension.value = std::move(*json); - } - } else { - extension.value = any_value; - } - return std::move(extension); -} - } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_common_types.h b/src/core/xds/grpc/xds_common_types.h index 1285e08f936..250f81857b4 100644 --- a/src/core/xds/grpc/xds_common_types.h +++ b/src/core/xds/grpc/xds_common_types.h @@ -21,25 +21,14 @@ #include #include "absl/strings/string_view.h" -#include "absl/types/optional.h" #include "absl/types/variant.h" -#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" -#include "google/protobuf/any.upb.h" -#include "google/protobuf/duration.upb.h" -#include - -#include "src/core/lib/gprpp/time.h" #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/lib/matchers/matchers.h" #include "src/core/util/json/json.h" -#include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { -Duration ParseDuration(const google_protobuf_Duration* proto_duration, - ValidationErrors* errors); - struct CommonTlsContext { struct CertificateProviderPluginInstance { std::string instance_name; @@ -80,12 +69,6 @@ struct CommonTlsContext { std::string ToString() const; bool Empty() const; - - static CommonTlsContext Parse( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* - common_tls_context_proto, - ValidationErrors* errors); }; struct XdsExtension { @@ -99,10 +82,6 @@ struct XdsExtension { std::vector validation_fields; }; -absl::optional ExtractXdsExtension( - const XdsResourceType::DecodeContext& context, - const google_protobuf_Any* any, ValidationErrors* errors); - } // namespace grpc_core #endif // GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_H diff --git a/src/core/xds/grpc/xds_common_types_parser.cc b/src/core/xds/grpc/xds_common_types_parser.cc new file mode 100644 index 00000000000..4aae5e273ee --- /dev/null +++ b/src/core/xds/grpc/xds_common_types_parser.cc @@ -0,0 +1,449 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_common_types_parser.h" + +#include +#include + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "envoy/extensions/transport_sockets/tls/v3/common.upb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" +#include "envoy/type/matcher/v3/regex.upb.h" +#include "envoy/type/matcher/v3/string.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/struct.upb.h" +#include "google/protobuf/struct.upbdefs.h" +#include "google/protobuf/wrappers.upb.h" +#include "upb/base/status.hpp" +#include "upb/json/encode.h" +#include "upb/mem/arena.h" +#include "xds/type/v3/typed_struct.upb.h" + +#include +#include + +#include "src/core/util/json/json_reader.h" +#include "src/core/util/upb_utils.h" +#include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/xds_client/xds_client.h" + +namespace grpc_core { + +// +// ParseDuration() +// + +Duration ParseDuration(const google_protobuf_Duration* proto_duration, + ValidationErrors* errors) { + int64_t seconds = google_protobuf_Duration_seconds(proto_duration); + if (seconds < 0 || seconds > 315576000000) { + ValidationErrors::ScopedField field(errors, ".seconds"); + errors->AddError("value must be in the range [0, 315576000000]"); + } + int32_t nanos = google_protobuf_Duration_nanos(proto_duration); + if (nanos < 0 || nanos > 999999999) { + ValidationErrors::ScopedField field(errors, ".nanos"); + errors->AddError("value must be in the range [0, 999999999]"); + } + return Duration::FromSecondsAndNanoseconds(seconds, nanos); +} + +// +// CommonTlsContextParse() +// + +namespace { + +// CertificateProviderInstance is deprecated but we are still supporting it for +// backward compatibility reasons. Note that we still parse the data into the +// same CertificateProviderPluginInstance struct since the fields are the same. +// TODO(yashykt): Remove this once we stop supporting the old way of fetching +// certificate provider instances. +CommonTlsContext::CertificateProviderPluginInstance +CertificateProviderInstanceParse( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance* + certificate_provider_instance_proto, + ValidationErrors* errors) { + CommonTlsContext::CertificateProviderPluginInstance cert_provider; + cert_provider.instance_name = UpbStringToStdString( + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name( + certificate_provider_instance_proto)); + const auto& bootstrap = + static_cast(context.client->bootstrap()); + if (bootstrap.certificate_providers().find(cert_provider.instance_name) == + bootstrap.certificate_providers().end()) { + ValidationErrors::ScopedField field(errors, ".instance_name"); + errors->AddError( + absl::StrCat("unrecognized certificate provider instance name: ", + cert_provider.instance_name)); + } + cert_provider.certificate_name = UpbStringToStdString( + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name( + certificate_provider_instance_proto)); + return cert_provider; +} + +CommonTlsContext::CertificateProviderPluginInstance +CertificateProviderPluginInstanceParse( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance* + certificate_provider_plugin_instance_proto, + ValidationErrors* errors) { + CommonTlsContext::CertificateProviderPluginInstance cert_provider; + cert_provider.instance_name = UpbStringToStdString( + envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name( + certificate_provider_plugin_instance_proto)); + const auto& bootstrap = + static_cast(context.client->bootstrap()); + if (bootstrap.certificate_providers().find(cert_provider.instance_name) == + bootstrap.certificate_providers().end()) { + ValidationErrors::ScopedField field(errors, ".instance_name"); + errors->AddError( + absl::StrCat("unrecognized certificate provider instance name: ", + cert_provider.instance_name)); + } + cert_provider.certificate_name = UpbStringToStdString( + envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name( + certificate_provider_plugin_instance_proto)); + return cert_provider; +} + +CommonTlsContext::CertificateValidationContext +CertificateValidationContextParse( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext* + certificate_validation_context_proto, + ValidationErrors* errors) { + CommonTlsContext::CertificateValidationContext certificate_validation_context; + size_t len = 0; + auto* subject_alt_names_matchers = + envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names( + certificate_validation_context_proto, &len); + for (size_t i = 0; i < len; ++i) { + ValidationErrors::ScopedField field( + errors, absl::StrCat(".match_subject_alt_names[", i, "]")); + StringMatcher::Type type; + std::string matcher; + if (envoy_type_matcher_v3_StringMatcher_has_exact( + subject_alt_names_matchers[i])) { + 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 = 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 = 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 = 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 = StringMatcher::Type::kSafeRegex; + auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex( + subject_alt_names_matchers[i]); + matcher = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); + } else { + errors->AddError("invalid StringMatcher specified"); + continue; + } + bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case( + subject_alt_names_matchers[i]); + absl::StatusOr string_matcher = + StringMatcher::Create(type, matcher, + /*case_sensitive=*/!ignore_case); + if (!string_matcher.ok()) { + errors->AddError(string_matcher.status().message()); + continue; + } + if (type == StringMatcher::Type::kSafeRegex && ignore_case) { + ValidationErrors::ScopedField field(errors, ".ignore_case"); + errors->AddError("not supported for regex matcher"); + continue; + } + certificate_validation_context.match_subject_alt_names.push_back( + std::move(string_matcher.value())); + } + auto* ca_certificate_provider_instance = + envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_ca_certificate_provider_instance( + certificate_validation_context_proto); + if (ca_certificate_provider_instance != nullptr) { + ValidationErrors::ScopedField field(errors, + ".ca_certificate_provider_instance"); + certificate_validation_context.ca_certificate_provider_instance = + CertificateProviderPluginInstanceParse( + context, ca_certificate_provider_instance, errors); + } + if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki( + certificate_validation_context_proto, nullptr) != nullptr) { + ValidationErrors::ScopedField field(errors, ".verify_certificate_spki"); + errors->AddError("feature unsupported"); + } + if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash( + certificate_validation_context_proto, nullptr) != nullptr) { + ValidationErrors::ScopedField field(errors, ".verify_certificate_hash"); + errors->AddError("feature unsupported"); + } + auto* require_signed_certificate_timestamp = + envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp( + certificate_validation_context_proto); + if (require_signed_certificate_timestamp != nullptr && + google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) { + ValidationErrors::ScopedField field( + errors, ".require_signed_certificate_timestamp"); + errors->AddError("feature unsupported"); + } + if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl( + certificate_validation_context_proto)) { + ValidationErrors::ScopedField field(errors, ".crl"); + errors->AddError("feature unsupported"); + } + if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config( + certificate_validation_context_proto)) { + ValidationErrors::ScopedField field(errors, ".custom_validator_config"); + errors->AddError("feature unsupported"); + } + return certificate_validation_context; +} + +} // namespace + +CommonTlsContext CommonTlsContextParse( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* + common_tls_context_proto, + ValidationErrors* errors) { + CommonTlsContext common_tls_context; + // The validation context is derived from the oneof in + // 'validation_context_type'. 'validation_context_sds_secret_config' is not + // supported. + auto* combined_validation_context = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context( + common_tls_context_proto); + if (combined_validation_context != nullptr) { + ValidationErrors::ScopedField field(errors, ".combined_validation_context"); + auto* default_validation_context = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context( + combined_validation_context); + if (default_validation_context != nullptr) { + ValidationErrors::ScopedField field(errors, + ".default_validation_context"); + common_tls_context.certificate_validation_context = + CertificateValidationContextParse(context, default_validation_context, + errors); + } + // If after parsing default_validation_context, + // common_tls_context->certificate_validation_context.ca_certificate_provider_instance + // is empty, fall back onto + // 'validation_context_certificate_provider_instance' inside + // 'combined_validation_context'. Note that this way of fetching root + // certificates is deprecated and will be removed in the future. + // TODO(yashykt): Remove this once it's no longer needed. + const auto* validation_context_certificate_provider_instance = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance( + combined_validation_context); + if (common_tls_context.certificate_validation_context + .ca_certificate_provider_instance.Empty() && + validation_context_certificate_provider_instance != nullptr) { + ValidationErrors::ScopedField field( + errors, ".validation_context_certificate_provider_instance"); + common_tls_context.certificate_validation_context + .ca_certificate_provider_instance = CertificateProviderInstanceParse( + context, validation_context_certificate_provider_instance, errors); + } + } else { + auto* validation_context = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context( + common_tls_context_proto); + if (validation_context != nullptr) { + ValidationErrors::ScopedField field(errors, ".validation_context"); + common_tls_context.certificate_validation_context = + CertificateValidationContextParse(context, validation_context, + errors); + } else if ( + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config( + common_tls_context_proto)) { + ValidationErrors::ScopedField field( + errors, ".validation_context_sds_secret_config"); + errors->AddError("feature unsupported"); + } + } + auto* tls_certificate_provider_instance = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_provider_instance( + common_tls_context_proto); + if (tls_certificate_provider_instance != nullptr) { + ValidationErrors::ScopedField field(errors, + ".tls_certificate_provider_instance"); + common_tls_context.tls_certificate_provider_instance = + CertificateProviderPluginInstanceParse( + context, tls_certificate_provider_instance, errors); + } else { + // Fall back onto 'tls_certificate_certificate_provider_instance'. Note that + // this way of fetching identity certificates is deprecated and will be + // removed in the future. + // TODO(yashykt): Remove this once it's no longer needed. + auto* tls_certificate_certificate_provider_instance = + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_certificate_provider_instance( + common_tls_context_proto); + if (tls_certificate_certificate_provider_instance != nullptr) { + ValidationErrors::ScopedField field( + errors, ".tls_certificate_certificate_provider_instance"); + common_tls_context.tls_certificate_provider_instance = + CertificateProviderInstanceParse( + context, tls_certificate_certificate_provider_instance, errors); + } else { + size_t size; + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificates( + common_tls_context_proto, &size); + if (size != 0) { + ValidationErrors::ScopedField field(errors, ".tls_certificates"); + errors->AddError("feature unsupported"); + } + envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_sds_secret_configs( + common_tls_context_proto, &size); + if (size != 0) { + ValidationErrors::ScopedField field( + errors, ".tls_certificate_sds_secret_configs"); + errors->AddError("feature unsupported"); + } + } + } + if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params( + common_tls_context_proto)) { + ValidationErrors::ScopedField field(errors, ".tls_params"); + errors->AddError("feature unsupported"); + } + if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker( + common_tls_context_proto)) { + ValidationErrors::ScopedField field(errors, ".custom_handshaker"); + errors->AddError("feature unsupported"); + } + return common_tls_context; +} + +// +// ExtractXdsExtension +// + +namespace { + +absl::StatusOr ParseProtobufStructToJson( + const XdsResourceType::DecodeContext& context, + const google_protobuf_Struct* resource) { + upb::Status status; + const auto* msg_def = google_protobuf_Struct_getmsgdef(context.symtab); + size_t json_size = + upb_JsonEncode(reinterpret_cast(resource), msg_def, + context.symtab, 0, nullptr, 0, status.ptr()); + if (json_size == static_cast(-1)) { + return absl::InvalidArgumentError( + absl::StrCat("error encoding google::Protobuf::Struct as JSON: ", + upb_Status_ErrorMessage(status.ptr()))); + } + void* buf = upb_Arena_Malloc(context.arena, json_size + 1); + upb_JsonEncode(reinterpret_cast(resource), msg_def, + context.symtab, 0, reinterpret_cast(buf), json_size + 1, + status.ptr()); + auto json = JsonParse(reinterpret_cast(buf)); + if (!json.ok()) { + // This should never happen. + return absl::InternalError( + absl::StrCat("error parsing JSON form of google::Protobuf::Struct " + "produced by upb library: ", + json.status().ToString())); + } + return std::move(*json); +} + +} // namespace + +absl::optional ExtractXdsExtension( + const XdsResourceType::DecodeContext& context, + const google_protobuf_Any* any, ValidationErrors* errors) { + if (any == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + XdsExtension extension; + auto strip_type_prefix = [&]() { + ValidationErrors::ScopedField field(errors, ".type_url"); + if (extension.type.empty()) { + errors->AddError("field not present"); + return false; + } + size_t pos = extension.type.rfind('/'); + if (pos == absl::string_view::npos || pos == extension.type.size() - 1) { + errors->AddError(absl::StrCat("invalid value \"", extension.type, "\"")); + } else { + extension.type = extension.type.substr(pos + 1); + } + return true; + }; + extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any)); + if (!strip_type_prefix()) return absl::nullopt; + extension.validation_fields.emplace_back( + errors, absl::StrCat(".value[", extension.type, "]")); + absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any)); + if (extension.type == "xds.type.v3.TypedStruct" || + extension.type == "udpa.type.v1.TypedStruct") { + const auto* typed_struct = xds_type_v3_TypedStruct_parse( + any_value.data(), any_value.size(), context.arena); + if (typed_struct == nullptr) { + errors->AddError("could not parse"); + return absl::nullopt; + } + extension.type = + UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct)); + if (!strip_type_prefix()) return absl::nullopt; + extension.validation_fields.emplace_back( + errors, absl::StrCat(".value[", extension.type, "]")); + auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct); + if (protobuf_struct == nullptr) { + extension.value = Json::FromObject({}); // Default to empty object. + } else { + auto json = ParseProtobufStructToJson(context, protobuf_struct); + if (!json.ok()) { + errors->AddError(json.status().message()); + return absl::nullopt; + } + extension.value = std::move(*json); + } + } else { + extension.value = any_value; + } + return std::move(extension); +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_common_types_parser.h b/src/core/xds/grpc/xds_common_types_parser.h new file mode 100644 index 00000000000..5633d71fca4 --- /dev/null +++ b/src/core/xds/grpc/xds_common_types_parser.h @@ -0,0 +1,47 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H + +#include "absl/types/optional.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/duration.upb.h" + +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +namespace grpc_core { + +Duration ParseDuration(const google_protobuf_Duration* proto_duration, + ValidationErrors* errors); + +CommonTlsContext CommonTlsContextParse( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* + common_tls_context_proto, + ValidationErrors* errors); + +absl::optional ExtractXdsExtension( + const XdsResourceType::DecodeContext& context, + const google_protobuf_Any* any, ValidationErrors* errors); + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H diff --git a/src/core/xds/grpc/xds_endpoint.cc b/src/core/xds/grpc/xds_endpoint.cc index dc761931d66..7c475cc1ecb 100644 --- a/src/core/xds/grpc/xds_endpoint.cc +++ b/src/core/xds/grpc/xds_endpoint.cc @@ -16,66 +16,13 @@ #include "src/core/xds/grpc/xds_endpoint.h" -#include -#include - -#include -#include -#include -#include #include -#include "absl/log/check.h" -#include "absl/log/log.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" -#include "absl/types/optional.h" -#include "envoy/config/core/v3/address.upb.h" -#include "envoy/config/core/v3/base.upb.h" -#include "envoy/config/endpoint/v3/endpoint.upb.h" -#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" -#include "envoy/config/endpoint/v3/endpoint_components.upb.h" -#include "envoy/type/v3/percent.upb.h" -#include "google/protobuf/wrappers.upb.h" -#include "upb/text/encode.h" - -#include - -#include "src/core/lib/address_utils/parse_address.h" -#include "src/core/lib/address_utils/sockaddr_utils.h" -#include "src/core/lib/channel/channel_args.h" -#include "src/core/lib/debug/trace.h" -#include "src/core/lib/gprpp/env.h" -#include "src/core/lib/gprpp/validation_errors.h" -#include "src/core/lib/iomgr/resolved_address.h" -#include "src/core/util/string.h" -#include "src/core/util/upb_utils.h" -#include "src/core/xds/grpc/xds_health_status.h" -#include "src/core/xds/xds_client/xds_resource_type.h" - -// IWYU pragma: no_include "absl/meta/type_traits.h" namespace grpc_core { -namespace { - -// TODO(roth): Remove this once dualstack support is stable. -bool XdsDualstackEndpointsEnabled() { - auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS"); - if (!value.has_value()) return false; - bool parsed_value; - bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); - return parse_succeeded && parsed_value; -} - -} // namespace - -// -// XdsEndpointResource -// - std::string XdsEndpointResource::Priority::Locality::ToString() const { std::vector endpoint_strings; for (const EndpointAddresses& endpoint : endpoints) { @@ -147,370 +94,4 @@ std::string XdsEndpointResource::ToString() const { drop_config == nullptr ? "" : drop_config->ToString()); } -// -// XdsEndpointResourceType -// - -namespace { - -void MaybeLogClusterLoadAssignment( - const XdsResourceType::DecodeContext& context, - const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { - const upb_MessageDef* msg_type = - envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef( - context.symtab); - char buf[10240]; - upb_TextEncode(reinterpret_cast(cla), msg_type, nullptr, - 0, buf, sizeof(buf)); - VLOG(2) << "[xds_client " << context.client - << "] ClusterLoadAssignment: " << buf; - } -} - -absl::optional ParseCoreAddress( - const envoy_config_core_v3_Address* address, ValidationErrors* errors) { - if (address == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - ValidationErrors::ScopedField field(errors, ".socket_address"); - const envoy_config_core_v3_SocketAddress* socket_address = - envoy_config_core_v3_Address_socket_address(address); - if (socket_address == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - std::string address_str = UpbStringToStdString( - envoy_config_core_v3_SocketAddress_address(socket_address)); - uint32_t port; - { - ValidationErrors::ScopedField field(errors, ".port_value"); - port = envoy_config_core_v3_SocketAddress_port_value(socket_address); - if (GPR_UNLIKELY(port >> 16) != 0) { - errors->AddError("invalid port"); - return absl::nullopt; - } - } - auto addr = StringToSockaddr(address_str, port); - if (!addr.ok()) { - errors->AddError(addr.status().message()); - return absl::nullopt; - } - return *addr; -} - -absl::optional EndpointAddressesParse( - const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, - ValidationErrors* errors) { - // health_status - const int32_t health_status = - envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint); - auto status = XdsHealthStatus::FromUpb(health_status); - if (!status.has_value()) return absl::nullopt; - // load_balancing_weight - uint32_t weight = 1; - { - ValidationErrors::ScopedField field(errors, ".load_balancing_weight"); - const google_protobuf_UInt32Value* load_balancing_weight = - envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); - if (load_balancing_weight != nullptr) { - weight = google_protobuf_UInt32Value_value(load_balancing_weight); - if (weight == 0) { - errors->AddError("must be greater than 0"); - } - } - } - // endpoint - std::vector addresses; - { - ValidationErrors::ScopedField field(errors, ".endpoint"); - const envoy_config_endpoint_v3_Endpoint* endpoint = - envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); - if (endpoint == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - { - ValidationErrors::ScopedField field(errors, ".address"); - auto address = ParseCoreAddress( - envoy_config_endpoint_v3_Endpoint_address(endpoint), errors); - if (address.has_value()) addresses.push_back(*address); - } - if (XdsDualstackEndpointsEnabled()) { - size_t size; - auto* additional_addresses = - envoy_config_endpoint_v3_Endpoint_additional_addresses(endpoint, - &size); - for (size_t i = 0; i < size; ++i) { - ValidationErrors::ScopedField field( - errors, absl::StrCat(".additional_addresses[", i, "].address")); - auto address = ParseCoreAddress( - envoy_config_endpoint_v3_Endpoint_AdditionalAddress_address( - additional_addresses[i]), - errors); - if (address.has_value()) addresses.push_back(*address); - } - } - } - if (addresses.empty()) return absl::nullopt; - // Convert to EndpointAddresses. - return EndpointAddresses( - addresses, ChannelArgs() - .Set(GRPC_ARG_ADDRESS_WEIGHT, weight) - .Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status())); -} - -struct ParsedLocality { - size_t priority; - XdsEndpointResource::Priority::Locality locality; -}; - -struct ResolvedAddressLessThan { - bool operator()(const grpc_resolved_address& a1, - const grpc_resolved_address& a2) const { - if (a1.len != a2.len) return a1.len < a2.len; - return memcmp(a1.addr, a2.addr, a1.len) < 0; - } -}; -using ResolvedAddressSet = - std::set; - -absl::optional LocalityParse( - const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, - ResolvedAddressSet* address_set, ValidationErrors* errors) { - const size_t original_error_size = errors->size(); - ParsedLocality parsed_locality; - // load_balancing_weight - // If LB weight is not specified or 0, it means this locality is assigned - // no load. - const google_protobuf_UInt32Value* lb_weight = - envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight( - locality_lb_endpoints); - parsed_locality.locality.lb_weight = - lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0; - if (parsed_locality.locality.lb_weight == 0) return absl::nullopt; - // locality - const envoy_config_core_v3_Locality* locality = - envoy_config_endpoint_v3_LocalityLbEndpoints_locality( - locality_lb_endpoints); - if (locality == nullptr) { - ValidationErrors::ScopedField field(errors, ".locality"); - errors->AddError("field not present"); - return absl::nullopt; - } - // region - std::string region = - UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); - // zone - std::string zone = - UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality)); - // sub_zone - std::string sub_zone = - UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality)); - parsed_locality.locality.name = MakeRefCounted( - std::move(region), std::move(zone), std::move(sub_zone)); - // lb_endpoints - size_t size; - const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints = - envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints( - locality_lb_endpoints, &size); - for (size_t i = 0; i < size; ++i) { - ValidationErrors::ScopedField field(errors, - absl::StrCat(".lb_endpoints[", i, "]")); - auto endpoint = EndpointAddressesParse(lb_endpoints[i], errors); - if (endpoint.has_value()) { - for (const auto& address : endpoint->addresses()) { - bool inserted = address_set->insert(address).second; - if (!inserted) { - errors->AddError(absl::StrCat( - "duplicate endpoint address \"", - grpc_sockaddr_to_uri(&address).value_or(""), "\"")); - } - } - parsed_locality.locality.endpoints.push_back(std::move(*endpoint)); - } - } - // priority - parsed_locality.priority = - envoy_config_endpoint_v3_LocalityLbEndpoints_priority( - locality_lb_endpoints); - // Return result. - if (original_error_size != errors->size()) return absl::nullopt; - return parsed_locality; -} - -void DropParseAndAppend( - const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* - drop_overload, - XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) { - // category - std::string category = UpbStringToStdString( - envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category( - drop_overload)); - if (category.empty()) { - ValidationErrors::ScopedField field(errors, ".category"); - errors->AddError("empty drop category name"); - } - // drop_percentage - uint32_t numerator; - { - ValidationErrors::ScopedField field(errors, ".drop_percentage"); - const envoy_type_v3_FractionalPercent* drop_percentage = - envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( - drop_overload); - if (drop_percentage == nullptr) { - errors->AddError("field not present"); - return; - } - numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage); - { - ValidationErrors::ScopedField field(errors, ".denominator"); - const int denominator = - envoy_type_v3_FractionalPercent_denominator(drop_percentage); - // Normalize to million. - switch (denominator) { - case envoy_type_v3_FractionalPercent_HUNDRED: - numerator *= 10000; - break; - case envoy_type_v3_FractionalPercent_TEN_THOUSAND: - numerator *= 100; - break; - case envoy_type_v3_FractionalPercent_MILLION: - break; - default: - errors->AddError("unknown denominator type"); - } - } - // Cap numerator to 1000000. - numerator = std::min(numerator, 1000000u); - } - // Add category. - drop_config->AddCategory(std::move(category), numerator); -} - -absl::StatusOr> EdsResourceParse( - const XdsResourceType::DecodeContext& /*context*/, - const envoy_config_endpoint_v3_ClusterLoadAssignment* - cluster_load_assignment) { - ValidationErrors errors; - auto eds_resource = std::make_shared(); - // endpoints - { - ValidationErrors::ScopedField field(&errors, "endpoints"); - ResolvedAddressSet address_set; - size_t locality_size; - const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = - envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( - cluster_load_assignment, &locality_size); - for (size_t i = 0; i < locality_size; ++i) { - ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); - auto parsed_locality = LocalityParse(endpoints[i], &address_set, &errors); - if (parsed_locality.has_value()) { - CHECK_NE(parsed_locality->locality.lb_weight, 0u); - // Make sure prorities is big enough. Note that they might not - // arrive in priority order. - if (eds_resource->priorities.size() < parsed_locality->priority + 1) { - eds_resource->priorities.resize(parsed_locality->priority + 1); - } - auto& locality_map = - eds_resource->priorities[parsed_locality->priority].localities; - auto it = locality_map.find(parsed_locality->locality.name.get()); - if (it != locality_map.end()) { - errors.AddError(absl::StrCat( - "duplicate locality ", - parsed_locality->locality.name->human_readable_string() - .as_string_view(), - " found in priority ", parsed_locality->priority)); - } else { - locality_map.emplace(parsed_locality->locality.name.get(), - std::move(parsed_locality->locality)); - } - } - } - for (size_t i = 0; i < eds_resource->priorities.size(); ++i) { - const auto& priority = eds_resource->priorities[i]; - if (priority.localities.empty()) { - errors.AddError(absl::StrCat("priority ", i, " empty")); - } else { - // Check that the sum of the locality weights in this priority - // does not exceed the max value for a uint32. - uint64_t total_weight = 0; - for (const auto& p : priority.localities) { - total_weight += p.second.lb_weight; - if (total_weight > std::numeric_limits::max()) { - errors.AddError( - absl::StrCat("sum of locality weights for priority ", i, - " exceeds uint32 max")); - break; - } - } - } - } - } - // policy - const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy( - cluster_load_assignment); - if (policy != nullptr) { - ValidationErrors::ScopedField field(&errors, "policy"); - size_t drop_size; - const auto* const* drop_overload = - envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( - policy, &drop_size); - if (drop_size > 0) { - eds_resource->drop_config = - MakeRefCounted(); - } - for (size_t i = 0; i < drop_size; ++i) { - ValidationErrors::ScopedField field( - &errors, absl::StrCat(".drop_overloads[", i, "]")); - DropParseAndAppend(drop_overload[i], eds_resource->drop_config.get(), - &errors); - } - } - // Return result. - if (!errors.ok()) { - return errors.status(absl::StatusCode::kInvalidArgument, - "errors parsing EDS resource"); - } - return eds_resource; -} - -} // namespace - -XdsResourceType::DecodeResult XdsEndpointResourceType::Decode( - const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const { - DecodeResult result; - // Parse serialized proto. - auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( - serialized_resource.data(), serialized_resource.size(), context.arena); - if (resource == nullptr) { - result.resource = absl::InvalidArgumentError( - "Can't parse ClusterLoadAssignment resource."); - return result; - } - MaybeLogClusterLoadAssignment(context, resource); - // Validate resource. - result.name = UpbStringToStdString( - envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource)); - auto eds_resource = EdsResourceParse(context, resource); - if (!eds_resource.ok()) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(ERROR) << "[xds_client " << context.client - << "] invalid ClusterLoadAssignment " << *result.name << ": " - << eds_resource.status(); - } - result.resource = eds_resource.status(); - } else { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(INFO) << "[xds_client " << context.client - << "] parsed ClusterLoadAssignment " << *result.name << ": " - << (*eds_resource)->ToString(); - } - result.resource = std::move(*eds_resource); - } - return result; -} - } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_endpoint.h b/src/core/xds/grpc/xds_endpoint.h index 43de2349901..e0bbc7c3934 100644 --- a/src/core/xds/grpc/xds_endpoint.h +++ b/src/core/xds/grpc/xds_endpoint.h @@ -17,9 +17,6 @@ #ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H #define GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H -#include - -#include #include #include #include @@ -27,17 +24,11 @@ #include "absl/base/thread_annotations.h" #include "absl/random/random.h" -#include "absl/strings/string_view.h" -#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" -#include "upb/reflection/def.h" - -#include #include "src/core/lib/gprpp/ref_counted.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/gprpp/sync.h" #include "src/core/resolver/endpoint_addresses.h" -#include "src/core/xds/xds_client/xds_client.h" #include "src/core/xds/xds_client/xds_client_stats.h" #include "src/core/xds/xds_client/xds_resource_type.h" #include "src/core/xds/xds_client/xds_resource_type_impl.h" @@ -130,21 +121,6 @@ struct XdsEndpointResource : public XdsResourceType::ResourceData { std::string ToString() const; }; -class XdsEndpointResourceType final - : public XdsResourceTypeImpl { - public: - absl::string_view type_url() const override { - return "envoy.config.endpoint.v3.ClusterLoadAssignment"; - } - - DecodeResult Decode(const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const override; - - void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { - envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab); - } -}; - } // namespace grpc_core #endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_H diff --git a/src/core/xds/grpc/xds_endpoint_parser.cc b/src/core/xds/grpc/xds_endpoint_parser.cc new file mode 100644 index 00000000000..28562f00f64 --- /dev/null +++ b/src/core/xds/grpc/xds_endpoint_parser.cc @@ -0,0 +1,433 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_endpoint_parser.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "absl/types/optional.h" +#include "envoy/config/core/v3/address.upb.h" +#include "envoy/config/core/v3/base.upb.h" +#include "envoy/config/endpoint/v3/endpoint.upb.h" +#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" +#include "envoy/config/endpoint/v3/endpoint_components.upb.h" +#include "envoy/type/v3/percent.upb.h" +#include "google/protobuf/wrappers.upb.h" +#include "upb/text/encode.h" + +#include + +#include "src/core/lib/address_utils/parse_address.h" +#include "src/core/lib/address_utils/sockaddr_utils.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/env.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/iomgr/resolved_address.h" +#include "src/core/util/string.h" +#include "src/core/util/upb_utils.h" +#include "src/core/xds/grpc/xds_health_status.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +// IWYU pragma: no_include "absl/meta/type_traits.h" + +namespace grpc_core { + +namespace { + +// TODO(roth): Remove this once dualstack support is stable. +bool XdsDualstackEndpointsEnabled() { + auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS"); + if (!value.has_value()) return false; + bool parsed_value; + bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); + return parse_succeeded && parsed_value; +} + +void MaybeLogClusterLoadAssignment( + const XdsResourceType::DecodeContext& context, + const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { + const upb_MessageDef* msg_type = + envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef( + context.symtab); + char buf[10240]; + upb_TextEncode(reinterpret_cast(cla), msg_type, nullptr, + 0, buf, sizeof(buf)); + VLOG(2) << "[xds_client " << context.client + << "] ClusterLoadAssignment: " << buf; + } +} + +absl::optional ParseCoreAddress( + const envoy_config_core_v3_Address* address, ValidationErrors* errors) { + if (address == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + ValidationErrors::ScopedField field(errors, ".socket_address"); + const envoy_config_core_v3_SocketAddress* socket_address = + envoy_config_core_v3_Address_socket_address(address); + if (socket_address == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + std::string address_str = UpbStringToStdString( + envoy_config_core_v3_SocketAddress_address(socket_address)); + uint32_t port; + { + ValidationErrors::ScopedField field(errors, ".port_value"); + port = envoy_config_core_v3_SocketAddress_port_value(socket_address); + if (GPR_UNLIKELY(port >> 16) != 0) { + errors->AddError("invalid port"); + return absl::nullopt; + } + } + auto addr = StringToSockaddr(address_str, port); + if (!addr.ok()) { + errors->AddError(addr.status().message()); + return absl::nullopt; + } + return *addr; +} + +absl::optional EndpointAddressesParse( + const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, + ValidationErrors* errors) { + // health_status + const int32_t health_status = + envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint); + auto status = XdsHealthStatus::FromUpb(health_status); + if (!status.has_value()) return absl::nullopt; + // load_balancing_weight + uint32_t weight = 1; + { + ValidationErrors::ScopedField field(errors, ".load_balancing_weight"); + const google_protobuf_UInt32Value* load_balancing_weight = + envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); + if (load_balancing_weight != nullptr) { + weight = google_protobuf_UInt32Value_value(load_balancing_weight); + if (weight == 0) { + errors->AddError("must be greater than 0"); + } + } + } + // endpoint + std::vector addresses; + { + ValidationErrors::ScopedField field(errors, ".endpoint"); + const envoy_config_endpoint_v3_Endpoint* endpoint = + envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); + if (endpoint == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + { + ValidationErrors::ScopedField field(errors, ".address"); + auto address = ParseCoreAddress( + envoy_config_endpoint_v3_Endpoint_address(endpoint), errors); + if (address.has_value()) addresses.push_back(*address); + } + if (XdsDualstackEndpointsEnabled()) { + size_t size; + auto* additional_addresses = + envoy_config_endpoint_v3_Endpoint_additional_addresses(endpoint, + &size); + for (size_t i = 0; i < size; ++i) { + ValidationErrors::ScopedField field( + errors, absl::StrCat(".additional_addresses[", i, "].address")); + auto address = ParseCoreAddress( + envoy_config_endpoint_v3_Endpoint_AdditionalAddress_address( + additional_addresses[i]), + errors); + if (address.has_value()) addresses.push_back(*address); + } + } + } + if (addresses.empty()) return absl::nullopt; + // Convert to EndpointAddresses. + return EndpointAddresses( + addresses, ChannelArgs() + .Set(GRPC_ARG_ADDRESS_WEIGHT, weight) + .Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status())); +} + +struct ParsedLocality { + size_t priority; + XdsEndpointResource::Priority::Locality locality; +}; + +struct ResolvedAddressLessThan { + bool operator()(const grpc_resolved_address& a1, + const grpc_resolved_address& a2) const { + if (a1.len != a2.len) return a1.len < a2.len; + return memcmp(a1.addr, a2.addr, a1.len) < 0; + } +}; +using ResolvedAddressSet = + std::set; + +absl::optional LocalityParse( + const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, + ResolvedAddressSet* address_set, ValidationErrors* errors) { + const size_t original_error_size = errors->size(); + ParsedLocality parsed_locality; + // load_balancing_weight + // If LB weight is not specified or 0, it means this locality is assigned + // no load. + const google_protobuf_UInt32Value* lb_weight = + envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight( + locality_lb_endpoints); + parsed_locality.locality.lb_weight = + lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0; + if (parsed_locality.locality.lb_weight == 0) return absl::nullopt; + // locality + const envoy_config_core_v3_Locality* locality = + envoy_config_endpoint_v3_LocalityLbEndpoints_locality( + locality_lb_endpoints); + if (locality == nullptr) { + ValidationErrors::ScopedField field(errors, ".locality"); + errors->AddError("field not present"); + return absl::nullopt; + } + // region + std::string region = + UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); + // zone + std::string zone = + UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality)); + // sub_zone + std::string sub_zone = + UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality)); + parsed_locality.locality.name = MakeRefCounted( + std::move(region), std::move(zone), std::move(sub_zone)); + // lb_endpoints + size_t size; + const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints = + envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints( + locality_lb_endpoints, &size); + for (size_t i = 0; i < size; ++i) { + ValidationErrors::ScopedField field(errors, + absl::StrCat(".lb_endpoints[", i, "]")); + auto endpoint = EndpointAddressesParse(lb_endpoints[i], errors); + if (endpoint.has_value()) { + for (const auto& address : endpoint->addresses()) { + bool inserted = address_set->insert(address).second; + if (!inserted) { + errors->AddError(absl::StrCat( + "duplicate endpoint address \"", + grpc_sockaddr_to_uri(&address).value_or(""), "\"")); + } + } + parsed_locality.locality.endpoints.push_back(std::move(*endpoint)); + } + } + // priority + parsed_locality.priority = + envoy_config_endpoint_v3_LocalityLbEndpoints_priority( + locality_lb_endpoints); + // Return result. + if (original_error_size != errors->size()) return absl::nullopt; + return parsed_locality; +} + +void DropParseAndAppend( + const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* + drop_overload, + XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) { + // category + std::string category = UpbStringToStdString( + envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category( + drop_overload)); + if (category.empty()) { + ValidationErrors::ScopedField field(errors, ".category"); + errors->AddError("empty drop category name"); + } + // drop_percentage + uint32_t numerator; + { + ValidationErrors::ScopedField field(errors, ".drop_percentage"); + const envoy_type_v3_FractionalPercent* drop_percentage = + envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( + drop_overload); + if (drop_percentage == nullptr) { + errors->AddError("field not present"); + return; + } + numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage); + { + ValidationErrors::ScopedField field(errors, ".denominator"); + const int denominator = + envoy_type_v3_FractionalPercent_denominator(drop_percentage); + // Normalize to million. + switch (denominator) { + case envoy_type_v3_FractionalPercent_HUNDRED: + numerator *= 10000; + break; + case envoy_type_v3_FractionalPercent_TEN_THOUSAND: + numerator *= 100; + break; + case envoy_type_v3_FractionalPercent_MILLION: + break; + default: + errors->AddError("unknown denominator type"); + } + } + // Cap numerator to 1000000. + numerator = std::min(numerator, 1000000u); + } + // Add category. + drop_config->AddCategory(std::move(category), numerator); +} + +absl::StatusOr> EdsResourceParse( + const XdsResourceType::DecodeContext& /*context*/, + const envoy_config_endpoint_v3_ClusterLoadAssignment* + cluster_load_assignment) { + ValidationErrors errors; + auto eds_resource = std::make_shared(); + // endpoints + { + ValidationErrors::ScopedField field(&errors, "endpoints"); + ResolvedAddressSet address_set; + size_t locality_size; + const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = + envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( + cluster_load_assignment, &locality_size); + for (size_t i = 0; i < locality_size; ++i) { + ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); + auto parsed_locality = LocalityParse(endpoints[i], &address_set, &errors); + if (parsed_locality.has_value()) { + CHECK_NE(parsed_locality->locality.lb_weight, 0u); + // Make sure prorities is big enough. Note that they might not + // arrive in priority order. + if (eds_resource->priorities.size() < parsed_locality->priority + 1) { + eds_resource->priorities.resize(parsed_locality->priority + 1); + } + auto& locality_map = + eds_resource->priorities[parsed_locality->priority].localities; + auto it = locality_map.find(parsed_locality->locality.name.get()); + if (it != locality_map.end()) { + errors.AddError(absl::StrCat( + "duplicate locality ", + parsed_locality->locality.name->human_readable_string() + .as_string_view(), + " found in priority ", parsed_locality->priority)); + } else { + locality_map.emplace(parsed_locality->locality.name.get(), + std::move(parsed_locality->locality)); + } + } + } + for (size_t i = 0; i < eds_resource->priorities.size(); ++i) { + const auto& priority = eds_resource->priorities[i]; + if (priority.localities.empty()) { + errors.AddError(absl::StrCat("priority ", i, " empty")); + } else { + // Check that the sum of the locality weights in this priority + // does not exceed the max value for a uint32. + uint64_t total_weight = 0; + for (const auto& p : priority.localities) { + total_weight += p.second.lb_weight; + if (total_weight > std::numeric_limits::max()) { + errors.AddError( + absl::StrCat("sum of locality weights for priority ", i, + " exceeds uint32 max")); + break; + } + } + } + } + } + // policy + const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy( + cluster_load_assignment); + if (policy != nullptr) { + ValidationErrors::ScopedField field(&errors, "policy"); + size_t drop_size; + const auto* const* drop_overload = + envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( + policy, &drop_size); + if (drop_size > 0) { + eds_resource->drop_config = + MakeRefCounted(); + } + for (size_t i = 0; i < drop_size; ++i) { + ValidationErrors::ScopedField field( + &errors, absl::StrCat(".drop_overloads[", i, "]")); + DropParseAndAppend(drop_overload[i], eds_resource->drop_config.get(), + &errors); + } + } + // Return result. + if (!errors.ok()) { + return errors.status(absl::StatusCode::kInvalidArgument, + "errors parsing EDS resource"); + } + return eds_resource; +} + +} // namespace + +XdsResourceType::DecodeResult XdsEndpointResourceType::Decode( + const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const { + DecodeResult result; + // Parse serialized proto. + auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( + serialized_resource.data(), serialized_resource.size(), context.arena); + if (resource == nullptr) { + result.resource = absl::InvalidArgumentError( + "Can't parse ClusterLoadAssignment resource."); + return result; + } + MaybeLogClusterLoadAssignment(context, resource); + // Validate resource. + result.name = UpbStringToStdString( + envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource)); + auto eds_resource = EdsResourceParse(context, resource); + if (!eds_resource.ok()) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(ERROR) << "[xds_client " << context.client + << "] invalid ClusterLoadAssignment " << *result.name << ": " + << eds_resource.status(); + } + result.resource = eds_resource.status(); + } else { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(INFO) << "[xds_client " << context.client + << "] parsed ClusterLoadAssignment " << *result.name << ": " + << (*eds_resource)->ToString(); + } + result.resource = std::move(*eds_resource); + } + return result; +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_endpoint_parser.h b/src/core/xds/grpc/xds_endpoint_parser.h new file mode 100644 index 00000000000..ababdd775bf --- /dev/null +++ b/src/core/xds/grpc/xds_endpoint_parser.h @@ -0,0 +1,48 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H + +#include "absl/strings/string_view.h" +#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" +#include "upb/reflection/def.h" + +#include "src/core/xds/grpc/xds_endpoint.h" +#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/xds_client/xds_resource_type.h" +#include "src/core/xds/xds_client/xds_resource_type_impl.h" + +namespace grpc_core { + +class XdsEndpointResourceType final + : public XdsResourceTypeImpl { + public: + absl::string_view type_url() const override { + return "envoy.config.endpoint.v3.ClusterLoadAssignment"; + } + + DecodeResult Decode(const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const override; + + void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { + envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab); + } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H diff --git a/src/core/xds/grpc/xds_health_status.cc b/src/core/xds/grpc/xds_health_status.cc index 6f137656e3c..8ac057c7c5a 100644 --- a/src/core/xds/grpc/xds_health_status.cc +++ b/src/core/xds/grpc/xds_health_status.cc @@ -22,8 +22,6 @@ #include "absl/strings/str_join.h" #include "envoy/config/core/v3/health_check.upb.h" -#include - namespace grpc_core { absl::optional XdsHealthStatus::FromUpb(uint32_t status) { diff --git a/src/core/xds/grpc/xds_health_status.h b/src/core/xds/grpc/xds_health_status.h index a0be5ecb443..6f4e346a268 100644 --- a/src/core/xds/grpc/xds_health_status.h +++ b/src/core/xds/grpc/xds_health_status.h @@ -23,8 +23,6 @@ #include "absl/types/optional.h" #include "absl/types/span.h" -#include - #include "src/core/resolver/endpoint_addresses.h" // Channel arg key for xDS health status. diff --git a/src/core/xds/grpc/xds_http_fault_filter.cc b/src/core/xds/grpc/xds_http_fault_filter.cc index 0c65e13deb2..846a4b490d4 100644 --- a/src/core/xds/grpc/xds_http_fault_filter.cc +++ b/src/core/xds/grpc/xds_http_fault_filter.cc @@ -45,7 +45,8 @@ #include "src/core/util/json/json.h" #include "src/core/util/json/json_writer.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_http_filter.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_http_fault_filter.h b/src/core/xds/grpc/xds_http_fault_filter.h index ff2394c7a28..b99b1d30085 100644 --- a/src/core/xds/grpc/xds_http_fault_filter.h +++ b/src/core/xds/grpc/xds_http_fault_filter.h @@ -28,7 +28,7 @@ #include "src/core/lib/channel/channel_fwd.h" #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_http_filters.h b/src/core/xds/grpc/xds_http_filter.h similarity index 64% rename from src/core/xds/grpc/xds_http_filters.h rename to src/core/xds/grpc/xds_http_filter.h index 6e5cda52b57..6e8c79d1284 100644 --- a/src/core/xds/grpc/xds_http_filters.h +++ b/src/core/xds/grpc/xds_http_filter.h @@ -14,24 +14,17 @@ // limitations under the License. // -#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H -#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H -#include -#include #include -#include -#include -#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "upb/reflection/def.h" -#include - #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/channel/channel_fwd.h" #include "src/core/lib/gprpp/validation_errors.h" @@ -127,60 +120,6 @@ class XdsHttpFilterImpl { virtual bool IsTerminalFilter() const { return false; } }; -class XdsHttpRouterFilter final : public XdsHttpFilterImpl { - public: - absl::string_view ConfigProtoName() const override; - absl::string_view OverrideConfigProtoName() const override; - void PopulateSymtab(upb_DefPool* symtab) const override; - absl::optional GenerateFilterConfig( - const XdsResourceType::DecodeContext& context, XdsExtension extension, - ValidationErrors* errors) const override; - absl::optional GenerateFilterConfigOverride( - const XdsResourceType::DecodeContext& context, XdsExtension extension, - ValidationErrors* errors) const override; - void AddFilter(InterceptionChainBuilder& /*builder*/) const override {} - const grpc_channel_filter* channel_filter() const override { return nullptr; } - absl::StatusOr GenerateServiceConfig( - const FilterConfig& /*hcm_filter_config*/, - const FilterConfig* /*filter_config_override*/) const override { - // This will never be called, since channel_filter() returns null. - return absl::UnimplementedError("router filter should never be called"); - } - bool IsSupportedOnClients() const override { return true; } - bool IsSupportedOnServers() const override { return true; } - bool IsTerminalFilter() const override { return true; } -}; - -class XdsHttpFilterRegistry final { - public: - explicit XdsHttpFilterRegistry(bool register_builtins = true); - - // Not copyable. - XdsHttpFilterRegistry(const XdsHttpFilterRegistry&) = delete; - XdsHttpFilterRegistry& operator=(const XdsHttpFilterRegistry&) = delete; - - // Movable. - XdsHttpFilterRegistry(XdsHttpFilterRegistry&& other) noexcept - : owning_list_(std::move(other.owning_list_)), - registry_map_(std::move(other.registry_map_)) {} - XdsHttpFilterRegistry& operator=(XdsHttpFilterRegistry&& other) noexcept { - owning_list_ = std::move(other.owning_list_); - registry_map_ = std::move(other.registry_map_); - return *this; - } - - void RegisterFilter(std::unique_ptr filter); - - const XdsHttpFilterImpl* GetFilterForType( - absl::string_view proto_type_name) const; - - void PopulateSymtab(upb_DefPool* symtab) const; - - private: - std::vector> owning_list_; - std::map registry_map_; -}; - } // namespace grpc_core -#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTERS_H +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_H diff --git a/src/core/xds/grpc/xds_http_filters.cc b/src/core/xds/grpc/xds_http_filter_registry.cc similarity index 97% rename from src/core/xds/grpc/xds_http_filters.cc rename to src/core/xds/grpc/xds_http_filter_registry.cc index 0b0f9e7c0af..c0ed05e7db0 100644 --- a/src/core/xds/grpc/xds_http_filters.cc +++ b/src/core/xds/grpc/xds_http_filter_registry.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" #include #include @@ -28,6 +28,7 @@ #include #include +#include "src/core/util/json/json.h" #include "src/core/xds/grpc/xds_http_fault_filter.h" #include "src/core/xds/grpc/xds_http_rbac_filter.h" #include "src/core/xds/grpc/xds_http_stateful_session_filter.h" diff --git a/src/core/xds/grpc/xds_http_filter_registry.h b/src/core/xds/grpc/xds_http_filter_registry.h new file mode 100644 index 00000000000..d25dc378b8b --- /dev/null +++ b/src/core/xds/grpc/xds_http_filter_registry.h @@ -0,0 +1,98 @@ +// +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H + +#include +#include +#include +#include +#include + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "upb/reflection/def.h" + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_fwd.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/transport/interception_chain.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +namespace grpc_core { + +// Exposed for testing purposes only. +class XdsHttpRouterFilter final : public XdsHttpFilterImpl { + public: + absl::string_view ConfigProtoName() const override; + absl::string_view OverrideConfigProtoName() const override; + void PopulateSymtab(upb_DefPool* symtab) const override; + absl::optional GenerateFilterConfig( + const XdsResourceType::DecodeContext& context, XdsExtension extension, + ValidationErrors* errors) const override; + absl::optional GenerateFilterConfigOverride( + const XdsResourceType::DecodeContext& context, XdsExtension extension, + ValidationErrors* errors) const override; + void AddFilter(InterceptionChainBuilder& /*builder*/) const override {} + const grpc_channel_filter* channel_filter() const override { return nullptr; } + absl::StatusOr GenerateServiceConfig( + const FilterConfig& /*hcm_filter_config*/, + const FilterConfig* /*filter_config_override*/) const override { + // This will never be called, since channel_filter() returns null. + return absl::UnimplementedError("router filter should never be called"); + } + bool IsSupportedOnClients() const override { return true; } + bool IsSupportedOnServers() const override { return true; } + bool IsTerminalFilter() const override { return true; } +}; + +class XdsHttpFilterRegistry final { + public: + explicit XdsHttpFilterRegistry(bool register_builtins = true); + + // Not copyable. + XdsHttpFilterRegistry(const XdsHttpFilterRegistry&) = delete; + XdsHttpFilterRegistry& operator=(const XdsHttpFilterRegistry&) = delete; + + // Movable. + XdsHttpFilterRegistry(XdsHttpFilterRegistry&& other) noexcept + : owning_list_(std::move(other.owning_list_)), + registry_map_(std::move(other.registry_map_)) {} + XdsHttpFilterRegistry& operator=(XdsHttpFilterRegistry&& other) noexcept { + owning_list_ = std::move(other.owning_list_); + registry_map_ = std::move(other.registry_map_); + return *this; + } + + void RegisterFilter(std::unique_ptr filter); + + const XdsHttpFilterImpl* GetFilterForType( + absl::string_view proto_type_name) const; + + void PopulateSymtab(upb_DefPool* symtab) const; + + private: + std::vector> owning_list_; + std::map registry_map_; +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H diff --git a/src/core/xds/grpc/xds_http_rbac_filter.h b/src/core/xds/grpc/xds_http_rbac_filter.h index ad357a978e2..8aac5bf1d63 100644 --- a/src/core/xds/grpc/xds_http_rbac_filter.h +++ b/src/core/xds/grpc/xds_http_rbac_filter.h @@ -28,7 +28,7 @@ #include "src/core/lib/channel/channel_fwd.h" #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_http_stateful_session_filter.cc b/src/core/xds/grpc/xds_http_stateful_session_filter.cc index 0c4203dd140..dd239bc0094 100644 --- a/src/core/xds/grpc/xds_http_stateful_session_filter.cc +++ b/src/core/xds/grpc/xds_http_stateful_session_filter.cc @@ -41,7 +41,8 @@ #include "src/core/util/json/json_writer.h" #include "src/core/util/upb_utils.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_http_filter.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_http_stateful_session_filter.h b/src/core/xds/grpc/xds_http_stateful_session_filter.h index 4df7a04a5d8..fb3ce72b59f 100644 --- a/src/core/xds/grpc/xds_http_stateful_session_filter.h +++ b/src/core/xds/grpc/xds_http_stateful_session_filter.h @@ -28,7 +28,7 @@ #include "src/core/lib/channel/channel_fwd.h" #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_lb_policy_registry.cc b/src/core/xds/grpc/xds_lb_policy_registry.cc index fa55b1997c5..46e0b0f666f 100644 --- a/src/core/xds/grpc/xds_lb_policy_registry.cc +++ b/src/core/xds/grpc/xds_lb_policy_registry.cc @@ -40,6 +40,7 @@ #include "src/core/lib/gprpp/validation_errors.h" #include "src/core/load_balancing/lb_policy_registry.h" #include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_listener.cc b/src/core/xds/grpc/xds_listener.cc index 02379bfe5a7..46b971d7692 100644 --- a/src/core/xds/grpc/xds_listener.cc +++ b/src/core/xds/grpc/xds_listener.cc @@ -16,48 +16,12 @@ #include "src/core/xds/grpc/xds_listener.h" -#include - -#include -#include - -#include "absl/log/check.h" -#include "absl/log/log.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "envoy/config/core/v3/address.upb.h" -#include "envoy/config/core/v3/base.upb.h" -#include "envoy/config/core/v3/config_source.upb.h" -#include "envoy/config/core/v3/protocol.upb.h" -#include "envoy/config/listener/v3/api_listener.upb.h" -#include "envoy/config/listener/v3/listener.upb.h" -#include "envoy/config/listener/v3/listener.upbdefs.h" -#include "envoy/config/listener/v3/listener_components.upb.h" -#include "envoy/config/route/v3/route.upb.h" -#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h" -#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" -#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" -#include "google/protobuf/any.upb.h" -#include "google/protobuf/duration.upb.h" -#include "google/protobuf/wrappers.upb.h" -#include "upb/text/encode.h" - -#include -#include "src/core/lib/address_utils/parse_address.h" #include "src/core/lib/address_utils/sockaddr_utils.h" -#include "src/core/lib/debug/trace.h" -#include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/gprpp/match.h" -#include "src/core/lib/gprpp/validation_errors.h" -#include "src/core/lib/iomgr/sockaddr.h" -#include "src/core/lib/matchers/matchers.h" -#include "src/core/util/upb_utils.h" -#include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { @@ -134,79 +98,6 @@ std::string XdsListenerResource::FilterChainMap::CidrRange::ToString() const { ", prefix_len=", prefix_len, "}"); } -// -// FilterChain -// - -struct FilterChain { - struct FilterChainMatch { - uint32_t destination_port = 0; - std::vector prefix_ranges; - XdsListenerResource::FilterChainMap::ConnectionSourceType source_type = - XdsListenerResource::FilterChainMap::ConnectionSourceType::kAny; - std::vector - source_prefix_ranges; - std::vector source_ports; - std::vector server_names; - std::string transport_protocol; - std::vector application_protocols; - - std::string ToString() const; - } filter_chain_match; - - std::shared_ptr filter_chain_data; -}; - -std::string FilterChain::FilterChainMatch::ToString() const { - std::vector contents; - if (destination_port != 0) { - contents.push_back(absl::StrCat("destination_port=", destination_port)); - } - if (!prefix_ranges.empty()) { - std::vector prefix_ranges_content; - prefix_ranges_content.reserve(prefix_ranges.size()); - for (const auto& range : prefix_ranges) { - prefix_ranges_content.push_back(range.ToString()); - } - contents.push_back(absl::StrCat( - "prefix_ranges={", absl::StrJoin(prefix_ranges_content, ", "), "}")); - } - if (source_type == XdsListenerResource::FilterChainMap::ConnectionSourceType:: - kSameIpOrLoopback) { - contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); - } else if (source_type == XdsListenerResource::FilterChainMap:: - ConnectionSourceType::kExternal) { - contents.push_back("source_type=EXTERNAL"); - } - if (!source_prefix_ranges.empty()) { - std::vector source_prefix_ranges_content; - source_prefix_ranges_content.reserve(source_prefix_ranges.size()); - for (const auto& range : source_prefix_ranges) { - source_prefix_ranges_content.push_back(range.ToString()); - } - contents.push_back( - absl::StrCat("source_prefix_ranges={", - absl::StrJoin(source_prefix_ranges_content, ", "), "}")); - } - if (!source_ports.empty()) { - contents.push_back( - absl::StrCat("source_ports={", absl::StrJoin(source_ports, ", "), "}")); - } - if (!server_names.empty()) { - contents.push_back( - absl::StrCat("server_names={", absl::StrJoin(server_names, ", "), "}")); - } - if (!transport_protocol.empty()) { - contents.push_back(absl::StrCat("transport_protocol=", transport_protocol)); - } - if (!application_protocols.empty()) { - contents.push_back(absl::StrCat("application_protocols={", - absl::StrJoin(application_protocols, ", "), - "}")); - } - return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); -} - // // XdsListenerResource::FilterChainMap // @@ -218,24 +109,31 @@ std::string XdsListenerResource::FilterChainMap::ToString() const { 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; + std::vector match_contents; if (destination_ip.prefix_range.has_value()) { - filter_chain_match.prefix_ranges.push_back( - *destination_ip.prefix_range); + match_contents.push_back( + absl::StrCat("prefix_ranges={", + destination_ip.prefix_range->ToString(), "}")); + } + if (static_cast(source_type) == + ConnectionSourceType::kSameIpOrLoopback) { + match_contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); + } else if (static_cast(source_type) == + ConnectionSourceType::kExternal) { + match_contents.push_back("source_type=EXTERNAL"); } - filter_chain_match.source_type = static_cast< - XdsListenerResource::FilterChainMap::ConnectionSourceType>( - source_type); if (source_ip.prefix_range.has_value()) { - filter_chain_match.source_prefix_ranges.push_back( - *source_ip.prefix_range); + match_contents.push_back( + absl::StrCat("source_prefix_ranges={", + source_ip.prefix_range->ToString(), "}")); } if (source_port_pair.first != 0) { - filter_chain_match.source_ports.push_back(source_port_pair.first); + match_contents.push_back( + absl::StrCat("source_ports={", source_port_pair.first, "}")); } contents.push_back(absl::StrCat( - "{filter_chain_match=", filter_chain_match.ToString(), - ", filter_chain=", source_port_pair.second.data->ToString(), + "{filter_chain_match={", absl::StrJoin(match_contents, ", "), + "}, filter_chain=", source_port_pair.second.data->ToString(), "}")); } } @@ -275,864 +173,4 @@ std::string XdsListenerResource::ToString() const { }); } -// -// XdsListenerResourceType -// - -namespace { - -void MaybeLogHttpConnectionManager( - const XdsResourceType::DecodeContext& context, - const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* - http_connection_manager_config) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { - const upb_MessageDef* msg_type = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( - context.symtab); - char buf[10240]; - upb_TextEncode( - reinterpret_cast(http_connection_manager_config), - msg_type, nullptr, 0, buf, sizeof(buf)); - VLOG(2) << "[xds_client " << context.client - << "] HttpConnectionManager: " << buf; - } -} - -XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse( - bool is_client, const XdsResourceType::DecodeContext& context, - XdsExtension extension, ValidationErrors* errors) { - if (extension.type != - "envoy.extensions.filters.network.http_connection_manager.v3" - ".HttpConnectionManager") { - errors->AddError("unsupported filter type"); - return {}; - } - auto* serialized_hcm_config = - absl::get_if(&extension.value); - if (serialized_hcm_config == nullptr) { - errors->AddError("could not parse HttpConnectionManager config"); - return {}; - } - const auto* http_connection_manager_proto = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( - serialized_hcm_config->data(), serialized_hcm_config->size(), - context.arena); - if (http_connection_manager_proto == nullptr) { - errors->AddError("could not parse HttpConnectionManager config"); - return {}; - } - MaybeLogHttpConnectionManager(context, http_connection_manager_proto); - XdsListenerResource::HttpConnectionManager http_connection_manager; - // xff_num_trusted_hops -- must be zero as per - // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md - if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops( - http_connection_manager_proto) != 0) { - ValidationErrors::ScopedField field(errors, ".xff_num_trusted_hops"); - errors->AddError("must be zero"); - } - // original_ip_detection_extensions -- must be empty as per - // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md - { - size_t size; - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_original_ip_detection_extensions( - http_connection_manager_proto, &size); - if (size != 0) { - ValidationErrors::ScopedField field(errors, - ".original_ip_detection_extensions"); - errors->AddError("must be empty"); - } - } - // common_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) { - // max_stream_duration - const google_protobuf_Duration* duration = - envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options); - if (duration != nullptr) { - ValidationErrors::ScopedField field( - errors, ".common_http_protocol_options.max_stream_duration"); - http_connection_manager.http_max_stream_duration = - ParseDuration(duration, errors); - } - } - // http_filters - { - ValidationErrors::ScopedField field(errors, ".http_filters"); - const auto& http_filter_registry = - static_cast(context.client->bootstrap()) - .http_filter_registry(); - size_t num_filters = 0; - const auto* http_filters = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( - http_connection_manager_proto, &num_filters); - std::set names_seen; - const size_t original_error_size = errors->size(); - for (size_t i = 0; i < num_filters; ++i) { - ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); - const auto* http_filter = http_filters[i]; - // name - absl::string_view name = UpbStringToAbsl( - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( - http_filter)); - { - ValidationErrors::ScopedField field(errors, ".name"); - if (name.empty()) { - errors->AddError("empty filter name"); - continue; - } - if (names_seen.find(name) != names_seen.end()) { - errors->AddError(absl::StrCat("duplicate HTTP filter name: ", name)); - continue; - } - } - names_seen.insert(name); - // is_optional - const bool is_optional = - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( - http_filter); - // typed_config - { - ValidationErrors::ScopedField field(errors, ".typed_config"); - const google_protobuf_Any* typed_config = - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( - http_filter); - auto extension = ExtractXdsExtension(context, typed_config, errors); - if (!extension.has_value()) continue; - const XdsHttpFilterImpl* filter_impl = - http_filter_registry.GetFilterForType(extension->type); - if (filter_impl == nullptr) { - if (!is_optional) errors->AddError("unsupported filter type"); - continue; - } - if ((is_client && !filter_impl->IsSupportedOnClients()) || - (!is_client && !filter_impl->IsSupportedOnServers())) { - if (!is_optional) { - errors->AddError(absl::StrCat("filter is not supported on ", - is_client ? "clients" : "servers")); - } - continue; - } - absl::optional filter_config = - filter_impl->GenerateFilterConfig(context, std::move(*extension), - errors); - if (filter_config.has_value()) { - http_connection_manager.http_filters.emplace_back( - XdsListenerResource::HttpConnectionManager::HttpFilter{ - std::string(name), std::move(*filter_config)}); - } - } - } - if (errors->size() == original_error_size && - http_connection_manager.http_filters.empty()) { - errors->AddError("expected at least one HTTP filter"); - } - // Make sure that the last filter is terminal and non-last filters are - // non-terminal. Note that this check is being performed in a separate loop - // to take care of the case where there are two terminal filters in the list - // out of which only one gets added in the final list. - for (const auto& http_filter : http_connection_manager.http_filters) { - const XdsHttpFilterImpl* filter_impl = - http_filter_registry.GetFilterForType( - http_filter.config.config_proto_type_name); - if (&http_filter != &http_connection_manager.http_filters.back()) { - // Filters before the last filter must not be terminal. - if (filter_impl->IsTerminalFilter()) { - errors->AddError( - absl::StrCat("terminal filter for config type ", - http_filter.config.config_proto_type_name, - " must be the last filter in the chain")); - } - } else { - // The last filter must be terminal. - if (!filter_impl->IsTerminalFilter()) { - errors->AddError( - absl::StrCat("non-terminal filter for config type ", - http_filter.config.config_proto_type_name, - " is the last filter in the chain")); - } - } - } - } - // 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); - ValidationErrors::ScopedField field(errors, ".route_config"); - http_connection_manager.route_config = - XdsRouteConfigResource::Parse(context, route_config, errors); - } else { - // 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) { - errors->AddError("neither route_config nor rds fields are present"); - } else { - // Get the route_config_name. - http_connection_manager.route_config = UpbStringToStdString( - envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( - 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); - ValidationErrors::ScopedField field(errors, ".rds.config_source"); - if (config_source == nullptr) { - errors->AddError("field not present"); - } else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && - !envoy_config_core_v3_ConfigSource_has_self(config_source)) { - errors->AddError("ConfigSource does not specify ADS or SELF"); - } - } - } - return http_connection_manager; -} - -absl::StatusOr> -LdsResourceParseClient( - const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_ApiListener* api_listener) { - auto lds_update = std::make_shared(); - ValidationErrors errors; - ValidationErrors::ScopedField field(&errors, "api_listener.api_listener"); - auto* api_listener_field = - envoy_config_listener_v3_ApiListener_api_listener(api_listener); - auto extension = ExtractXdsExtension(context, api_listener_field, &errors); - if (extension.has_value()) { - lds_update->listener = HttpConnectionManagerParse( - /*is_client=*/true, context, std::move(*extension), &errors); - } - if (!errors.ok()) { - return errors.status(absl::StatusCode::kInvalidArgument, - "errors validating ApiListener"); - } - return std::move(lds_update); -} - -XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_core_v3_TransportSocket* transport_socket, - ValidationErrors* errors) { - ValidationErrors::ScopedField field(errors, ".typed_config"); - const auto* typed_config = - envoy_config_core_v3_TransportSocket_typed_config(transport_socket); - auto extension = ExtractXdsExtension(context, typed_config, errors); - if (!extension.has_value()) return {}; - if (extension->type != - "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext") { - ValidationErrors::ScopedField field(errors, ".type_url"); - errors->AddError("unsupported transport socket type"); - return {}; - } - absl::string_view* serialized_downstream_tls_context = - absl::get_if(&extension->value); - if (serialized_downstream_tls_context == nullptr) { - errors->AddError("can't decode DownstreamTlsContext"); - return {}; - } - const auto* downstream_tls_context_proto = - envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse( - serialized_downstream_tls_context->data(), - serialized_downstream_tls_context->size(), context.arena); - if (downstream_tls_context_proto == nullptr) { - errors->AddError("can't decode DownstreamTlsContext"); - return {}; - } - XdsListenerResource::DownstreamTlsContext 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) { - ValidationErrors::ScopedField field(errors, ".common_tls_context"); - downstream_tls_context.common_tls_context = - CommonTlsContext::Parse(context, common_tls_context, errors); - // Note: We can't be more specific about the field name for this - // error, because we don't know which fields they were found in - // inside of CommonTlsContext, so we make the error message a bit - // more verbose to compensate. - if (!downstream_tls_context.common_tls_context - .certificate_validation_context.match_subject_alt_names.empty()) { - errors->AddError("match_subject_alt_names not supported on servers"); - } - } - // Note: We can't be more specific about the field name for this - // error, because we don't know which fields they were found in - // inside of CommonTlsContext, so we make the error message a bit - // more verbose to compensate. - if (downstream_tls_context.common_tls_context - .tls_certificate_provider_instance.instance_name.empty()) { - errors->AddError( - "TLS configuration provided but no " - "tls_certificate_provider_instance found"); - } - 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.require_client_certificate && - downstream_tls_context.common_tls_context.certificate_validation_context - .ca_certificate_provider_instance.instance_name.empty()) { - ValidationErrors::ScopedField field(errors, - ".require_client_certificate"); - errors->AddError( - "client certificate required but no certificate " - "provider instance specified for validation"); - } - } - auto* require_sni = - envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni( - downstream_tls_context_proto); - if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) { - ValidationErrors::ScopedField field(errors, ".require_sni"); - errors->AddError("field unsupported"); - } - if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy( - downstream_tls_context_proto) != - envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) { - ValidationErrors::ScopedField field(errors, ".ocsp_staple_policy"); - errors->AddError("value must be LENIENT_STAPLING"); - } - return downstream_tls_context; -} - -absl::optional CidrRangeParse( - const envoy_config_core_v3_CidrRange* cidr_range_proto, - ValidationErrors* errors) { - ValidationErrors::ScopedField field(errors, ".address_prefix"); - XdsListenerResource::FilterChainMap::CidrRange cidr_range; - std::string address_prefix = UpbStringToStdString( - envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto)); - auto address = StringToSockaddr(address_prefix, /*port=*/0); - if (!address.ok()) { - errors->AddError(address.status().message()); - return absl::nullopt; - } - cidr_range.address = *address; - cidr_range.prefix_len = 0; - auto* prefix_len_proto = - envoy_config_core_v3_CidrRange_prefix_len(cidr_range_proto); - if (prefix_len_proto != nullptr) { - cidr_range.prefix_len = std::min( - google_protobuf_UInt32Value_value(prefix_len_proto), - (reinterpret_cast(cidr_range.address.addr)) - ->sa_family == GRPC_AF_INET - ? uint32_t{32} - : uint32_t{128}); - } - // Normalize the network address by masking it with prefix_len - grpc_sockaddr_mask_bits(&cidr_range.address, cidr_range.prefix_len); - return cidr_range; -} - -absl::optional FilterChainMatchParse( - const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto, - ValidationErrors* errors) { - FilterChain::FilterChainMatch filter_chain_match; - const size_t original_error_size = errors->size(); - // destination_port - 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); - } - // prefix_ranges - 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++) { - ValidationErrors::ScopedField field( - errors, absl::StrCat(".prefix_ranges[", i, "]")); - auto cidr_range = CidrRangeParse(prefix_ranges[i], errors); - if (cidr_range.has_value()) { - filter_chain_match.prefix_ranges.push_back(*cidr_range); - } - } - // source_type - filter_chain_match.source_type = - static_cast( - envoy_config_listener_v3_FilterChainMatch_source_type( - filter_chain_match_proto)); - // source_prefix_ranges - 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++) { - ValidationErrors::ScopedField field( - errors, absl::StrCat(".source_prefix_ranges[", i, "]")); - auto cidr_range = CidrRangeParse(source_prefix_ranges[i], errors); - if (cidr_range.has_value()) { - filter_chain_match.source_prefix_ranges.push_back(*cidr_range); - } - } - // source_ports - 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]); - } - // server_names - 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])); - } - // transport_protocol - filter_chain_match.transport_protocol = UpbStringToStdString( - envoy_config_listener_v3_FilterChainMatch_transport_protocol( - filter_chain_match_proto)); - // application_protocols - 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 result. - if (errors->size() != original_error_size) return absl::nullopt; - return filter_chain_match; -} - -absl::optional FilterChainParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_FilterChain* filter_chain_proto, - ValidationErrors* errors) { - FilterChain filter_chain; - const size_t original_error_size = errors->size(); - // filter_chain_match - auto* filter_chain_match = - envoy_config_listener_v3_FilterChain_filter_chain_match( - filter_chain_proto); - if (filter_chain_match != nullptr) { - ValidationErrors::ScopedField field(errors, ".filter_chain_match"); - auto match = FilterChainMatchParse(filter_chain_match, errors); - if (match.has_value()) { - filter_chain.filter_chain_match = std::move(*match); - } - } - // filters - { - ValidationErrors::ScopedField field(errors, ".filters"); - filter_chain.filter_chain_data = - std::make_shared(); - size_t size = 0; - auto* filters = - envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); - if (size != 1) { - errors->AddError( - "must have exactly one filter (HttpConnectionManager -- " - "no other filter is supported at the moment)"); - } - // entries in filters list - for (size_t i = 0; i < size; ++i) { - ValidationErrors::ScopedField field( - errors, absl::StrCat("[", i, "].typed_config")); - auto* typed_config = - envoy_config_listener_v3_Filter_typed_config(filters[i]); - auto extension = ExtractXdsExtension(context, typed_config, errors); - if (extension.has_value()) { - filter_chain.filter_chain_data->http_connection_manager = - HttpConnectionManagerParse(/*is_client=*/false, context, - std::move(*extension), errors); - } - } - } - // transport_socket - auto* transport_socket = - envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto); - if (transport_socket != nullptr) { - ValidationErrors::ScopedField field(errors, ".transport_socket"); - filter_chain.filter_chain_data->downstream_tls_context = - DownstreamTlsContextParse(context, transport_socket, errors); - } - // Return result. - if (errors->size() != original_error_size) return absl::nullopt; - return filter_chain; -} - -absl::optional AddressParse( - const envoy_config_core_v3_Address* address_proto, - ValidationErrors* errors) { - if (address_proto == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - ValidationErrors::ScopedField field(errors, ".socket_address"); - const auto* socket_address = - envoy_config_core_v3_Address_socket_address(address_proto); - if (socket_address == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - { - ValidationErrors::ScopedField field(errors, ".protocol"); - if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != - envoy_config_core_v3_SocketAddress_TCP) { - errors->AddError("value must be TCP"); - } - } - ValidationErrors::ScopedField field2(errors, ".port_value"); - uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); - if (port > 65535) { - errors->AddError("invalid port"); - return absl::nullopt; - } - return JoinHostPort( - UpbStringToAbsl( - envoy_config_core_v3_SocketAddress_address(socket_address)), - port); -} - -// An intermediate map for filter chains that we create to validate the list of -// filter chains received from the control plane and to finally create -// XdsListenerResource::FilterChainMap -struct InternalFilterChainMap { - using SourceIpMap = - std::map; - using ConnectionSourceTypesArray = std::array; - struct DestinationIp { - absl::optional prefix_range; - bool transport_protocol_raw_buffer_provided = false; - ConnectionSourceTypesArray source_types_array; - }; - using DestinationIpMap = std::map; - DestinationIpMap destination_ip_map; -}; - -void AddFilterChainDataForSourcePort( - const FilterChain& filter_chain, uint32_t port, - XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, - ValidationErrors* errors) { - auto insert_result = ports_map->emplace( - port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{ - filter_chain.filter_chain_data}); - if (!insert_result.second) { - errors->AddError(absl::StrCat( - "duplicate matching rules detected when adding filter chain: ", - filter_chain.filter_chain_match.ToString())); - } -} - -void AddFilterChainDataForSourcePorts( - const FilterChain& filter_chain, - XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, - ValidationErrors* errors) { - if (filter_chain.filter_chain_match.source_ports.empty()) { - AddFilterChainDataForSourcePort(filter_chain, 0, ports_map, errors); - } else { - for (uint32_t port : filter_chain.filter_chain_match.source_ports) { - AddFilterChainDataForSourcePort(filter_chain, port, ports_map, errors); - } - } -} - -void AddFilterChainDataForSourceIpRange( - const FilterChain& filter_chain, - InternalFilterChainMap::SourceIpMap* source_ip_map, - ValidationErrors* errors) { - if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) { - auto insert_result = source_ip_map->emplace( - "", XdsListenerResource::FilterChainMap::SourceIp()); - AddFilterChainDataForSourcePorts( - filter_chain, &insert_result.first->second.ports_map, errors); - } else { - for (const auto& prefix_range : - filter_chain.filter_chain_match.source_prefix_ranges) { - auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); - if (!addr_str.ok()) { - errors->AddError(absl::StrCat( - "error parsing source IP sockaddr (should not happen): ", - addr_str.status().message())); - continue; - } - auto insert_result = source_ip_map->emplace( - absl::StrCat(*addr_str, "/", prefix_range.prefix_len), - XdsListenerResource::FilterChainMap::SourceIp()); - if (insert_result.second) { - insert_result.first->second.prefix_range.emplace(prefix_range); - } - AddFilterChainDataForSourcePorts( - filter_chain, &insert_result.first->second.ports_map, errors); - } - } -} - -void AddFilterChainDataForSourceType( - const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip, - ValidationErrors* errors) { - CHECK(static_cast(filter_chain.filter_chain_match.source_type) < - 3u); - AddFilterChainDataForSourceIpRange( - filter_chain, - &destination_ip->source_types_array[static_cast( - filter_chain.filter_chain_match.source_type)], - errors); -} - -void AddFilterChainDataForApplicationProtocols( - const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip, - ValidationErrors* errors) { - // Only allow filter chains that do not mention application protocols - if (filter_chain.filter_chain_match.application_protocols.empty()) { - AddFilterChainDataForSourceType(filter_chain, destination_ip, errors); - } -} - -void AddFilterChainDataForTransportProtocol( - const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip, - ValidationErrors* errors) { - 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; - } - // 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; - } - 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(); - } - AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip, - errors); -} - -void AddFilterChainDataForServerNames( - const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip, - ValidationErrors* errors) { - // Don't continue adding filter chains with server names mentioned - if (filter_chain.filter_chain_match.server_names.empty()) { - AddFilterChainDataForTransportProtocol(filter_chain, destination_ip, - errors); - } -} - -void AddFilterChainDataForDestinationIpRange( - const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIpMap* destination_ip_map, - ValidationErrors* errors) { - if (filter_chain.filter_chain_match.prefix_ranges.empty()) { - auto insert_result = destination_ip_map->emplace( - "", InternalFilterChainMap::DestinationIp()); - AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second, - errors); - } else { - for (const auto& prefix_range : - filter_chain.filter_chain_match.prefix_ranges) { - auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); - if (!addr_str.ok()) { - errors->AddError(absl::StrCat( - "error parsing destination IP sockaddr (should not happen): ", - addr_str.status().message())); - continue; - } - auto insert_result = destination_ip_map->emplace( - absl::StrCat(*addr_str, "/", prefix_range.prefix_len), - InternalFilterChainMap::DestinationIp()); - if (insert_result.second) { - insert_result.first->second.prefix_range.emplace(prefix_range); - } - AddFilterChainDataForServerNames(filter_chain, - &insert_result.first->second, errors); - } - } -} - -XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( - InternalFilterChainMap* internal_filter_chain_map) { - XdsListenerResource::FilterChainMap filter_chain_map; - for (auto& destination_ip_pair : - internal_filter_chain_map->destination_ip_map) { - XdsListenerResource::FilterChainMap::DestinationIp destination_ip; - destination_ip.prefix_range = destination_ip_pair.second.prefix_range; - for (int i = 0; i < 3; i++) { - auto& source_ip_map = destination_ip_pair.second.source_types_array[i]; - for (auto& source_ip_pair : source_ip_map) { - destination_ip.source_types_array[i].push_back( - std::move(source_ip_pair.second)); - } - } - filter_chain_map.destination_ip_vector.push_back(std::move(destination_ip)); - } - return filter_chain_map; -} - -XdsListenerResource::FilterChainMap BuildFilterChainMap( - const std::vector& filter_chains, ValidationErrors* errors) { - 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; - AddFilterChainDataForDestinationIpRange( - filter_chain, &internal_filter_chain_map.destination_ip_map, errors); - } - return BuildFromInternalFilterChainMap(&internal_filter_chain_map); -} - -absl::StatusOr> -LdsResourceParseServer(const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_Listener* listener) { - ValidationErrors errors; - XdsListenerResource::TcpListener tcp_listener; - // address - { - ValidationErrors::ScopedField field(&errors, "address"); - auto address = AddressParse( - envoy_config_listener_v3_Listener_address(listener), &errors); - if (address.has_value()) tcp_listener.address = std::move(*address); - } - // use_original_dst - { - ValidationErrors::ScopedField field(&errors, "use_original_dst"); - const auto* use_original_dst = - envoy_config_listener_v3_Listener_use_original_dst(listener); - if (use_original_dst != nullptr && - google_protobuf_BoolValue_value(use_original_dst)) { - errors.AddError("field not supported"); - } - } - // filter_chains - size_t num_filter_chains = 0; - { - ValidationErrors::ScopedField field(&errors, "filter_chains"); - auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains( - listener, &num_filter_chains); - std::vector parsed_filter_chains; - parsed_filter_chains.reserve(num_filter_chains); - for (size_t i = 0; i < num_filter_chains; i++) { - ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); - auto filter_chain = FilterChainParse(context, filter_chains[i], &errors); - if (filter_chain.has_value()) { - parsed_filter_chains.push_back(std::move(*filter_chain)); - } - } - tcp_listener.filter_chain_map = - BuildFilterChainMap(parsed_filter_chains, &errors); - } - // default_filter_chain - { - ValidationErrors::ScopedField field(&errors, "default_filter_chain"); - auto* default_filter_chain = - envoy_config_listener_v3_Listener_default_filter_chain(listener); - if (default_filter_chain != nullptr) { - auto filter_chain = - FilterChainParse(context, default_filter_chain, &errors); - if (filter_chain.has_value() && - filter_chain->filter_chain_data != nullptr) { - tcp_listener.default_filter_chain = - std::move(*filter_chain->filter_chain_data); - } - } else if (num_filter_chains == 0) { - // Make sure that there is at least one filter chain to use. - errors.AddError("must be set if filter_chains is unset"); - } - } - // Return result. - if (!errors.ok()) { - return errors.status(absl::StatusCode::kInvalidArgument, - "errors validating server Listener"); - } - auto lds_update = std::make_shared(); - lds_update->listener = std::move(tcp_listener); - return lds_update; -} - -absl::StatusOr> LdsResourceParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_Listener* 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); - // TODO(roth): Re-enable the following check once - // github.com/istio/istio/issues/38914 is resolved. - // if (api_listener != nullptr && address != nullptr) { - // return absl::InvalidArgumentError( - // "Listener has both address and ApiListener"); - // } - if (api_listener == nullptr && address == nullptr) { - return absl::InvalidArgumentError( - "Listener has neither address nor ApiListener"); - } - // If api_listener is present, it's for a client; otherwise, it's - // for a server. - if (api_listener != nullptr) { - return LdsResourceParseClient(context, api_listener); - } - return LdsResourceParseServer(context, listener); -} - -void MaybeLogListener(const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_Listener* listener) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { - const upb_MessageDef* msg_type = - envoy_config_listener_v3_Listener_getmsgdef(context.symtab); - char buf[10240]; - upb_TextEncode(reinterpret_cast(listener), msg_type, - nullptr, 0, buf, sizeof(buf)); - VLOG(2) << "[xds_client " << context.client << "] Listener: " << buf; - } -} - -} // namespace - -XdsResourceType::DecodeResult XdsListenerResourceType::Decode( - const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const { - DecodeResult result; - // Parse serialized proto. - auto* resource = envoy_config_listener_v3_Listener_parse( - serialized_resource.data(), serialized_resource.size(), context.arena); - if (resource == nullptr) { - result.resource = - absl::InvalidArgumentError("Can't parse Listener resource."); - return result; - } - MaybeLogListener(context, resource); - // Validate resource. - result.name = - UpbStringToStdString(envoy_config_listener_v3_Listener_name(resource)); - auto listener = LdsResourceParse(context, resource); - if (!listener.ok()) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(ERROR) << "[xds_client " << context.client << "] invalid Listener " - << *result.name << ": " << listener.status(); - } - result.resource = listener.status(); - } else { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(INFO) << "[xds_client " << context.client << "] parsed Listener " - << *result.name << ": " << (*listener)->ToString(); - } - result.resource = std::move(*listener); - } - return result; -} - } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_listener.h b/src/core/xds/grpc/xds_listener.h index ce5d1ca11a6..f6d49bf3f31 100644 --- a/src/core/xds/grpc/xds_listener.h +++ b/src/core/xds/grpc/xds_listener.h @@ -27,22 +27,14 @@ #include #include -#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" -#include "envoy/config/listener/v3/listener.upbdefs.h" -#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" -#include "upb/reflection/def.h" - -#include #include "src/core/lib/gprpp/time.h" #include "src/core/lib/iomgr/resolved_address.h" -#include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/grpc/xds_route_config.h" -#include "src/core/xds/xds_client/xds_client.h" #include "src/core/xds/xds_client/xds_resource_type.h" #include "src/core/xds/xds_client/xds_resource_type_impl.h" @@ -207,30 +199,6 @@ struct XdsListenerResource : public XdsResourceType::ResourceData { std::string ToString() const; }; -class XdsListenerResourceType final - : public XdsResourceTypeImpl { - public: - absl::string_view type_url() const override { - return "envoy.config.listener.v3.Listener"; - } - - DecodeResult Decode(const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const override; - - bool AllResourcesRequiredInSotW() const override { return true; } - - void InitUpbSymtab(XdsClient* xds_client, - upb_DefPool* symtab) const override { - envoy_config_listener_v3_Listener_getmsgdef(symtab); - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( - symtab); - const auto& http_filter_registry = - static_cast(xds_client->bootstrap()) - .http_filter_registry(); - http_filter_registry.PopulateSymtab(symtab); - } -}; - } // namespace grpc_core #endif // GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_H diff --git a/src/core/xds/grpc/xds_listener_parser.cc b/src/core/xds/grpc/xds_listener_parser.cc new file mode 100644 index 00000000000..cf3508533ba --- /dev/null +++ b/src/core/xds/grpc/xds_listener_parser.cc @@ -0,0 +1,991 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_listener_parser.h" + +#include + +#include +#include + +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "envoy/config/core/v3/address.upb.h" +#include "envoy/config/core/v3/base.upb.h" +#include "envoy/config/core/v3/config_source.upb.h" +#include "envoy/config/core/v3/protocol.upb.h" +#include "envoy/config/listener/v3/api_listener.upb.h" +#include "envoy/config/listener/v3/listener.upb.h" +#include "envoy/config/listener/v3/listener.upbdefs.h" +#include "envoy/config/listener/v3/listener_components.upb.h" +#include "envoy/config/route/v3/route.upb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/wrappers.upb.h" +#include "upb/text/encode.h" + +#include + +#include "src/core/lib/address_utils/parse_address.h" +#include "src/core/lib/address_utils/sockaddr_utils.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/host_port.h" +#include "src/core/lib/gprpp/match.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/matchers/matchers.h" +#include "src/core/util/upb_utils.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_route_config_parser.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +namespace grpc_core { + +namespace { + +struct FilterChain { + struct FilterChainMatch { + uint32_t destination_port = 0; + std::vector prefix_ranges; + XdsListenerResource::FilterChainMap::ConnectionSourceType source_type = + XdsListenerResource::FilterChainMap::ConnectionSourceType::kAny; + std::vector + source_prefix_ranges; + std::vector source_ports; + std::vector server_names; + std::string transport_protocol; + std::vector application_protocols; + + std::string ToString() const; + } filter_chain_match; + + std::shared_ptr filter_chain_data; +}; + +std::string FilterChain::FilterChainMatch::ToString() const { + std::vector contents; + if (destination_port != 0) { + contents.push_back(absl::StrCat("destination_port=", destination_port)); + } + if (!prefix_ranges.empty()) { + std::vector prefix_ranges_content; + prefix_ranges_content.reserve(prefix_ranges.size()); + for (const auto& range : prefix_ranges) { + prefix_ranges_content.push_back(range.ToString()); + } + contents.push_back(absl::StrCat( + "prefix_ranges={", absl::StrJoin(prefix_ranges_content, ", "), "}")); + } + if (source_type == XdsListenerResource::FilterChainMap::ConnectionSourceType:: + kSameIpOrLoopback) { + contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); + } else if (source_type == XdsListenerResource::FilterChainMap:: + ConnectionSourceType::kExternal) { + contents.push_back("source_type=EXTERNAL"); + } + if (!source_prefix_ranges.empty()) { + std::vector source_prefix_ranges_content; + source_prefix_ranges_content.reserve(source_prefix_ranges.size()); + 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, ", "), "}"); +} + +void MaybeLogHttpConnectionManager( + const XdsResourceType::DecodeContext& context, + const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* + http_connection_manager_config) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { + const upb_MessageDef* msg_type = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( + context.symtab); + char buf[10240]; + upb_TextEncode( + reinterpret_cast(http_connection_manager_config), + msg_type, nullptr, 0, buf, sizeof(buf)); + VLOG(2) << "[xds_client " << context.client + << "] HttpConnectionManager: " << buf; + } +} + +XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse( + bool is_client, const XdsResourceType::DecodeContext& context, + XdsExtension extension, ValidationErrors* errors) { + if (extension.type != + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager") { + errors->AddError("unsupported filter type"); + return {}; + } + auto* serialized_hcm_config = + absl::get_if(&extension.value); + if (serialized_hcm_config == nullptr) { + errors->AddError("could not parse HttpConnectionManager config"); + return {}; + } + const auto* http_connection_manager_proto = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( + serialized_hcm_config->data(), serialized_hcm_config->size(), + context.arena); + if (http_connection_manager_proto == nullptr) { + errors->AddError("could not parse HttpConnectionManager config"); + return {}; + } + MaybeLogHttpConnectionManager(context, http_connection_manager_proto); + XdsListenerResource::HttpConnectionManager http_connection_manager; + // xff_num_trusted_hops -- must be zero as per + // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md + if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops( + http_connection_manager_proto) != 0) { + ValidationErrors::ScopedField field(errors, ".xff_num_trusted_hops"); + errors->AddError("must be zero"); + } + // original_ip_detection_extensions -- must be empty as per + // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md + { + size_t size; + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_original_ip_detection_extensions( + http_connection_manager_proto, &size); + if (size != 0) { + ValidationErrors::ScopedField field(errors, + ".original_ip_detection_extensions"); + errors->AddError("must be empty"); + } + } + // common_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) { + // max_stream_duration + const google_protobuf_Duration* duration = + envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options); + if (duration != nullptr) { + ValidationErrors::ScopedField field( + errors, ".common_http_protocol_options.max_stream_duration"); + http_connection_manager.http_max_stream_duration = + ParseDuration(duration, errors); + } + } + // http_filters + { + ValidationErrors::ScopedField field(errors, ".http_filters"); + const auto& http_filter_registry = + static_cast(context.client->bootstrap()) + .http_filter_registry(); + size_t num_filters = 0; + const auto* http_filters = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( + http_connection_manager_proto, &num_filters); + std::set names_seen; + const size_t original_error_size = errors->size(); + for (size_t i = 0; i < num_filters; ++i) { + ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); + const auto* http_filter = http_filters[i]; + // name + absl::string_view name = UpbStringToAbsl( + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( + http_filter)); + { + ValidationErrors::ScopedField field(errors, ".name"); + if (name.empty()) { + errors->AddError("empty filter name"); + continue; + } + if (names_seen.find(name) != names_seen.end()) { + errors->AddError(absl::StrCat("duplicate HTTP filter name: ", name)); + continue; + } + } + names_seen.insert(name); + // is_optional + const bool is_optional = + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( + http_filter); + // typed_config + { + ValidationErrors::ScopedField field(errors, ".typed_config"); + const google_protobuf_Any* typed_config = + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( + http_filter); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) continue; + const XdsHttpFilterImpl* filter_impl = + http_filter_registry.GetFilterForType(extension->type); + if (filter_impl == nullptr) { + if (!is_optional) errors->AddError("unsupported filter type"); + continue; + } + if ((is_client && !filter_impl->IsSupportedOnClients()) || + (!is_client && !filter_impl->IsSupportedOnServers())) { + if (!is_optional) { + errors->AddError(absl::StrCat("filter is not supported on ", + is_client ? "clients" : "servers")); + } + continue; + } + absl::optional filter_config = + filter_impl->GenerateFilterConfig(context, std::move(*extension), + errors); + if (filter_config.has_value()) { + http_connection_manager.http_filters.emplace_back( + XdsListenerResource::HttpConnectionManager::HttpFilter{ + std::string(name), std::move(*filter_config)}); + } + } + } + if (errors->size() == original_error_size && + http_connection_manager.http_filters.empty()) { + errors->AddError("expected at least one HTTP filter"); + } + // Make sure that the last filter is terminal and non-last filters are + // non-terminal. Note that this check is being performed in a separate loop + // to take care of the case where there are two terminal filters in the list + // out of which only one gets added in the final list. + for (const auto& http_filter : http_connection_manager.http_filters) { + const XdsHttpFilterImpl* filter_impl = + http_filter_registry.GetFilterForType( + http_filter.config.config_proto_type_name); + if (&http_filter != &http_connection_manager.http_filters.back()) { + // Filters before the last filter must not be terminal. + if (filter_impl->IsTerminalFilter()) { + errors->AddError( + absl::StrCat("terminal filter for config type ", + http_filter.config.config_proto_type_name, + " must be the last filter in the chain")); + } + } else { + // The last filter must be terminal. + if (!filter_impl->IsTerminalFilter()) { + errors->AddError( + absl::StrCat("non-terminal filter for config type ", + http_filter.config.config_proto_type_name, + " is the last filter in the chain")); + } + } + } + } + // 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); + ValidationErrors::ScopedField field(errors, ".route_config"); + http_connection_manager.route_config = + XdsRouteConfigResourceParse(context, route_config, errors); + } else { + // 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) { + errors->AddError("neither route_config nor rds fields are present"); + } else { + // Get the route_config_name. + http_connection_manager.route_config = UpbStringToStdString( + envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( + 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); + ValidationErrors::ScopedField field(errors, ".rds.config_source"); + if (config_source == nullptr) { + errors->AddError("field not present"); + } else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && + !envoy_config_core_v3_ConfigSource_has_self(config_source)) { + errors->AddError("ConfigSource does not specify ADS or SELF"); + } + } + } + return http_connection_manager; +} + +absl::StatusOr> +LdsResourceParseClient( + const XdsResourceType::DecodeContext& context, + const envoy_config_listener_v3_ApiListener* api_listener) { + auto lds_update = std::make_shared(); + ValidationErrors errors; + ValidationErrors::ScopedField field(&errors, "api_listener.api_listener"); + auto* api_listener_field = + envoy_config_listener_v3_ApiListener_api_listener(api_listener); + auto extension = ExtractXdsExtension(context, api_listener_field, &errors); + if (extension.has_value()) { + lds_update->listener = HttpConnectionManagerParse( + /*is_client=*/true, context, std::move(*extension), &errors); + } + if (!errors.ok()) { + return errors.status(absl::StatusCode::kInvalidArgument, + "errors validating ApiListener"); + } + return std::move(lds_update); +} + +XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_core_v3_TransportSocket* transport_socket, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".typed_config"); + const auto* typed_config = + envoy_config_core_v3_TransportSocket_typed_config(transport_socket); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) return {}; + if (extension->type != + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext") { + ValidationErrors::ScopedField field(errors, ".type_url"); + errors->AddError("unsupported transport socket type"); + return {}; + } + absl::string_view* serialized_downstream_tls_context = + absl::get_if(&extension->value); + if (serialized_downstream_tls_context == nullptr) { + errors->AddError("can't decode DownstreamTlsContext"); + return {}; + } + const auto* downstream_tls_context_proto = + envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse( + serialized_downstream_tls_context->data(), + serialized_downstream_tls_context->size(), context.arena); + if (downstream_tls_context_proto == nullptr) { + errors->AddError("can't decode DownstreamTlsContext"); + return {}; + } + XdsListenerResource::DownstreamTlsContext 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) { + ValidationErrors::ScopedField field(errors, ".common_tls_context"); + downstream_tls_context.common_tls_context = + CommonTlsContextParse(context, common_tls_context, errors); + // Note: We can't be more specific about the field name for this + // error, because we don't know which fields they were found in + // inside of CommonTlsContext, so we make the error message a bit + // more verbose to compensate. + if (!downstream_tls_context.common_tls_context + .certificate_validation_context.match_subject_alt_names.empty()) { + errors->AddError("match_subject_alt_names not supported on servers"); + } + } + // Note: We can't be more specific about the field name for this + // error, because we don't know which fields they were found in + // inside of CommonTlsContext, so we make the error message a bit + // more verbose to compensate. + if (downstream_tls_context.common_tls_context + .tls_certificate_provider_instance.instance_name.empty()) { + errors->AddError( + "TLS configuration provided but no " + "tls_certificate_provider_instance found"); + } + 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.require_client_certificate && + downstream_tls_context.common_tls_context.certificate_validation_context + .ca_certificate_provider_instance.instance_name.empty()) { + ValidationErrors::ScopedField field(errors, + ".require_client_certificate"); + errors->AddError( + "client certificate required but no certificate " + "provider instance specified for validation"); + } + } + auto* require_sni = + envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni( + downstream_tls_context_proto); + if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) { + ValidationErrors::ScopedField field(errors, ".require_sni"); + errors->AddError("field unsupported"); + } + if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy( + downstream_tls_context_proto) != + envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) { + ValidationErrors::ScopedField field(errors, ".ocsp_staple_policy"); + errors->AddError("value must be LENIENT_STAPLING"); + } + return downstream_tls_context; +} + +absl::optional CidrRangeParse( + const envoy_config_core_v3_CidrRange* cidr_range_proto, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".address_prefix"); + XdsListenerResource::FilterChainMap::CidrRange cidr_range; + std::string address_prefix = UpbStringToStdString( + envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto)); + auto address = StringToSockaddr(address_prefix, /*port=*/0); + if (!address.ok()) { + errors->AddError(address.status().message()); + return absl::nullopt; + } + cidr_range.address = *address; + cidr_range.prefix_len = 0; + auto* prefix_len_proto = + envoy_config_core_v3_CidrRange_prefix_len(cidr_range_proto); + if (prefix_len_proto != nullptr) { + cidr_range.prefix_len = std::min( + google_protobuf_UInt32Value_value(prefix_len_proto), + (reinterpret_cast(cidr_range.address.addr)) + ->sa_family == GRPC_AF_INET + ? uint32_t{32} + : uint32_t{128}); + } + // Normalize the network address by masking it with prefix_len + grpc_sockaddr_mask_bits(&cidr_range.address, cidr_range.prefix_len); + return cidr_range; +} + +absl::optional FilterChainMatchParse( + const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto, + ValidationErrors* errors) { + FilterChain::FilterChainMatch filter_chain_match; + const size_t original_error_size = errors->size(); + // destination_port + 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); + } + // prefix_ranges + 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++) { + ValidationErrors::ScopedField field( + errors, absl::StrCat(".prefix_ranges[", i, "]")); + auto cidr_range = CidrRangeParse(prefix_ranges[i], errors); + if (cidr_range.has_value()) { + filter_chain_match.prefix_ranges.push_back(*cidr_range); + } + } + // source_type + filter_chain_match.source_type = + static_cast( + envoy_config_listener_v3_FilterChainMatch_source_type( + filter_chain_match_proto)); + // source_prefix_ranges + 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++) { + ValidationErrors::ScopedField field( + errors, absl::StrCat(".source_prefix_ranges[", i, "]")); + auto cidr_range = CidrRangeParse(source_prefix_ranges[i], errors); + if (cidr_range.has_value()) { + filter_chain_match.source_prefix_ranges.push_back(*cidr_range); + } + } + // source_ports + 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]); + } + // server_names + 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])); + } + // transport_protocol + filter_chain_match.transport_protocol = UpbStringToStdString( + envoy_config_listener_v3_FilterChainMatch_transport_protocol( + filter_chain_match_proto)); + // application_protocols + 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 result. + if (errors->size() != original_error_size) return absl::nullopt; + return filter_chain_match; +} + +absl::optional FilterChainParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_listener_v3_FilterChain* filter_chain_proto, + ValidationErrors* errors) { + FilterChain filter_chain; + const size_t original_error_size = errors->size(); + // filter_chain_match + auto* filter_chain_match = + envoy_config_listener_v3_FilterChain_filter_chain_match( + filter_chain_proto); + if (filter_chain_match != nullptr) { + ValidationErrors::ScopedField field(errors, ".filter_chain_match"); + auto match = FilterChainMatchParse(filter_chain_match, errors); + if (match.has_value()) { + filter_chain.filter_chain_match = std::move(*match); + } + } + // filters + { + ValidationErrors::ScopedField field(errors, ".filters"); + filter_chain.filter_chain_data = + std::make_shared(); + size_t size = 0; + auto* filters = + envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); + if (size != 1) { + errors->AddError( + "must have exactly one filter (HttpConnectionManager -- " + "no other filter is supported at the moment)"); + } + // entries in filters list + for (size_t i = 0; i < size; ++i) { + ValidationErrors::ScopedField field( + errors, absl::StrCat("[", i, "].typed_config")); + auto* typed_config = + envoy_config_listener_v3_Filter_typed_config(filters[i]); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (extension.has_value()) { + filter_chain.filter_chain_data->http_connection_manager = + HttpConnectionManagerParse(/*is_client=*/false, context, + std::move(*extension), errors); + } + } + } + // transport_socket + auto* transport_socket = + envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto); + if (transport_socket != nullptr) { + ValidationErrors::ScopedField field(errors, ".transport_socket"); + filter_chain.filter_chain_data->downstream_tls_context = + DownstreamTlsContextParse(context, transport_socket, errors); + } + // Return result. + if (errors->size() != original_error_size) return absl::nullopt; + return filter_chain; +} + +absl::optional AddressParse( + const envoy_config_core_v3_Address* address_proto, + ValidationErrors* errors) { + if (address_proto == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + ValidationErrors::ScopedField field(errors, ".socket_address"); + const auto* socket_address = + envoy_config_core_v3_Address_socket_address(address_proto); + if (socket_address == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + { + ValidationErrors::ScopedField field(errors, ".protocol"); + if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != + envoy_config_core_v3_SocketAddress_TCP) { + errors->AddError("value must be TCP"); + } + } + ValidationErrors::ScopedField field2(errors, ".port_value"); + uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); + if (port > 65535) { + errors->AddError("invalid port"); + return absl::nullopt; + } + return JoinHostPort( + UpbStringToAbsl( + envoy_config_core_v3_SocketAddress_address(socket_address)), + port); +} + +// An intermediate map for filter chains that we create to validate the list of +// filter chains received from the control plane and to finally create +// XdsListenerResource::FilterChainMap +struct InternalFilterChainMap { + using SourceIpMap = + std::map; + using ConnectionSourceTypesArray = std::array; + struct DestinationIp { + absl::optional prefix_range; + bool transport_protocol_raw_buffer_provided = false; + ConnectionSourceTypesArray source_types_array; + }; + using DestinationIpMap = std::map; + DestinationIpMap destination_ip_map; +}; + +void AddFilterChainDataForSourcePort( + const FilterChain& filter_chain, uint32_t port, + XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, + ValidationErrors* errors) { + auto insert_result = ports_map->emplace( + port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{ + filter_chain.filter_chain_data}); + if (!insert_result.second) { + errors->AddError(absl::StrCat( + "duplicate matching rules detected when adding filter chain: ", + filter_chain.filter_chain_match.ToString())); + } +} + +void AddFilterChainDataForSourcePorts( + const FilterChain& filter_chain, + XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, + ValidationErrors* errors) { + if (filter_chain.filter_chain_match.source_ports.empty()) { + AddFilterChainDataForSourcePort(filter_chain, 0, ports_map, errors); + } else { + for (uint32_t port : filter_chain.filter_chain_match.source_ports) { + AddFilterChainDataForSourcePort(filter_chain, port, ports_map, errors); + } + } +} + +void AddFilterChainDataForSourceIpRange( + const FilterChain& filter_chain, + InternalFilterChainMap::SourceIpMap* source_ip_map, + ValidationErrors* errors) { + if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) { + auto insert_result = source_ip_map->emplace( + "", XdsListenerResource::FilterChainMap::SourceIp()); + AddFilterChainDataForSourcePorts( + filter_chain, &insert_result.first->second.ports_map, errors); + } else { + for (const auto& prefix_range : + filter_chain.filter_chain_match.source_prefix_ranges) { + auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); + if (!addr_str.ok()) { + errors->AddError(absl::StrCat( + "error parsing source IP sockaddr (should not happen): ", + addr_str.status().message())); + continue; + } + auto insert_result = source_ip_map->emplace( + absl::StrCat(*addr_str, "/", prefix_range.prefix_len), + XdsListenerResource::FilterChainMap::SourceIp()); + if (insert_result.second) { + insert_result.first->second.prefix_range.emplace(prefix_range); + } + AddFilterChainDataForSourcePorts( + filter_chain, &insert_result.first->second.ports_map, errors); + } + } +} + +void AddFilterChainDataForSourceType( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { + CHECK(static_cast(filter_chain.filter_chain_match.source_type) < + 3u); + AddFilterChainDataForSourceIpRange( + filter_chain, + &destination_ip->source_types_array[static_cast( + filter_chain.filter_chain_match.source_type)], + errors); +} + +void AddFilterChainDataForApplicationProtocols( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { + // Only allow filter chains that do not mention application protocols + if (filter_chain.filter_chain_match.application_protocols.empty()) { + AddFilterChainDataForSourceType(filter_chain, destination_ip, errors); + } +} + +void AddFilterChainDataForTransportProtocol( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { + 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; + } + // 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; + } + 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(); + } + AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip, + errors); +} + +void AddFilterChainDataForServerNames( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { + // Don't continue adding filter chains with server names mentioned + if (filter_chain.filter_chain_match.server_names.empty()) { + AddFilterChainDataForTransportProtocol(filter_chain, destination_ip, + errors); + } +} + +void AddFilterChainDataForDestinationIpRange( + const FilterChain& filter_chain, + InternalFilterChainMap::DestinationIpMap* destination_ip_map, + ValidationErrors* errors) { + if (filter_chain.filter_chain_match.prefix_ranges.empty()) { + auto insert_result = destination_ip_map->emplace( + "", InternalFilterChainMap::DestinationIp()); + AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second, + errors); + } else { + for (const auto& prefix_range : + filter_chain.filter_chain_match.prefix_ranges) { + auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); + if (!addr_str.ok()) { + errors->AddError(absl::StrCat( + "error parsing destination IP sockaddr (should not happen): ", + addr_str.status().message())); + continue; + } + auto insert_result = destination_ip_map->emplace( + absl::StrCat(*addr_str, "/", prefix_range.prefix_len), + InternalFilterChainMap::DestinationIp()); + if (insert_result.second) { + insert_result.first->second.prefix_range.emplace(prefix_range); + } + AddFilterChainDataForServerNames(filter_chain, + &insert_result.first->second, errors); + } + } +} + +XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( + InternalFilterChainMap* internal_filter_chain_map) { + XdsListenerResource::FilterChainMap filter_chain_map; + for (auto& destination_ip_pair : + internal_filter_chain_map->destination_ip_map) { + XdsListenerResource::FilterChainMap::DestinationIp destination_ip; + destination_ip.prefix_range = destination_ip_pair.second.prefix_range; + for (int i = 0; i < 3; i++) { + auto& source_ip_map = destination_ip_pair.second.source_types_array[i]; + for (auto& source_ip_pair : source_ip_map) { + destination_ip.source_types_array[i].push_back( + std::move(source_ip_pair.second)); + } + } + filter_chain_map.destination_ip_vector.push_back(std::move(destination_ip)); + } + return filter_chain_map; +} + +XdsListenerResource::FilterChainMap BuildFilterChainMap( + const std::vector& filter_chains, ValidationErrors* errors) { + 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; + AddFilterChainDataForDestinationIpRange( + filter_chain, &internal_filter_chain_map.destination_ip_map, errors); + } + return BuildFromInternalFilterChainMap(&internal_filter_chain_map); +} + +absl::StatusOr> +LdsResourceParseServer(const XdsResourceType::DecodeContext& context, + const envoy_config_listener_v3_Listener* listener) { + ValidationErrors errors; + XdsListenerResource::TcpListener tcp_listener; + // address + { + ValidationErrors::ScopedField field(&errors, "address"); + auto address = AddressParse( + envoy_config_listener_v3_Listener_address(listener), &errors); + if (address.has_value()) tcp_listener.address = std::move(*address); + } + // use_original_dst + { + ValidationErrors::ScopedField field(&errors, "use_original_dst"); + const auto* use_original_dst = + envoy_config_listener_v3_Listener_use_original_dst(listener); + if (use_original_dst != nullptr && + google_protobuf_BoolValue_value(use_original_dst)) { + errors.AddError("field not supported"); + } + } + // filter_chains + size_t num_filter_chains = 0; + { + ValidationErrors::ScopedField field(&errors, "filter_chains"); + auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains( + listener, &num_filter_chains); + std::vector parsed_filter_chains; + parsed_filter_chains.reserve(num_filter_chains); + for (size_t i = 0; i < num_filter_chains; i++) { + ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); + auto filter_chain = FilterChainParse(context, filter_chains[i], &errors); + if (filter_chain.has_value()) { + parsed_filter_chains.push_back(std::move(*filter_chain)); + } + } + tcp_listener.filter_chain_map = + BuildFilterChainMap(parsed_filter_chains, &errors); + } + // default_filter_chain + { + ValidationErrors::ScopedField field(&errors, "default_filter_chain"); + auto* default_filter_chain = + envoy_config_listener_v3_Listener_default_filter_chain(listener); + if (default_filter_chain != nullptr) { + auto filter_chain = + FilterChainParse(context, default_filter_chain, &errors); + if (filter_chain.has_value() && + filter_chain->filter_chain_data != nullptr) { + tcp_listener.default_filter_chain = + std::move(*filter_chain->filter_chain_data); + } + } else if (num_filter_chains == 0) { + // Make sure that there is at least one filter chain to use. + errors.AddError("must be set if filter_chains is unset"); + } + } + // Return result. + if (!errors.ok()) { + return errors.status(absl::StatusCode::kInvalidArgument, + "errors validating server Listener"); + } + auto lds_update = std::make_shared(); + lds_update->listener = std::move(tcp_listener); + return lds_update; +} + +absl::StatusOr> LdsResourceParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_listener_v3_Listener* 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); + // TODO(roth): Re-enable the following check once + // github.com/istio/istio/issues/38914 is resolved. + // if (api_listener != nullptr && address != nullptr) { + // return absl::InvalidArgumentError( + // "Listener has both address and ApiListener"); + // } + if (api_listener == nullptr && address == nullptr) { + return absl::InvalidArgumentError( + "Listener has neither address nor ApiListener"); + } + // If api_listener is present, it's for a client; otherwise, it's + // for a server. + if (api_listener != nullptr) { + return LdsResourceParseClient(context, api_listener); + } + return LdsResourceParseServer(context, listener); +} + +void MaybeLogListener(const XdsResourceType::DecodeContext& context, + const envoy_config_listener_v3_Listener* listener) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { + const upb_MessageDef* msg_type = + envoy_config_listener_v3_Listener_getmsgdef(context.symtab); + char buf[10240]; + upb_TextEncode(reinterpret_cast(listener), msg_type, + nullptr, 0, buf, sizeof(buf)); + VLOG(2) << "[xds_client " << context.client << "] Listener: " << buf; + } +} + +} // namespace + +XdsResourceType::DecodeResult XdsListenerResourceType::Decode( + const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const { + DecodeResult result; + // Parse serialized proto. + auto* resource = envoy_config_listener_v3_Listener_parse( + serialized_resource.data(), serialized_resource.size(), context.arena); + if (resource == nullptr) { + result.resource = + absl::InvalidArgumentError("Can't parse Listener resource."); + return result; + } + MaybeLogListener(context, resource); + // Validate resource. + result.name = + UpbStringToStdString(envoy_config_listener_v3_Listener_name(resource)); + auto listener = LdsResourceParse(context, resource); + if (!listener.ok()) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(ERROR) << "[xds_client " << context.client << "] invalid Listener " + << *result.name << ": " << listener.status(); + } + result.resource = listener.status(); + } else { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(INFO) << "[xds_client " << context.client << "] parsed Listener " + << *result.name << ": " << (*listener)->ToString(); + } + result.resource = std::move(*listener); + } + return result; +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_listener_parser.h b/src/core/xds/grpc/xds_listener_parser.h new file mode 100644 index 00000000000..1a0e1e95004 --- /dev/null +++ b/src/core/xds/grpc/xds_listener_parser.h @@ -0,0 +1,60 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H + +#include "absl/strings/string_view.h" +#include "envoy/config/listener/v3/listener.upbdefs.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" +#include "upb/reflection/def.h" + +#include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" +#include "src/core/xds/grpc/xds_listener.h" +#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/xds_client/xds_resource_type.h" +#include "src/core/xds/xds_client/xds_resource_type_impl.h" + +namespace grpc_core { + +class XdsListenerResourceType final + : public XdsResourceTypeImpl { + public: + absl::string_view type_url() const override { + return "envoy.config.listener.v3.Listener"; + } + + DecodeResult Decode(const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const override; + + bool AllResourcesRequiredInSotW() const override { return true; } + + void InitUpbSymtab(XdsClient* xds_client, + upb_DefPool* symtab) const override { + envoy_config_listener_v3_Listener_getmsgdef(symtab); + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( + symtab); + const auto& http_filter_registry = + static_cast(xds_client->bootstrap()) + .http_filter_registry(); + http_filter_registry.PopulateSymtab(symtab); + } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H diff --git a/src/core/xds/grpc/xds_route_config.cc b/src/core/xds/grpc/xds_route_config.cc index c2f8a406207..3c6bbe7f8a5 100644 --- a/src/core/xds/grpc/xds_route_config.cc +++ b/src/core/xds/grpc/xds_route_config.cc @@ -16,78 +16,22 @@ #include "src/core/xds/grpc/xds_route_config.h" -#include -#include - -#include -#include -#include -#include #include #include #include -#include "absl/log/check.h" -#include "absl/log/log.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" -#include "envoy/config/core/v3/base.upb.h" -#include "envoy/config/core/v3/extension.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/type/matcher/v3/regex.upb.h" -#include "envoy/type/matcher/v3/string.upb.h" -#include "envoy/type/v3/percent.upb.h" -#include "envoy/type/v3/range.upb.h" -#include "google/protobuf/any.upb.h" -#include "google/protobuf/duration.upb.h" -#include "google/protobuf/wrappers.upb.h" #include "re2/re2.h" -#include "upb/base/string_view.h" -#include "upb/message/map.h" -#include "upb/text/encode.h" - -#include -#include -#include "src/core/lib/channel/status_util.h" -#include "src/core/lib/config/core_configuration.h" -#include "src/core/lib/debug/trace.h" -#include "src/core/lib/gprpp/env.h" #include "src/core/lib/gprpp/match.h" -#include "src/core/lib/gprpp/ref_counted_ptr.h" -#include "src/core/lib/gprpp/time.h" #include "src/core/lib/matchers/matchers.h" -#include "src/core/load_balancing/lb_policy_registry.h" -#include "src/core/util/json/json.h" -#include "src/core/util/json/json_writer.h" -#include "src/core/util/string.h" -#include "src/core/util/upb_utils.h" -#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" -#include "src/core/xds/grpc/xds_common_types.h" -#include "src/core/xds/grpc/xds_http_filters.h" -#include "src/core/xds/grpc/xds_routing.h" -#include "src/core/xds/xds_client/xds_resource_type.h" namespace grpc_core { -// TODO(apolcyn): remove this flag by the 1.58 release -bool XdsRlsEnabled() { - auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB"); - if (!value.has_value()) return true; - bool parsed_value; - bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); - return parse_succeeded && parsed_value; -} - // // XdsRouteConfigResource::RetryPolicy // @@ -335,863 +279,4 @@ std::string XdsRouteConfigResource::ToString() const { return absl::StrJoin(parts, ""); } -namespace { - -XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RouteConfiguration* route_config, - ValidationErrors* errors) { - XdsRouteConfigResource::ClusterSpecifierPluginMap - cluster_specifier_plugin_map; - const auto& cluster_specifier_plugin_registry = - static_cast(context.client->bootstrap()) - .cluster_specifier_plugin_registry(); - size_t num_cluster_specifier_plugins; - const envoy_config_route_v3_ClusterSpecifierPlugin* const* - cluster_specifier_plugin = - envoy_config_route_v3_RouteConfiguration_cluster_specifier_plugins( - route_config, &num_cluster_specifier_plugins); - for (size_t i = 0; i < num_cluster_specifier_plugins; ++i) { - bool is_optional = envoy_config_route_v3_ClusterSpecifierPlugin_is_optional( - cluster_specifier_plugin[i]); - ValidationErrors::ScopedField field( - errors, absl::StrCat(".cluster_specifier_plugins[", i, "].extension")); - const envoy_config_core_v3_TypedExtensionConfig* typed_extension_config = - envoy_config_route_v3_ClusterSpecifierPlugin_extension( - cluster_specifier_plugin[i]); - if (typed_extension_config == nullptr) { - errors->AddError("field not present"); - continue; - } - std::string name = UpbStringToStdString( - envoy_config_core_v3_TypedExtensionConfig_name(typed_extension_config)); - if (cluster_specifier_plugin_map.find(name) != - cluster_specifier_plugin_map.end()) { - ValidationErrors::ScopedField field(errors, ".name"); - errors->AddError(absl::StrCat("duplicate name \"", name, "\"")); - } else { - // Add a sentinel entry in case we encounter an error later, just so we - // don't generate duplicate errors for each route that uses this plugin. - cluster_specifier_plugin_map[name] = ""; - } - ValidationErrors::ScopedField field2(errors, ".typed_config"); - const google_protobuf_Any* any = - envoy_config_core_v3_TypedExtensionConfig_typed_config( - typed_extension_config); - auto extension = ExtractXdsExtension(context, any, errors); - if (!extension.has_value()) continue; - const XdsClusterSpecifierPluginImpl* cluster_specifier_plugin_impl = - cluster_specifier_plugin_registry.GetPluginForType(extension->type); - if (cluster_specifier_plugin_impl == nullptr) { - if (is_optional) { - // Empty string indicates an optional plugin. - // This is used later when validating routes, and since we will skip - // any routes that refer to this plugin, we won't wind up including - // this plugin in the resource that we return to the watcher. - cluster_specifier_plugin_map[std::move(name)] = ""; - } else { - // Not optional, report error. - errors->AddError("unsupported ClusterSpecifierPlugin type"); - } - continue; - } - const size_t original_error_size = errors->size(); - Json lb_policy_config = - cluster_specifier_plugin_impl->GenerateLoadBalancingPolicyConfig( - std::move(*extension), context.arena, context.symtab, errors); - if (errors->size() != original_error_size) continue; - auto config = - CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig( - lb_policy_config); - if (!config.ok()) { - errors->AddError(absl::StrCat( - "ClusterSpecifierPlugin returned invalid LB policy config: ", - config.status().message())); - } else { - cluster_specifier_plugin_map[std::move(name)] = - JsonDump(lb_policy_config); - } - } - return cluster_specifier_plugin_map; -} - -absl::optional RoutePathMatchParse( - const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) { - bool case_sensitive = true; - auto* case_sensitive_ptr = - envoy_config_route_v3_RouteMatch_case_sensitive(match); - 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)); - // For any prefix that cannot match a path of the form "/service/method", - // ignore the route. - if (!prefix.empty()) { - // Does not start with a slash. - if (prefix[0] != '/') return absl::nullopt; - std::vector prefix_elements = - absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2)); - // More than 2 slashes. - if (prefix_elements.size() > 2) return absl::nullopt; - // Two consecutive slashes. - if (prefix_elements.size() == 2 && prefix_elements[0].empty()) { - return absl::nullopt; - } - } - 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)); - // For any path not of the form "/service/method", ignore the route. - // Empty path. - if (path.empty()) return absl::nullopt; - // Does not start with a slash. - if (path[0] != '/') return absl::nullopt; - std::vector path_elements = - absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2)); - // Number of slashes does not equal 2. - if (path_elements.size() != 2) return absl::nullopt; - // Empty service name. - if (path_elements[0].empty()) return absl::nullopt; - // Empty method name. - if (path_elements[1].empty()) return absl::nullopt; - 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); - CHECK_NE(regex_matcher, nullptr); - type = StringMatcher::Type::kSafeRegex; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - } else { - errors->AddError("invalid path specifier"); - return absl::nullopt; - } - absl::StatusOr string_matcher = - StringMatcher::Create(type, match_string, case_sensitive); - if (!string_matcher.ok()) { - errors->AddError(absl::StrCat("error creating path matcher: ", - string_matcher.status().message())); - return absl::nullopt; - } - return std::move(*string_matcher); -} - -void RouteHeaderMatchersParse(const envoy_config_route_v3_RouteMatch* match, - XdsRouteConfigResource::Route* route, - ValidationErrors* errors) { - 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) { - ValidationErrors::ScopedField field(errors, - absl::StrCat(".headers[", i, "]")); - const envoy_config_route_v3_HeaderMatcher* header = headers[i]; - CHECK_NE(header, nullptr); - 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; - bool case_sensitive = true; - if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) { - type = HeaderMatcher::Type::kExact; - match_string = UpbStringToStdString( - envoy_config_route_v3_HeaderMatcher_exact_match(header)); - } else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) { - 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)) { - 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 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); - CHECK_NE(regex_matcher, nullptr); - type = HeaderMatcher::Type::kSafeRegex; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - } else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) { - type = HeaderMatcher::Type::kRange; - const envoy_type_v3_Int64Range* range_matcher = - envoy_config_route_v3_HeaderMatcher_range_match(header); - CHECK_NE(range_matcher, nullptr); - 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)) { - type = HeaderMatcher::Type::kPresent; - present_match = envoy_config_route_v3_HeaderMatcher_present_match(header); - } else if (envoy_config_route_v3_HeaderMatcher_has_string_match(header)) { - ValidationErrors::ScopedField field(errors, ".string_match"); - const auto* matcher = - envoy_config_route_v3_HeaderMatcher_string_match(header); - CHECK_NE(matcher, nullptr); - if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) { - type = HeaderMatcher::Type::kExact; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_StringMatcher_exact(matcher)); - } else if (envoy_type_matcher_v3_StringMatcher_has_prefix(matcher)) { - type = HeaderMatcher::Type::kPrefix; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_StringMatcher_prefix(matcher)); - } else if (envoy_type_matcher_v3_StringMatcher_has_suffix(matcher)) { - type = HeaderMatcher::Type::kSuffix; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_StringMatcher_suffix(matcher)); - } else if (envoy_type_matcher_v3_StringMatcher_has_contains(matcher)) { - type = HeaderMatcher::Type::kContains; - match_string = UpbStringToStdString( - envoy_type_matcher_v3_StringMatcher_contains(matcher)); - } else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(matcher)) { - type = HeaderMatcher::Type::kSafeRegex; - const auto* regex_matcher = - envoy_type_matcher_v3_StringMatcher_safe_regex(matcher); - CHECK_NE(regex_matcher, nullptr); - match_string = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); - } else { - errors->AddError("invalid string matcher"); - continue; - } - case_sensitive = - !envoy_type_matcher_v3_StringMatcher_ignore_case(matcher); - } else { - errors->AddError("invalid header matcher"); - continue; - } - bool invert_match = - envoy_config_route_v3_HeaderMatcher_invert_match(header); - absl::StatusOr header_matcher = - HeaderMatcher::Create(name, type, match_string, range_start, range_end, - present_match, invert_match, case_sensitive); - if (!header_matcher.ok()) { - errors->AddError(absl::StrCat("cannot create header matcher: ", - header_matcher.status().message())); - } else { - route->matchers.header_matchers.emplace_back(std::move(*header_matcher)); - } - } -} - -void RouteRuntimeFractionParse(const envoy_config_route_v3_RouteMatch* match, - XdsRouteConfigResource::Route* route, - ValidationErrors* errors) { - const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction = - envoy_config_route_v3_RouteMatch_runtime_fraction(match); - if (runtime_fraction != nullptr) { - const envoy_type_v3_FractionalPercent* fraction = - envoy_config_core_v3_RuntimeFractionalPercent_default_value( - runtime_fraction); - if (fraction != nullptr) { - uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction); - const uint32_t denominator = - envoy_type_v3_FractionalPercent_denominator(fraction); - // Normalize to million. - switch (denominator) { - case envoy_type_v3_FractionalPercent_HUNDRED: - numerator *= 10000; - break; - case envoy_type_v3_FractionalPercent_TEN_THOUSAND: - numerator *= 100; - break; - case envoy_type_v3_FractionalPercent_MILLION: - break; - default: { - ValidationErrors::ScopedField field( - errors, ".runtime_fraction.default_value.denominator"); - errors->AddError("unknown denominator type"); - return; - } - } - route->matchers.fraction_per_million = numerator; - } - } -} - -template -XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig( - const XdsResourceType::DecodeContext& context, const ParentType* parent, - const EntryType* (*entry_func)(const ParentType*, size_t*), - upb_StringView (*key_func)(const EntryType*), - const google_protobuf_Any* (*value_func)(const EntryType*), - ValidationErrors* errors) { - XdsRouteConfigResource::TypedPerFilterConfig typed_per_filter_config; - size_t filter_it = kUpb_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)); - ValidationErrors::ScopedField field(errors, absl::StrCat("[", key, "]")); - if (key.empty()) errors->AddError("filter name must be non-empty"); - const google_protobuf_Any* any = value_func(filter_entry); - auto extension = ExtractXdsExtension(context, any, errors); - if (!extension.has_value()) continue; - auto* extension_to_use = &*extension; - absl::optional nested_extension; - bool is_optional = false; - if (extension->type == "envoy.config.route.v3.FilterConfig") { - absl::string_view* serialized_config = - absl::get_if(&extension->value); - if (serialized_config == nullptr) { - errors->AddError("could not parse FilterConfig"); - continue; - } - const auto* filter_config = envoy_config_route_v3_FilterConfig_parse( - serialized_config->data(), serialized_config->size(), context.arena); - if (filter_config == nullptr) { - errors->AddError("could not parse FilterConfig"); - continue; - } - is_optional = - envoy_config_route_v3_FilterConfig_is_optional(filter_config); - any = envoy_config_route_v3_FilterConfig_config(filter_config); - extension->validation_fields.emplace_back(errors, ".config"); - nested_extension = ExtractXdsExtension(context, any, errors); - if (!nested_extension.has_value()) continue; - extension_to_use = &*nested_extension; - } - const auto& http_filter_registry = - static_cast(context.client->bootstrap()) - .http_filter_registry(); - const XdsHttpFilterImpl* filter_impl = - http_filter_registry.GetFilterForType(extension_to_use->type); - if (filter_impl == nullptr) { - if (!is_optional) errors->AddError("unsupported filter type"); - continue; - } - absl::optional filter_config = - filter_impl->GenerateFilterConfigOverride( - context, std::move(*extension_to_use), errors); - if (filter_config.has_value()) { - typed_per_filter_config[std::string(key)] = std::move(*filter_config); - } - } - return typed_per_filter_config; -} - -XdsRouteConfigResource::RetryPolicy RetryPolicyParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RetryPolicy* retry_policy_proto, - ValidationErrors* errors) { - XdsRouteConfigResource::RetryPolicy retry_policy; - auto retry_on = UpbStringToStdString( - envoy_config_route_v3_RetryPolicy_retry_on(retry_policy_proto)); - std::vector codes = absl::StrSplit(retry_on, ','); - for (const auto& code : codes) { - if (code == "cancelled") { - retry_policy.retry_on.Add(GRPC_STATUS_CANCELLED); - } else if (code == "deadline-exceeded") { - retry_policy.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED); - } else if (code == "internal") { - retry_policy.retry_on.Add(GRPC_STATUS_INTERNAL); - } else if (code == "resource-exhausted") { - retry_policy.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED); - } else if (code == "unavailable") { - retry_policy.retry_on.Add(GRPC_STATUS_UNAVAILABLE); - } else { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(INFO) << "Unsupported retry_on policy " << code; - } - } - } - const google_protobuf_UInt32Value* num_retries = - envoy_config_route_v3_RetryPolicy_num_retries(retry_policy_proto); - if (num_retries != nullptr) { - uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries); - if (num_retries_value == 0) { - ValidationErrors::ScopedField field(errors, ".num_retries"); - errors->AddError("must be greater than 0"); - } else { - retry_policy.num_retries = num_retries_value; - } - } else { - retry_policy.num_retries = 1; - } - const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff = - envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy_proto); - if (backoff != nullptr) { - ValidationErrors::ScopedField field(errors, ".retry_back_off"); - { - ValidationErrors::ScopedField field(errors, ".base_interval"); - const google_protobuf_Duration* base_interval = - envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff); - if (base_interval == nullptr) { - errors->AddError("field not present"); - } else { - retry_policy.retry_back_off.base_interval = - ParseDuration(base_interval, errors); - } - } - { - ValidationErrors::ScopedField field(errors, ".max_interval"); - const google_protobuf_Duration* max_interval = - envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff); - Duration max; - if (max_interval != nullptr) { - max = ParseDuration(max_interval, errors); - } else { - // if max interval is not set, it is 10x the base. - max = 10 * retry_policy.retry_back_off.base_interval; - } - retry_policy.retry_back_off.max_interval = max; - } - } else { - retry_policy.retry_back_off.base_interval = Duration::Milliseconds(25); - retry_policy.retry_back_off.max_interval = Duration::Milliseconds(250); - } - return retry_policy; -} - -absl::optional RouteActionParse( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RouteAction* route_action_proto, - const std::map& - cluster_specifier_plugin_map, - ValidationErrors* errors) { - XdsRouteConfigResource::Route::RouteAction route_action; - // grpc_timeout_header_max or max_stream_duration - const auto* max_stream_duration = - envoy_config_route_v3_RouteAction_max_stream_duration(route_action_proto); - if (max_stream_duration != nullptr) { - ValidationErrors::ScopedField field(errors, ".max_stream_duration"); - const google_protobuf_Duration* duration = - envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max( - max_stream_duration); - if (duration != nullptr) { - ValidationErrors::ScopedField field(errors, ".grpc_timeout_header_max"); - route_action.max_stream_duration = ParseDuration(duration, errors); - } else { - duration = - envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration( - max_stream_duration); - if (duration != nullptr) { - ValidationErrors::ScopedField field(errors, ".max_stream_duration"); - route_action.max_stream_duration = ParseDuration(duration, errors); - } - } - } - // hash_policy - size_t size = 0; - const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies = - envoy_config_route_v3_RouteAction_hash_policy(route_action_proto, &size); - for (size_t i = 0; i < size; ++i) { - ValidationErrors::ScopedField field(errors, - absl::StrCat(".hash_policy[", i, "]")); - const auto* hash_policy = hash_policies[i]; - XdsRouteConfigResource::Route::RouteAction::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) { - // header - ValidationErrors::ScopedField field(errors, ".header"); - XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header - header_policy; - header_policy.header_name = UpbStringToStdString( - envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name( - header)); - if (header_policy.header_name.empty()) { - ValidationErrors::ScopedField field(errors, ".header_name"); - errors->AddError("must be non-empty"); - } - // regex_rewrite - const auto* regex_rewrite = - envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite( - header); - if (regex_rewrite != nullptr) { - ValidationErrors::ScopedField field(errors, ".regex_rewrite.pattern"); - const auto* pattern = - envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern( - regex_rewrite); - if (pattern == nullptr) { - errors->AddError("field not present"); - continue; - } - ValidationErrors::ScopedField field2(errors, ".regex"); - std::string regex = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatcher_regex(pattern)); - if (regex.empty()) { - errors->AddError("field not present"); - continue; - } - RE2::Options options; - header_policy.regex = std::make_unique(regex, options); - if (!header_policy.regex->ok()) { - errors->AddError(absl::StrCat("errors compiling regex: ", - header_policy.regex->error())); - continue; - } - header_policy.regex_substitution = UpbStringToStdString( - envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution( - regex_rewrite)); - } - policy.policy = std::move(header_policy); - } else if ((filter_state = - envoy_config_route_v3_RouteAction_HashPolicy_filter_state( - hash_policy)) != nullptr) { - // filter_state - std::string key = UpbStringToStdString( - envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key( - filter_state)); - if (key != "io.grpc.channel_id") continue; - policy.policy = - XdsRouteConfigResource::Route::RouteAction::HashPolicy::ChannelId(); - } else { - // Unsupported hash policy type, ignore it. - continue; - } - route_action.hash_policies.emplace_back(std::move(policy)); - } - // Get retry policy - const envoy_config_route_v3_RetryPolicy* retry_policy = - envoy_config_route_v3_RouteAction_retry_policy(route_action_proto); - if (retry_policy != nullptr) { - ValidationErrors::ScopedField field(errors, ".retry_policy"); - route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors); - } - // Parse cluster specifier, which is one of several options. - if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) { - // Cluster name. - std::string cluster_name = UpbStringToStdString( - envoy_config_route_v3_RouteAction_cluster(route_action_proto)); - if (cluster_name.empty()) { - ValidationErrors::ScopedField field(errors, ".cluster"); - errors->AddError("must be non-empty"); - } - route_action.action = - XdsRouteConfigResource::Route::RouteAction::ClusterName{ - std::move(cluster_name)}; - } else if (envoy_config_route_v3_RouteAction_has_weighted_clusters( - route_action_proto)) { - // WeightedClusters. - ValidationErrors::ScopedField field(errors, ".weighted_clusters"); - const envoy_config_route_v3_WeightedCluster* weighted_clusters_proto = - envoy_config_route_v3_RouteAction_weighted_clusters(route_action_proto); - CHECK_NE(weighted_clusters_proto, nullptr); - std::vector - action_weighted_clusters; - uint64_t total_weight = 0; - size_t clusters_size; - const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters = - envoy_config_route_v3_WeightedCluster_clusters(weighted_clusters_proto, - &clusters_size); - for (size_t i = 0; i < clusters_size; ++i) { - ValidationErrors::ScopedField field(errors, - absl::StrCat(".clusters[", i, "]")); - const auto* cluster_proto = clusters[i]; - XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster; - // typed_per_filter_config - { - ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); - cluster.typed_per_filter_config = ParseTypedPerFilterConfig< - envoy_config_route_v3_WeightedCluster_ClusterWeight, - envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>( - context, cluster_proto, - 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, - errors); - } - // name - cluster.name = UpbStringToStdString( - envoy_config_route_v3_WeightedCluster_ClusterWeight_name( - cluster_proto)); - if (cluster.name.empty()) { - ValidationErrors::ScopedField field(errors, ".name"); - errors->AddError("must be non-empty"); - } - // weight - const google_protobuf_UInt32Value* weight_proto = - envoy_config_route_v3_WeightedCluster_ClusterWeight_weight( - cluster_proto); - if (weight_proto == nullptr) { - ValidationErrors::ScopedField field(errors, ".weight"); - errors->AddError("field not present"); - } else { - cluster.weight = google_protobuf_UInt32Value_value(weight_proto); - if (cluster.weight == 0) continue; - total_weight += cluster.weight; - } - // Add entry to WeightedClusters. - action_weighted_clusters.emplace_back(std::move(cluster)); - } - if (action_weighted_clusters.empty()) { - errors->AddError("no valid clusters specified"); - } else if (total_weight > std::numeric_limits::max()) { - errors->AddError("sum of cluster weights exceeds uint32 max"); - } - route_action.action = std::move(action_weighted_clusters); - } else if (XdsRlsEnabled() && - envoy_config_route_v3_RouteAction_has_cluster_specifier_plugin( - route_action_proto)) { - // ClusterSpecifierPlugin - ValidationErrors::ScopedField field(errors, ".cluster_specifier_plugin"); - std::string plugin_name = UpbStringToStdString( - envoy_config_route_v3_RouteAction_cluster_specifier_plugin( - route_action_proto)); - if (plugin_name.empty()) { - errors->AddError("must be non-empty"); - return absl::nullopt; - } - auto it = cluster_specifier_plugin_map.find(plugin_name); - if (it == cluster_specifier_plugin_map.end()) { - errors->AddError(absl::StrCat("unknown cluster specifier plugin name \"", - plugin_name, "\"")); - } else { - // If the cluster specifier config is empty, that means that the - // plugin was unsupported but optional. In that case, skip this route. - if (it->second.empty()) return absl::nullopt; - } - route_action.action = - XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName{ - std::move(plugin_name)}; - } else { - // Not a supported cluster specifier, so ignore this route. - return absl::nullopt; - } - return route_action; -} - -absl::optional ParseRoute( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_Route* route_proto, - const absl::optional& - virtual_host_retry_policy, - const XdsRouteConfigResource::ClusterSpecifierPluginMap& - cluster_specifier_plugin_map, - std::set* cluster_specifier_plugins_not_seen, - ValidationErrors* errors) { - XdsRouteConfigResource::Route route; - // Parse route match. - { - ValidationErrors::ScopedField field(errors, ".match"); - const auto* match = envoy_config_route_v3_Route_match(route_proto); - if (match == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - // Skip routes with query_parameters set. - size_t query_parameters_size; - static_cast(envoy_config_route_v3_RouteMatch_query_parameters( - match, &query_parameters_size)); - if (query_parameters_size > 0) return absl::nullopt; - // Parse matchers. - auto path_matcher = RoutePathMatchParse(match, errors); - if (!path_matcher.has_value()) return absl::nullopt; - route.matchers.path_matcher = std::move(*path_matcher); - RouteHeaderMatchersParse(match, &route, errors); - RouteRuntimeFractionParse(match, &route, errors); - } - // Parse route action. - const envoy_config_route_v3_RouteAction* route_action_proto = - envoy_config_route_v3_Route_route(route_proto); - if (route_action_proto != nullptr) { - ValidationErrors::ScopedField field(errors, ".route"); - auto route_action = RouteActionParse(context, route_action_proto, - cluster_specifier_plugin_map, errors); - if (!route_action.has_value()) return absl::nullopt; - // If the route does not have a retry policy but the vhost does, - // use the vhost retry policy for this route. - if (!route_action->retry_policy.has_value()) { - route_action->retry_policy = virtual_host_retry_policy; - } - // Mark off plugins used in route action. - auto* cluster_specifier_action = absl::get_if< - XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName>( - &route_action->action); - if (cluster_specifier_action != nullptr) { - cluster_specifier_plugins_not_seen->erase( - cluster_specifier_action->cluster_specifier_plugin_name); - } - route.action = std::move(*route_action); - } else if (envoy_config_route_v3_Route_has_non_forwarding_action( - route_proto)) { - route.action = XdsRouteConfigResource::Route::NonForwardingAction(); - } else { - // Leave route.action initialized to UnknownAction (its default). - } - // Parse typed_per_filter_config. - { - ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); - route.typed_per_filter_config = ParseTypedPerFilterConfig< - envoy_config_route_v3_Route, - envoy_config_route_v3_Route_TypedPerFilterConfigEntry>( - context, route_proto, - envoy_config_route_v3_Route_typed_per_filter_config_next, - envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key, - envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, errors); - } - return route; -} - -} // namespace - -std::shared_ptr XdsRouteConfigResource::Parse( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RouteConfiguration* route_config, - ValidationErrors* errors) { - auto rds_update = std::make_shared(); - // Get the cluster spcifier plugin map. - if (XdsRlsEnabled()) { - rds_update->cluster_specifier_plugin_map = - ClusterSpecifierPluginParse(context, route_config, errors); - } - // Build a set of configured cluster_specifier_plugin names to make sure - // each is actually referenced by a route action. - std::set cluster_specifier_plugins_not_seen; - for (auto& plugin : rds_update->cluster_specifier_plugin_map) { - cluster_specifier_plugins_not_seen.emplace(plugin.first); - } - // Get the virtual hosts. - size_t num_virtual_hosts; - const envoy_config_route_v3_VirtualHost* const* virtual_hosts = - envoy_config_route_v3_RouteConfiguration_virtual_hosts( - route_config, &num_virtual_hosts); - for (size_t i = 0; i < num_virtual_hosts; ++i) { - ValidationErrors::ScopedField field( - errors, absl::StrCat(".virtual_hosts[", i, "]")); - rds_update->virtual_hosts.emplace_back(); - XdsRouteConfigResource::VirtualHost& vhost = - rds_update->virtual_hosts.back(); - // Parse domains. - size_t domain_size; - upb_StringView const* domains = envoy_config_route_v3_VirtualHost_domains( - virtual_hosts[i], &domain_size); - for (size_t j = 0; j < domain_size; ++j) { - std::string domain_pattern = UpbStringToStdString(domains[j]); - if (!XdsRouting::IsValidDomainPattern(domain_pattern)) { - ValidationErrors::ScopedField field(errors, - absl::StrCat(".domains[", j, "]")); - errors->AddError( - absl::StrCat("invalid domain pattern \"", domain_pattern, "\"")); - } - vhost.domains.emplace_back(std::move(domain_pattern)); - } - if (vhost.domains.empty()) { - ValidationErrors::ScopedField field(errors, ".domains"); - errors->AddError("must be non-empty"); - } - // Parse typed_per_filter_config. - { - ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); - vhost.typed_per_filter_config = 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, - errors); - } - // Parse retry policy. - absl::optional - virtual_host_retry_policy; - const envoy_config_route_v3_RetryPolicy* retry_policy = - envoy_config_route_v3_VirtualHost_retry_policy(virtual_hosts[i]); - if (retry_policy != nullptr) { - ValidationErrors::ScopedField field(errors, ".retry_policy"); - virtual_host_retry_policy = - RetryPolicyParse(context, retry_policy, errors); - } - // Parse routes. - ValidationErrors::ScopedField field2(errors, ".routes"); - size_t num_routes; - const envoy_config_route_v3_Route* const* routes = - envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes); - for (size_t j = 0; j < num_routes; ++j) { - ValidationErrors::ScopedField field(errors, absl::StrCat("[", j, "]")); - auto route = ParseRoute(context, routes[j], virtual_host_retry_policy, - rds_update->cluster_specifier_plugin_map, - &cluster_specifier_plugins_not_seen, errors); - if (route.has_value()) vhost.routes.emplace_back(std::move(*route)); - } - } - // For cluster specifier plugins that were not used in any route action, - // delete them from the update, since they will never be used. - for (auto& unused_plugin : cluster_specifier_plugins_not_seen) { - rds_update->cluster_specifier_plugin_map.erase(std::string(unused_plugin)); - } - return rds_update; -} - -// -// XdsRouteConfigResourceType -// - -namespace { - -void MaybeLogRouteConfiguration( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RouteConfiguration* route_config) { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { - const upb_MessageDef* msg_type = - envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab); - char buf[10240]; - upb_TextEncode(reinterpret_cast(route_config), msg_type, - nullptr, 0, buf, sizeof(buf)); - VLOG(2) << "[xds_client " << context.client - << "] RouteConfiguration: " << buf; - } -} - -} // namespace - -XdsResourceType::DecodeResult XdsRouteConfigResourceType::Decode( - const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const { - DecodeResult result; - // Parse serialized proto. - auto* resource = envoy_config_route_v3_RouteConfiguration_parse( - serialized_resource.data(), serialized_resource.size(), context.arena); - if (resource == nullptr) { - result.resource = - absl::InvalidArgumentError("Can't parse RouteConfiguration resource."); - return result; - } - MaybeLogRouteConfiguration(context, resource); - // Validate resource. - result.name = UpbStringToStdString( - envoy_config_route_v3_RouteConfiguration_name(resource)); - ValidationErrors errors; - auto rds_update = XdsRouteConfigResource::Parse(context, resource, &errors); - if (!errors.ok()) { - absl::Status status = - errors.status(absl::StatusCode::kInvalidArgument, - "errors validating RouteConfiguration resource"); - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(ERROR) << "[xds_client " << context.client - << "] invalid RouteConfiguration " << *result.name << ": " - << status; - } - result.resource = std::move(status); - } else { - if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { - LOG(INFO) << "[xds_client " << context.client - << "] parsed RouteConfiguration " << *result.name << ": " - << rds_update->ToString(); - } - result.resource = std::move(rds_update); - } - return result; -} - } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_route_config.h b/src/core/xds/grpc/xds_route_config.h index f6d802aa91e..6928b01c93a 100644 --- a/src/core/xds/grpc/xds_route_config.h +++ b/src/core/xds/grpc/xds_route_config.h @@ -25,24 +25,14 @@ #include #include -#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" -#include "envoy/config/route/v3/route.upb.h" -#include "envoy/config/route/v3/route.upbdefs.h" #include "re2/re2.h" -#include "upb/reflection/def.h" - -#include #include "src/core/lib/channel/status_util.h" #include "src/core/lib/gprpp/time.h" -#include "src/core/lib/gprpp/validation_errors.h" #include "src/core/lib/matchers/matchers.h" -#include "src/core/xds/grpc/xds_bootstrap_grpc.h" -#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" -#include "src/core/xds/grpc/xds_http_filters.h" -#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/grpc/xds_http_filter.h" #include "src/core/xds/xds_client/xds_resource_type.h" #include "src/core/xds/xds_client/xds_resource_type_impl.h" @@ -220,32 +210,6 @@ struct XdsRouteConfigResource : public XdsResourceType::ResourceData { cluster_specifier_plugin_map == other.cluster_specifier_plugin_map; } std::string ToString() const; - - static std::shared_ptr Parse( - const XdsResourceType::DecodeContext& context, - const envoy_config_route_v3_RouteConfiguration* route_config, - ValidationErrors* errors); -}; - -class XdsRouteConfigResourceType final - : public XdsResourceTypeImpl { - public: - absl::string_view type_url() const override { - return "envoy.config.route.v3.RouteConfiguration"; - } - - DecodeResult Decode(const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource) const override; - - void InitUpbSymtab(XdsClient* xds_client, - upb_DefPool* symtab) const override { - envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab); - const auto& cluster_specifier_plugin_registry = - static_cast(xds_client->bootstrap()) - .cluster_specifier_plugin_registry(); - cluster_specifier_plugin_registry.PopulateSymtab(symtab); - } }; } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_route_config_parser.cc b/src/core/xds/grpc/xds_route_config_parser.cc new file mode 100644 index 00000000000..bf140b73adb --- /dev/null +++ b/src/core/xds/grpc/xds_route_config_parser.cc @@ -0,0 +1,956 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_route_config_parser.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "envoy/config/core/v3/base.upb.h" +#include "envoy/config/core/v3/extension.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/type/matcher/v3/regex.upb.h" +#include "envoy/type/matcher/v3/string.upb.h" +#include "envoy/type/v3/percent.upb.h" +#include "envoy/type/v3/range.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/wrappers.upb.h" +#include "re2/re2.h" +#include "upb/base/string_view.h" +#include "upb/message/map.h" +#include "upb/text/encode.h" + +#include +#include + +#include "src/core/lib/channel/status_util.h" +#include "src/core/lib/config/core_configuration.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/env.h" +#include "src/core/lib/gprpp/match.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/matchers/matchers.h" +#include "src/core/load_balancing/lb_policy_registry.h" +#include "src/core/util/json/json.h" +#include "src/core/util/json/json_writer.h" +#include "src/core/util/string.h" +#include "src/core/util/upb_utils.h" +#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" +#include "src/core/xds/grpc/xds_routing.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +namespace grpc_core { + +// TODO(apolcyn): remove this flag by the 1.58 release +bool XdsRlsEnabled() { + auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB"); + if (!value.has_value()) return true; + bool parsed_value; + bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); + return parse_succeeded && parsed_value; +} + +// +// XdsRouteConfigResourceParse() +// + +namespace { + +XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RouteConfiguration* route_config, + ValidationErrors* errors) { + XdsRouteConfigResource::ClusterSpecifierPluginMap + cluster_specifier_plugin_map; + const auto& cluster_specifier_plugin_registry = + static_cast(context.client->bootstrap()) + .cluster_specifier_plugin_registry(); + size_t num_cluster_specifier_plugins; + const envoy_config_route_v3_ClusterSpecifierPlugin* const* + cluster_specifier_plugin = + envoy_config_route_v3_RouteConfiguration_cluster_specifier_plugins( + route_config, &num_cluster_specifier_plugins); + for (size_t i = 0; i < num_cluster_specifier_plugins; ++i) { + bool is_optional = envoy_config_route_v3_ClusterSpecifierPlugin_is_optional( + cluster_specifier_plugin[i]); + ValidationErrors::ScopedField field( + errors, absl::StrCat(".cluster_specifier_plugins[", i, "].extension")); + const envoy_config_core_v3_TypedExtensionConfig* typed_extension_config = + envoy_config_route_v3_ClusterSpecifierPlugin_extension( + cluster_specifier_plugin[i]); + if (typed_extension_config == nullptr) { + errors->AddError("field not present"); + continue; + } + std::string name = UpbStringToStdString( + envoy_config_core_v3_TypedExtensionConfig_name(typed_extension_config)); + if (cluster_specifier_plugin_map.find(name) != + cluster_specifier_plugin_map.end()) { + ValidationErrors::ScopedField field(errors, ".name"); + errors->AddError(absl::StrCat("duplicate name \"", name, "\"")); + } else { + // Add a sentinel entry in case we encounter an error later, just so we + // don't generate duplicate errors for each route that uses this plugin. + cluster_specifier_plugin_map[name] = ""; + } + ValidationErrors::ScopedField field2(errors, ".typed_config"); + const google_protobuf_Any* any = + envoy_config_core_v3_TypedExtensionConfig_typed_config( + typed_extension_config); + auto extension = ExtractXdsExtension(context, any, errors); + if (!extension.has_value()) continue; + const XdsClusterSpecifierPluginImpl* cluster_specifier_plugin_impl = + cluster_specifier_plugin_registry.GetPluginForType(extension->type); + if (cluster_specifier_plugin_impl == nullptr) { + if (is_optional) { + // Empty string indicates an optional plugin. + // This is used later when validating routes, and since we will skip + // any routes that refer to this plugin, we won't wind up including + // this plugin in the resource that we return to the watcher. + cluster_specifier_plugin_map[std::move(name)] = ""; + } else { + // Not optional, report error. + errors->AddError("unsupported ClusterSpecifierPlugin type"); + } + continue; + } + const size_t original_error_size = errors->size(); + Json lb_policy_config = + cluster_specifier_plugin_impl->GenerateLoadBalancingPolicyConfig( + std::move(*extension), context.arena, context.symtab, errors); + if (errors->size() != original_error_size) continue; + auto config = + CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig( + lb_policy_config); + if (!config.ok()) { + errors->AddError(absl::StrCat( + "ClusterSpecifierPlugin returned invalid LB policy config: ", + config.status().message())); + } else { + cluster_specifier_plugin_map[std::move(name)] = + JsonDump(lb_policy_config); + } + } + return cluster_specifier_plugin_map; +} + +absl::optional RoutePathMatchParse( + const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) { + bool case_sensitive = true; + auto* case_sensitive_ptr = + envoy_config_route_v3_RouteMatch_case_sensitive(match); + 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)); + // For any prefix that cannot match a path of the form "/service/method", + // ignore the route. + if (!prefix.empty()) { + // Does not start with a slash. + if (prefix[0] != '/') return absl::nullopt; + std::vector prefix_elements = + absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2)); + // More than 2 slashes. + if (prefix_elements.size() > 2) return absl::nullopt; + // Two consecutive slashes. + if (prefix_elements.size() == 2 && prefix_elements[0].empty()) { + return absl::nullopt; + } + } + 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)); + // For any path not of the form "/service/method", ignore the route. + // Empty path. + if (path.empty()) return absl::nullopt; + // Does not start with a slash. + if (path[0] != '/') return absl::nullopt; + std::vector path_elements = + absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2)); + // Number of slashes does not equal 2. + if (path_elements.size() != 2) return absl::nullopt; + // Empty service name. + if (path_elements[0].empty()) return absl::nullopt; + // Empty method name. + if (path_elements[1].empty()) return absl::nullopt; + 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); + CHECK_NE(regex_matcher, nullptr); + type = StringMatcher::Type::kSafeRegex; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); + } else { + errors->AddError("invalid path specifier"); + return absl::nullopt; + } + absl::StatusOr string_matcher = + StringMatcher::Create(type, match_string, case_sensitive); + if (!string_matcher.ok()) { + errors->AddError(absl::StrCat("error creating path matcher: ", + string_matcher.status().message())); + return absl::nullopt; + } + return std::move(*string_matcher); +} + +void RouteHeaderMatchersParse(const envoy_config_route_v3_RouteMatch* match, + XdsRouteConfigResource::Route* route, + ValidationErrors* errors) { + 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) { + ValidationErrors::ScopedField field(errors, + absl::StrCat(".headers[", i, "]")); + const envoy_config_route_v3_HeaderMatcher* header = headers[i]; + CHECK_NE(header, nullptr); + 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; + bool case_sensitive = true; + if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) { + type = HeaderMatcher::Type::kExact; + match_string = UpbStringToStdString( + envoy_config_route_v3_HeaderMatcher_exact_match(header)); + } else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) { + 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)) { + 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 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); + CHECK_NE(regex_matcher, nullptr); + type = HeaderMatcher::Type::kSafeRegex; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); + } else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) { + type = HeaderMatcher::Type::kRange; + const envoy_type_v3_Int64Range* range_matcher = + envoy_config_route_v3_HeaderMatcher_range_match(header); + CHECK_NE(range_matcher, nullptr); + 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)) { + type = HeaderMatcher::Type::kPresent; + present_match = envoy_config_route_v3_HeaderMatcher_present_match(header); + } else if (envoy_config_route_v3_HeaderMatcher_has_string_match(header)) { + ValidationErrors::ScopedField field(errors, ".string_match"); + const auto* matcher = + envoy_config_route_v3_HeaderMatcher_string_match(header); + CHECK_NE(matcher, nullptr); + if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) { + type = HeaderMatcher::Type::kExact; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_StringMatcher_exact(matcher)); + } else if (envoy_type_matcher_v3_StringMatcher_has_prefix(matcher)) { + type = HeaderMatcher::Type::kPrefix; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_StringMatcher_prefix(matcher)); + } else if (envoy_type_matcher_v3_StringMatcher_has_suffix(matcher)) { + type = HeaderMatcher::Type::kSuffix; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_StringMatcher_suffix(matcher)); + } else if (envoy_type_matcher_v3_StringMatcher_has_contains(matcher)) { + type = HeaderMatcher::Type::kContains; + match_string = UpbStringToStdString( + envoy_type_matcher_v3_StringMatcher_contains(matcher)); + } else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(matcher)) { + type = HeaderMatcher::Type::kSafeRegex; + const auto* regex_matcher = + envoy_type_matcher_v3_StringMatcher_safe_regex(matcher); + CHECK_NE(regex_matcher, nullptr); + match_string = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); + } else { + errors->AddError("invalid string matcher"); + continue; + } + case_sensitive = + !envoy_type_matcher_v3_StringMatcher_ignore_case(matcher); + } else { + errors->AddError("invalid header matcher"); + continue; + } + bool invert_match = + envoy_config_route_v3_HeaderMatcher_invert_match(header); + absl::StatusOr header_matcher = + HeaderMatcher::Create(name, type, match_string, range_start, range_end, + present_match, invert_match, case_sensitive); + if (!header_matcher.ok()) { + errors->AddError(absl::StrCat("cannot create header matcher: ", + header_matcher.status().message())); + } else { + route->matchers.header_matchers.emplace_back(std::move(*header_matcher)); + } + } +} + +void RouteRuntimeFractionParse(const envoy_config_route_v3_RouteMatch* match, + XdsRouteConfigResource::Route* route, + ValidationErrors* errors) { + const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction = + envoy_config_route_v3_RouteMatch_runtime_fraction(match); + if (runtime_fraction != nullptr) { + const envoy_type_v3_FractionalPercent* fraction = + envoy_config_core_v3_RuntimeFractionalPercent_default_value( + runtime_fraction); + if (fraction != nullptr) { + uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction); + const uint32_t denominator = + envoy_type_v3_FractionalPercent_denominator(fraction); + // Normalize to million. + switch (denominator) { + case envoy_type_v3_FractionalPercent_HUNDRED: + numerator *= 10000; + break; + case envoy_type_v3_FractionalPercent_TEN_THOUSAND: + numerator *= 100; + break; + case envoy_type_v3_FractionalPercent_MILLION: + break; + default: { + ValidationErrors::ScopedField field( + errors, ".runtime_fraction.default_value.denominator"); + errors->AddError("unknown denominator type"); + return; + } + } + route->matchers.fraction_per_million = numerator; + } + } +} + +template +XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig( + const XdsResourceType::DecodeContext& context, const ParentType* parent, + const EntryType* (*entry_func)(const ParentType*, size_t*), + upb_StringView (*key_func)(const EntryType*), + const google_protobuf_Any* (*value_func)(const EntryType*), + ValidationErrors* errors) { + XdsRouteConfigResource::TypedPerFilterConfig typed_per_filter_config; + size_t filter_it = kUpb_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)); + ValidationErrors::ScopedField field(errors, absl::StrCat("[", key, "]")); + if (key.empty()) errors->AddError("filter name must be non-empty"); + const google_protobuf_Any* any = value_func(filter_entry); + auto extension = ExtractXdsExtension(context, any, errors); + if (!extension.has_value()) continue; + auto* extension_to_use = &*extension; + absl::optional nested_extension; + bool is_optional = false; + if (extension->type == "envoy.config.route.v3.FilterConfig") { + absl::string_view* serialized_config = + absl::get_if(&extension->value); + if (serialized_config == nullptr) { + errors->AddError("could not parse FilterConfig"); + continue; + } + const auto* filter_config = envoy_config_route_v3_FilterConfig_parse( + serialized_config->data(), serialized_config->size(), context.arena); + if (filter_config == nullptr) { + errors->AddError("could not parse FilterConfig"); + continue; + } + is_optional = + envoy_config_route_v3_FilterConfig_is_optional(filter_config); + any = envoy_config_route_v3_FilterConfig_config(filter_config); + extension->validation_fields.emplace_back(errors, ".config"); + nested_extension = ExtractXdsExtension(context, any, errors); + if (!nested_extension.has_value()) continue; + extension_to_use = &*nested_extension; + } + const auto& http_filter_registry = + static_cast(context.client->bootstrap()) + .http_filter_registry(); + const XdsHttpFilterImpl* filter_impl = + http_filter_registry.GetFilterForType(extension_to_use->type); + if (filter_impl == nullptr) { + if (!is_optional) errors->AddError("unsupported filter type"); + continue; + } + absl::optional filter_config = + filter_impl->GenerateFilterConfigOverride( + context, std::move(*extension_to_use), errors); + if (filter_config.has_value()) { + typed_per_filter_config[std::string(key)] = std::move(*filter_config); + } + } + return typed_per_filter_config; +} + +XdsRouteConfigResource::RetryPolicy RetryPolicyParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RetryPolicy* retry_policy_proto, + ValidationErrors* errors) { + XdsRouteConfigResource::RetryPolicy retry_policy; + auto retry_on = UpbStringToStdString( + envoy_config_route_v3_RetryPolicy_retry_on(retry_policy_proto)); + std::vector codes = absl::StrSplit(retry_on, ','); + for (const auto& code : codes) { + if (code == "cancelled") { + retry_policy.retry_on.Add(GRPC_STATUS_CANCELLED); + } else if (code == "deadline-exceeded") { + retry_policy.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED); + } else if (code == "internal") { + retry_policy.retry_on.Add(GRPC_STATUS_INTERNAL); + } else if (code == "resource-exhausted") { + retry_policy.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED); + } else if (code == "unavailable") { + retry_policy.retry_on.Add(GRPC_STATUS_UNAVAILABLE); + } else { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(INFO) << "Unsupported retry_on policy " << code; + } + } + } + const google_protobuf_UInt32Value* num_retries = + envoy_config_route_v3_RetryPolicy_num_retries(retry_policy_proto); + if (num_retries != nullptr) { + uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries); + if (num_retries_value == 0) { + ValidationErrors::ScopedField field(errors, ".num_retries"); + errors->AddError("must be greater than 0"); + } else { + retry_policy.num_retries = num_retries_value; + } + } else { + retry_policy.num_retries = 1; + } + const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff = + envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy_proto); + if (backoff != nullptr) { + ValidationErrors::ScopedField field(errors, ".retry_back_off"); + { + ValidationErrors::ScopedField field(errors, ".base_interval"); + const google_protobuf_Duration* base_interval = + envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff); + if (base_interval == nullptr) { + errors->AddError("field not present"); + } else { + retry_policy.retry_back_off.base_interval = + ParseDuration(base_interval, errors); + } + } + { + ValidationErrors::ScopedField field(errors, ".max_interval"); + const google_protobuf_Duration* max_interval = + envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff); + Duration max; + if (max_interval != nullptr) { + max = ParseDuration(max_interval, errors); + } else { + // if max interval is not set, it is 10x the base. + max = 10 * retry_policy.retry_back_off.base_interval; + } + retry_policy.retry_back_off.max_interval = max; + } + } else { + retry_policy.retry_back_off.base_interval = Duration::Milliseconds(25); + retry_policy.retry_back_off.max_interval = Duration::Milliseconds(250); + } + return retry_policy; +} + +absl::optional RouteActionParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RouteAction* route_action_proto, + const std::map& + cluster_specifier_plugin_map, + ValidationErrors* errors) { + XdsRouteConfigResource::Route::RouteAction route_action; + // grpc_timeout_header_max or max_stream_duration + const auto* max_stream_duration = + envoy_config_route_v3_RouteAction_max_stream_duration(route_action_proto); + if (max_stream_duration != nullptr) { + ValidationErrors::ScopedField field(errors, ".max_stream_duration"); + const google_protobuf_Duration* duration = + envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max( + max_stream_duration); + if (duration != nullptr) { + ValidationErrors::ScopedField field(errors, ".grpc_timeout_header_max"); + route_action.max_stream_duration = ParseDuration(duration, errors); + } else { + duration = + envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration( + max_stream_duration); + if (duration != nullptr) { + ValidationErrors::ScopedField field(errors, ".max_stream_duration"); + route_action.max_stream_duration = ParseDuration(duration, errors); + } + } + } + // hash_policy + size_t size = 0; + const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies = + envoy_config_route_v3_RouteAction_hash_policy(route_action_proto, &size); + for (size_t i = 0; i < size; ++i) { + ValidationErrors::ScopedField field(errors, + absl::StrCat(".hash_policy[", i, "]")); + const auto* hash_policy = hash_policies[i]; + XdsRouteConfigResource::Route::RouteAction::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) { + // header + ValidationErrors::ScopedField field(errors, ".header"); + XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header + header_policy; + header_policy.header_name = UpbStringToStdString( + envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name( + header)); + if (header_policy.header_name.empty()) { + ValidationErrors::ScopedField field(errors, ".header_name"); + errors->AddError("must be non-empty"); + } + // regex_rewrite + const auto* regex_rewrite = + envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite( + header); + if (regex_rewrite != nullptr) { + ValidationErrors::ScopedField field(errors, ".regex_rewrite.pattern"); + const auto* pattern = + envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern( + regex_rewrite); + if (pattern == nullptr) { + errors->AddError("field not present"); + continue; + } + ValidationErrors::ScopedField field2(errors, ".regex"); + std::string regex = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatcher_regex(pattern)); + if (regex.empty()) { + errors->AddError("field not present"); + continue; + } + RE2::Options options; + header_policy.regex = std::make_unique(regex, options); + if (!header_policy.regex->ok()) { + errors->AddError(absl::StrCat("errors compiling regex: ", + header_policy.regex->error())); + continue; + } + header_policy.regex_substitution = UpbStringToStdString( + envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution( + regex_rewrite)); + } + policy.policy = std::move(header_policy); + } else if ((filter_state = + envoy_config_route_v3_RouteAction_HashPolicy_filter_state( + hash_policy)) != nullptr) { + // filter_state + std::string key = UpbStringToStdString( + envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key( + filter_state)); + if (key != "io.grpc.channel_id") continue; + policy.policy = + XdsRouteConfigResource::Route::RouteAction::HashPolicy::ChannelId(); + } else { + // Unsupported hash policy type, ignore it. + continue; + } + route_action.hash_policies.emplace_back(std::move(policy)); + } + // Get retry policy + const envoy_config_route_v3_RetryPolicy* retry_policy = + envoy_config_route_v3_RouteAction_retry_policy(route_action_proto); + if (retry_policy != nullptr) { + ValidationErrors::ScopedField field(errors, ".retry_policy"); + route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors); + } + // Parse cluster specifier, which is one of several options. + if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) { + // Cluster name. + std::string cluster_name = UpbStringToStdString( + envoy_config_route_v3_RouteAction_cluster(route_action_proto)); + if (cluster_name.empty()) { + ValidationErrors::ScopedField field(errors, ".cluster"); + errors->AddError("must be non-empty"); + } + route_action.action = + XdsRouteConfigResource::Route::RouteAction::ClusterName{ + std::move(cluster_name)}; + } else if (envoy_config_route_v3_RouteAction_has_weighted_clusters( + route_action_proto)) { + // WeightedClusters. + ValidationErrors::ScopedField field(errors, ".weighted_clusters"); + const envoy_config_route_v3_WeightedCluster* weighted_clusters_proto = + envoy_config_route_v3_RouteAction_weighted_clusters(route_action_proto); + CHECK_NE(weighted_clusters_proto, nullptr); + std::vector + action_weighted_clusters; + uint64_t total_weight = 0; + size_t clusters_size; + const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters = + envoy_config_route_v3_WeightedCluster_clusters(weighted_clusters_proto, + &clusters_size); + for (size_t i = 0; i < clusters_size; ++i) { + ValidationErrors::ScopedField field(errors, + absl::StrCat(".clusters[", i, "]")); + const auto* cluster_proto = clusters[i]; + XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster; + // typed_per_filter_config + { + ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); + cluster.typed_per_filter_config = ParseTypedPerFilterConfig< + envoy_config_route_v3_WeightedCluster_ClusterWeight, + envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>( + context, cluster_proto, + 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, + errors); + } + // name + cluster.name = UpbStringToStdString( + envoy_config_route_v3_WeightedCluster_ClusterWeight_name( + cluster_proto)); + if (cluster.name.empty()) { + ValidationErrors::ScopedField field(errors, ".name"); + errors->AddError("must be non-empty"); + } + // weight + const google_protobuf_UInt32Value* weight_proto = + envoy_config_route_v3_WeightedCluster_ClusterWeight_weight( + cluster_proto); + if (weight_proto == nullptr) { + ValidationErrors::ScopedField field(errors, ".weight"); + errors->AddError("field not present"); + } else { + cluster.weight = google_protobuf_UInt32Value_value(weight_proto); + if (cluster.weight == 0) continue; + total_weight += cluster.weight; + } + // Add entry to WeightedClusters. + action_weighted_clusters.emplace_back(std::move(cluster)); + } + if (action_weighted_clusters.empty()) { + errors->AddError("no valid clusters specified"); + } else if (total_weight > std::numeric_limits::max()) { + errors->AddError("sum of cluster weights exceeds uint32 max"); + } + route_action.action = std::move(action_weighted_clusters); + } else if (XdsRlsEnabled() && + envoy_config_route_v3_RouteAction_has_cluster_specifier_plugin( + route_action_proto)) { + // ClusterSpecifierPlugin + ValidationErrors::ScopedField field(errors, ".cluster_specifier_plugin"); + std::string plugin_name = UpbStringToStdString( + envoy_config_route_v3_RouteAction_cluster_specifier_plugin( + route_action_proto)); + if (plugin_name.empty()) { + errors->AddError("must be non-empty"); + return absl::nullopt; + } + auto it = cluster_specifier_plugin_map.find(plugin_name); + if (it == cluster_specifier_plugin_map.end()) { + errors->AddError(absl::StrCat("unknown cluster specifier plugin name \"", + plugin_name, "\"")); + } else { + // If the cluster specifier config is empty, that means that the + // plugin was unsupported but optional. In that case, skip this route. + if (it->second.empty()) return absl::nullopt; + } + route_action.action = + XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName{ + std::move(plugin_name)}; + } else { + // Not a supported cluster specifier, so ignore this route. + return absl::nullopt; + } + return route_action; +} + +absl::optional ParseRoute( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_Route* route_proto, + const absl::optional& + virtual_host_retry_policy, + const XdsRouteConfigResource::ClusterSpecifierPluginMap& + cluster_specifier_plugin_map, + std::set* cluster_specifier_plugins_not_seen, + ValidationErrors* errors) { + XdsRouteConfigResource::Route route; + // Parse route match. + { + ValidationErrors::ScopedField field(errors, ".match"); + const auto* match = envoy_config_route_v3_Route_match(route_proto); + if (match == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + // Skip routes with query_parameters set. + size_t query_parameters_size; + static_cast(envoy_config_route_v3_RouteMatch_query_parameters( + match, &query_parameters_size)); + if (query_parameters_size > 0) return absl::nullopt; + // Parse matchers. + auto path_matcher = RoutePathMatchParse(match, errors); + if (!path_matcher.has_value()) return absl::nullopt; + route.matchers.path_matcher = std::move(*path_matcher); + RouteHeaderMatchersParse(match, &route, errors); + RouteRuntimeFractionParse(match, &route, errors); + } + // Parse route action. + const envoy_config_route_v3_RouteAction* route_action_proto = + envoy_config_route_v3_Route_route(route_proto); + if (route_action_proto != nullptr) { + ValidationErrors::ScopedField field(errors, ".route"); + auto route_action = RouteActionParse(context, route_action_proto, + cluster_specifier_plugin_map, errors); + if (!route_action.has_value()) return absl::nullopt; + // If the route does not have a retry policy but the vhost does, + // use the vhost retry policy for this route. + if (!route_action->retry_policy.has_value()) { + route_action->retry_policy = virtual_host_retry_policy; + } + // Mark off plugins used in route action. + auto* cluster_specifier_action = absl::get_if< + XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName>( + &route_action->action); + if (cluster_specifier_action != nullptr) { + cluster_specifier_plugins_not_seen->erase( + cluster_specifier_action->cluster_specifier_plugin_name); + } + route.action = std::move(*route_action); + } else if (envoy_config_route_v3_Route_has_non_forwarding_action( + route_proto)) { + route.action = XdsRouteConfigResource::Route::NonForwardingAction(); + } else { + // Leave route.action initialized to UnknownAction (its default). + } + // Parse typed_per_filter_config. + { + ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); + route.typed_per_filter_config = ParseTypedPerFilterConfig< + envoy_config_route_v3_Route, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry>( + context, route_proto, + envoy_config_route_v3_Route_typed_per_filter_config_next, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key, + envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, errors); + } + return route; +} + +} // namespace + +std::shared_ptr XdsRouteConfigResourceParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RouteConfiguration* route_config, + ValidationErrors* errors) { + auto rds_update = std::make_shared(); + // Get the cluster spcifier plugin map. + if (XdsRlsEnabled()) { + rds_update->cluster_specifier_plugin_map = + ClusterSpecifierPluginParse(context, route_config, errors); + } + // Build a set of configured cluster_specifier_plugin names to make sure + // each is actually referenced by a route action. + std::set cluster_specifier_plugins_not_seen; + for (auto& plugin : rds_update->cluster_specifier_plugin_map) { + cluster_specifier_plugins_not_seen.emplace(plugin.first); + } + // Get the virtual hosts. + size_t num_virtual_hosts; + const envoy_config_route_v3_VirtualHost* const* virtual_hosts = + envoy_config_route_v3_RouteConfiguration_virtual_hosts( + route_config, &num_virtual_hosts); + for (size_t i = 0; i < num_virtual_hosts; ++i) { + ValidationErrors::ScopedField field( + errors, absl::StrCat(".virtual_hosts[", i, "]")); + rds_update->virtual_hosts.emplace_back(); + XdsRouteConfigResource::VirtualHost& vhost = + rds_update->virtual_hosts.back(); + // Parse domains. + size_t domain_size; + upb_StringView const* domains = envoy_config_route_v3_VirtualHost_domains( + virtual_hosts[i], &domain_size); + for (size_t j = 0; j < domain_size; ++j) { + std::string domain_pattern = UpbStringToStdString(domains[j]); + if (!XdsRouting::IsValidDomainPattern(domain_pattern)) { + ValidationErrors::ScopedField field(errors, + absl::StrCat(".domains[", j, "]")); + errors->AddError( + absl::StrCat("invalid domain pattern \"", domain_pattern, "\"")); + } + vhost.domains.emplace_back(std::move(domain_pattern)); + } + if (vhost.domains.empty()) { + ValidationErrors::ScopedField field(errors, ".domains"); + errors->AddError("must be non-empty"); + } + // Parse typed_per_filter_config. + { + ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); + vhost.typed_per_filter_config = 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, + errors); + } + // Parse retry policy. + absl::optional + virtual_host_retry_policy; + const envoy_config_route_v3_RetryPolicy* retry_policy = + envoy_config_route_v3_VirtualHost_retry_policy(virtual_hosts[i]); + if (retry_policy != nullptr) { + ValidationErrors::ScopedField field(errors, ".retry_policy"); + virtual_host_retry_policy = + RetryPolicyParse(context, retry_policy, errors); + } + // Parse routes. + ValidationErrors::ScopedField field2(errors, ".routes"); + size_t num_routes; + const envoy_config_route_v3_Route* const* routes = + envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes); + for (size_t j = 0; j < num_routes; ++j) { + ValidationErrors::ScopedField field(errors, absl::StrCat("[", j, "]")); + auto route = ParseRoute(context, routes[j], virtual_host_retry_policy, + rds_update->cluster_specifier_plugin_map, + &cluster_specifier_plugins_not_seen, errors); + if (route.has_value()) vhost.routes.emplace_back(std::move(*route)); + } + } + // For cluster specifier plugins that were not used in any route action, + // delete them from the update, since they will never be used. + for (auto& unused_plugin : cluster_specifier_plugins_not_seen) { + rds_update->cluster_specifier_plugin_map.erase(std::string(unused_plugin)); + } + return rds_update; +} + +// +// XdsRouteConfigResourceType +// + +namespace { + +void MaybeLogRouteConfiguration( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RouteConfiguration* route_config) { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { + const upb_MessageDef* msg_type = + envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab); + char buf[10240]; + upb_TextEncode(reinterpret_cast(route_config), msg_type, + nullptr, 0, buf, sizeof(buf)); + VLOG(2) << "[xds_client " << context.client + << "] RouteConfiguration: " << buf; + } +} + +} // namespace + +XdsResourceType::DecodeResult XdsRouteConfigResourceType::Decode( + const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const { + DecodeResult result; + // Parse serialized proto. + auto* resource = envoy_config_route_v3_RouteConfiguration_parse( + serialized_resource.data(), serialized_resource.size(), context.arena); + if (resource == nullptr) { + result.resource = + absl::InvalidArgumentError("Can't parse RouteConfiguration resource."); + return result; + } + MaybeLogRouteConfiguration(context, resource); + // Validate resource. + result.name = UpbStringToStdString( + envoy_config_route_v3_RouteConfiguration_name(resource)); + ValidationErrors errors; + auto rds_update = XdsRouteConfigResourceParse(context, resource, &errors); + if (!errors.ok()) { + absl::Status status = + errors.status(absl::StatusCode::kInvalidArgument, + "errors validating RouteConfiguration resource"); + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(ERROR) << "[xds_client " << context.client + << "] invalid RouteConfiguration " << *result.name << ": " + << status; + } + result.resource = std::move(status); + } else { + if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { + LOG(INFO) << "[xds_client " << context.client + << "] parsed RouteConfiguration " << *result.name << ": " + << rds_update->ToString(); + } + result.resource = std::move(rds_update); + } + return result; +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_route_config_parser.h b/src/core/xds/grpc/xds_route_config_parser.h new file mode 100644 index 00000000000..2523f8caa7a --- /dev/null +++ b/src/core/xds/grpc/xds_route_config_parser.h @@ -0,0 +1,80 @@ +// +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H + +#include + +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "envoy/config/route/v3/route.upb.h" +#include "envoy/config/route/v3/route.upbdefs.h" +#include "re2/re2.h" +#include "upb/reflection/def.h" + +#include + +#include "src/core/lib/channel/status_util.h" +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/matchers/matchers.h" +#include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/grpc/xds_route_config.h" +#include "src/core/xds/xds_client/xds_client.h" +#include "src/core/xds/xds_client/xds_resource_type.h" +#include "src/core/xds/xds_client/xds_resource_type_impl.h" + +namespace grpc_core { + +std::shared_ptr XdsRouteConfigResourceParse( + const XdsResourceType::DecodeContext& context, + const envoy_config_route_v3_RouteConfiguration* route_config, + ValidationErrors* errors); + +class XdsRouteConfigResourceType final + : public XdsResourceTypeImpl { + public: + absl::string_view type_url() const override { + return "envoy.config.route.v3.RouteConfiguration"; + } + + DecodeResult Decode(const XdsResourceType::DecodeContext& context, + absl::string_view serialized_resource) const override; + + void InitUpbSymtab(XdsClient* xds_client, + upb_DefPool* symtab) const override { + envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab); + const auto& cluster_specifier_plugin_registry = + static_cast(xds_client->bootstrap()) + .cluster_specifier_plugin_registry(); + cluster_specifier_plugin_registry.PopulateSymtab(symtab); + } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H diff --git a/src/core/xds/grpc/xds_routing.cc b/src/core/xds/grpc/xds_routing.cc index eadbe762286..1cedfd603d7 100644 --- a/src/core/xds/grpc/xds_routing.cc +++ b/src/core/xds/grpc/xds_routing.cc @@ -36,7 +36,7 @@ #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/matchers/matchers.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter.h" namespace grpc_core { diff --git a/src/core/xds/grpc/xds_routing.h b/src/core/xds/grpc/xds_routing.h index e0e4d46c43d..1d4966961d1 100644 --- a/src/core/xds/grpc/xds_routing.h +++ b/src/core/xds/grpc/xds_routing.h @@ -33,7 +33,7 @@ #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/transport/metadata_batch.h" -#include "src/core/xds/grpc/xds_http_filters.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" #include "src/core/xds/grpc/xds_listener.h" #include "src/core/xds/grpc/xds_route_config.h" diff --git a/src/core/xds/grpc/xds_server_grpc.cc b/src/core/xds/grpc/xds_server_grpc.cc new file mode 100644 index 00000000000..f7dd1d7366e --- /dev/null +++ b/src/core/xds/grpc/xds_server_grpc.cc @@ -0,0 +1,152 @@ +// +// Copyright 2019 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_server_grpc.h" + +#include + +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +#include "src/core/lib/config/core_configuration.h" +#include "src/core/util/json/json_reader.h" +#include "src/core/util/json/json_writer.h" + +namespace grpc_core { + +namespace { + +constexpr absl::string_view kServerFeatureIgnoreResourceDeletion = + "ignore_resource_deletion"; + +} // namespace + +bool GrpcXdsServer::IgnoreResourceDeletion() const { + return server_features_.find(std::string( + kServerFeatureIgnoreResourceDeletion)) != server_features_.end(); +} + +bool GrpcXdsServer::Equals(const XdsServer& other) const { + const auto& o = static_cast(other); + return (server_uri_ == o.server_uri_ && + channel_creds_config_->type() == o.channel_creds_config_->type() && + channel_creds_config_->Equals(*o.channel_creds_config_) && + server_features_ == o.server_features_); +} + +std::string GrpcXdsServer::Key() const { return JsonDump(ToJson()); } + +const JsonLoaderInterface* GrpcXdsServer::JsonLoader(const JsonArgs&) { + static const auto* loader = + JsonObjectLoader() + .Field("server_uri", &GrpcXdsServer::server_uri_) + .Finish(); + return loader; +} + +namespace { + +struct ChannelCreds { + std::string type; + Json::Object config; + + static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { + static const auto* loader = + JsonObjectLoader() + .Field("type", &ChannelCreds::type) + .OptionalField("config", &ChannelCreds::config) + .Finish(); + return loader; + } +}; + +} // namespace + +void GrpcXdsServer::JsonPostLoad(const Json& json, const JsonArgs& args, + ValidationErrors* errors) { + // Parse "channel_creds". + auto channel_creds_list = LoadJsonObjectField>( + json.object(), args, "channel_creds", errors); + if (channel_creds_list.has_value()) { + ValidationErrors::ScopedField field(errors, ".channel_creds"); + for (size_t i = 0; i < channel_creds_list->size(); ++i) { + ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); + auto& creds = (*channel_creds_list)[i]; + // Select the first channel creds type that we support, but + // validate all entries. + if (CoreConfiguration::Get().channel_creds_registry().IsSupported( + creds.type)) { + ValidationErrors::ScopedField field(errors, ".config"); + auto config = + CoreConfiguration::Get().channel_creds_registry().ParseConfig( + creds.type, Json::FromObject(creds.config), args, errors); + if (channel_creds_config_ == nullptr) { + channel_creds_config_ = std::move(config); + } + } + } + if (channel_creds_config_ == nullptr) { + errors->AddError("no known creds type found"); + } + } + // Parse "server_features". + { + ValidationErrors::ScopedField field(errors, ".server_features"); + auto it = json.object().find("server_features"); + if (it != json.object().end()) { + if (it->second.type() != Json::Type::kArray) { + errors->AddError("is not an array"); + } else { + const Json::Array& array = it->second.array(); + for (const Json& feature_json : array) { + if (feature_json.type() == Json::Type::kString && + (feature_json.string() == kServerFeatureIgnoreResourceDeletion)) { + server_features_.insert(feature_json.string()); + } + } + } + } + } +} + +Json GrpcXdsServer::ToJson() const { + Json::Object channel_creds_json{ + {"type", Json::FromString(std::string(channel_creds_config_->type()))}, + }; + if (channel_creds_config_ != nullptr) { + channel_creds_json["config"] = channel_creds_config_->ToJson(); + } + Json::Object json{ + {"server_uri", Json::FromString(server_uri_)}, + {"channel_creds", + Json::FromArray({Json::FromObject(std::move(channel_creds_json))})}, + }; + if (!server_features_.empty()) { + Json::Array server_features_json; + for (auto& feature : server_features_) { + server_features_json.emplace_back(Json::FromString(feature)); + } + json["server_features"] = Json::FromArray(std::move(server_features_json)); + } + return Json::FromObject(std::move(json)); +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_server_grpc.h b/src/core/xds/grpc/xds_server_grpc.h new file mode 100644 index 00000000000..80ef2070285 --- /dev/null +++ b/src/core/xds/grpc/xds_server_grpc.h @@ -0,0 +1,61 @@ +// +// Copyright 2019 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H + +#include +#include + +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/security/credentials/channel_creds_registry.h" +#include "src/core/util/json/json.h" +#include "src/core/util/json/json_args.h" +#include "src/core/util/json/json_object_loader.h" +#include "src/core/xds/xds_client/xds_bootstrap.h" + +namespace grpc_core { + +class GrpcXdsServer final : public XdsBootstrap::XdsServer { + public: + const std::string& server_uri() const override { return server_uri_; } + + bool IgnoreResourceDeletion() const override; + + bool Equals(const XdsServer& other) const override; + + std::string Key() const override; + + RefCountedPtr channel_creds_config() const { + return channel_creds_config_; + } + + static const JsonLoaderInterface* JsonLoader(const JsonArgs&); + void JsonPostLoad(const Json& json, const JsonArgs& args, + ValidationErrors* errors); + + Json ToJson() const; + + private: + std::string server_uri_; + RefCountedPtr channel_creds_config_; + std::set server_features_; +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H diff --git a/src/core/xds/grpc/xds_transport_grpc.cc b/src/core/xds/grpc/xds_transport_grpc.cc index 4344f1f8d1f..f2759b251a6 100644 --- a/src/core/xds/grpc/xds_transport_grpc.cc +++ b/src/core/xds/grpc/xds_transport_grpc.cc @@ -253,8 +253,8 @@ class GrpcXdsTransportFactory::GrpcXdsTransport::StateWatcher final namespace { -RefCountedPtr CreateXdsChannel( - const ChannelArgs& args, const GrpcXdsBootstrap::GrpcXdsServer& server) { +RefCountedPtr CreateXdsChannel(const ChannelArgs& args, + const GrpcXdsServer& server) { RefCountedPtr channel_creds = CoreConfiguration::Get().channel_creds_registry().CreateChannelCreds( server.channel_creds_config()); @@ -269,9 +269,8 @@ GrpcXdsTransportFactory::GrpcXdsTransport::GrpcXdsTransport( std::function on_connectivity_failure, absl::Status* status) : factory_(factory) { - channel_ = CreateXdsChannel( - factory->args_, - static_cast(server)); + channel_ = CreateXdsChannel(factory->args_, + static_cast(server)); CHECK(channel_ != nullptr); if (channel_->IsLame()) { *status = absl::UnavailableError("xds client has a lame channel"); diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index fb16edec478..75a21995f6f 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -840,18 +840,24 @@ CORE_SOURCE_FILES = [ 'src/core/xds/grpc/xds_certificate_provider.cc', 'src/core/xds/grpc/xds_client_grpc.cc', 'src/core/xds/grpc/xds_cluster.cc', + 'src/core/xds/grpc/xds_cluster_parser.cc', 'src/core/xds/grpc/xds_cluster_specifier_plugin.cc', 'src/core/xds/grpc/xds_common_types.cc', + 'src/core/xds/grpc/xds_common_types_parser.cc', 'src/core/xds/grpc/xds_endpoint.cc', + 'src/core/xds/grpc/xds_endpoint_parser.cc', 'src/core/xds/grpc/xds_health_status.cc', 'src/core/xds/grpc/xds_http_fault_filter.cc', - 'src/core/xds/grpc/xds_http_filters.cc', + 'src/core/xds/grpc/xds_http_filter_registry.cc', 'src/core/xds/grpc/xds_http_rbac_filter.cc', 'src/core/xds/grpc/xds_http_stateful_session_filter.cc', 'src/core/xds/grpc/xds_lb_policy_registry.cc', 'src/core/xds/grpc/xds_listener.cc', + 'src/core/xds/grpc/xds_listener_parser.cc', 'src/core/xds/grpc/xds_route_config.cc', + 'src/core/xds/grpc/xds_route_config_parser.cc', 'src/core/xds/grpc/xds_routing.cc', + 'src/core/xds/grpc/xds_server_grpc.cc', 'src/core/xds/grpc/xds_transport_grpc.cc', 'src/core/xds/xds_client/xds_api.cc', 'src/core/xds/xds_client/xds_bootstrap.cc', diff --git a/test/core/load_balancing/BUILD b/test/core/load_balancing/BUILD index 584c6167598..00464d63e97 100644 --- a/test/core/load_balancing/BUILD +++ b/test/core/load_balancing/BUILD @@ -158,6 +158,7 @@ grpc_cc_test( deps = [ ":lb_policy_test_lib", "//src/core:grpc_lb_policy_xds_override_host", + "//src/core:xds_health_status", "//test/core/test_util:grpc_test_util", ], ) diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index a4e133970e7..51413628d61 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -36,6 +36,7 @@ grpc_cc_test( deps = [ "//:gpr", "//src/core:grpc_xds_client", + "//src/core:xds_server_grpc", "//test/core/test_util:grpc_test_util", "//test/core/test_util:scoped_env_var", ], @@ -309,6 +310,7 @@ grpc_cc_test( "//:gpr", "//:grpc", "//src/core:grpc_xds_client", + "//src/core:xds_health_status", "//src/proto/grpc/testing/xds/v3:aggregate_cluster_proto", "//src/proto/grpc/testing/xds/v3:cluster_proto", "//src/proto/grpc/testing/xds/v3:http_protocol_options_proto", @@ -332,6 +334,7 @@ grpc_cc_test( "//:grpc", "//src/core:channel_args", "//src/core:grpc_xds_client", + "//src/core:xds_health_status", "//src/proto/grpc/testing/xds/v3:endpoint_proto", "//test/core/test_util:grpc_test_util", "//test/core/test_util:scoped_env_var", diff --git a/test/core/xds/xds_audit_logger_registry_test.cc b/test/core/xds/xds_audit_logger_registry_test.cc index 15089f371ac..dc79749f983 100644 --- a/test/core/xds/xds_audit_logger_registry_test.cc +++ b/test/core/xds/xds_audit_logger_registry_test.cc @@ -66,9 +66,8 @@ absl::StatusOr ConvertAuditLoggerConfig( std::string serialized_config = config.SerializeAsString(); upb::Arena arena; upb::DefPool def_pool; - XdsResourceType::DecodeContext context = { - nullptr, GrpcXdsBootstrap::GrpcXdsServer(), nullptr, def_pool.ptr(), - arena.ptr()}; + XdsResourceType::DecodeContext context = {nullptr, GrpcXdsServer(), nullptr, + def_pool.ptr(), arena.ptr()}; auto* upb_config = envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig_parse( serialized_config.data(), serialized_config.size(), arena.ptr()); diff --git a/test/core/xds/xds_bootstrap_test.cc b/test/core/xds/xds_bootstrap_test.cc index 529bcbeef1e..1bda95388b4 100644 --- a/test/core/xds/xds_bootstrap_test.cc +++ b/test/core/xds/xds_bootstrap_test.cc @@ -49,6 +49,7 @@ #include "src/core/util/tmpfile.h" #include "src/core/xds/grpc/certificate_provider_store.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_server_grpc.h" #include "test/core/test_util/scoped_env_var.h" #include "test/core/test_util/test_config.h" @@ -57,7 +58,7 @@ namespace testing { namespace { MATCHER_P2(EqXdsServer, name, creds_config_type, "equals XdsServer") { - auto* server = static_cast(arg); + auto* server = static_cast(arg); if (!::testing::ExplainMatchResult(::testing::Ne(nullptr), server, result_listener)) { return false; @@ -715,11 +716,10 @@ TEST(XdsBootstrapTest, XdsServerToJsonAndParse) { " }"; auto json = JsonParse(json_str); ASSERT_TRUE(json.ok()) << json.status(); - auto xds_server = LoadFromJson(*json); + auto xds_server = LoadFromJson(*json); ASSERT_TRUE(xds_server.ok()) << xds_server.status(); Json output = xds_server->ToJson(); - auto output_xds_server = - LoadFromJson(output); + auto output_xds_server = LoadFromJson(output); ASSERT_TRUE(output_xds_server.ok()) << output_xds_server.status(); EXPECT_EQ(*xds_server, *output_xds_server); } diff --git a/test/core/xds/xds_client_fuzzer.cc b/test/core/xds/xds_client_fuzzer.cc index f7b3e2fc48e..ddeaf96f86d 100644 --- a/test/core/xds/xds_client_fuzzer.cc +++ b/test/core/xds/xds_client_fuzzer.cc @@ -35,9 +35,13 @@ #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_cluster.h" +#include "src/core/xds/grpc/xds_cluster_parser.h" #include "src/core/xds/grpc/xds_endpoint.h" +#include "src/core/xds/grpc/xds_endpoint_parser.h" #include "src/core/xds/grpc/xds_listener.h" +#include "src/core/xds/grpc/xds_listener_parser.h" #include "src/core/xds/grpc/xds_route_config.h" +#include "src/core/xds/grpc/xds_route_config_parser.h" #include "src/core/xds/xds_client/xds_bootstrap.h" #include "src/core/xds/xds_client/xds_client.h" #include "src/libfuzzer/libfuzzer_macro.h" diff --git a/test/core/xds/xds_cluster_resource_type_test.cc b/test/core/xds/xds_cluster_resource_type_test.cc index adff21d6e31..053e5d59bb1 100644 --- a/test/core/xds/xds_cluster_resource_type_test.cc +++ b/test/core/xds/xds_cluster_resource_type_test.cc @@ -45,6 +45,7 @@ #include "src/core/util/json/json_writer.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_cluster.h" +#include "src/core/xds/grpc/xds_cluster_parser.h" #include "src/core/xds/grpc/xds_common_types.h" #include "src/core/xds/grpc/xds_health_status.h" #include "src/core/xds/xds_client/xds_bootstrap.h" diff --git a/test/core/xds/xds_common_types_test.cc b/test/core/xds/xds_common_types_test.cc index 6202407862f..5a4fb324691 100644 --- a/test/core/xds/xds_common_types_test.cc +++ b/test/core/xds/xds_common_types_test.cc @@ -47,6 +47,7 @@ #include "src/core/util/json/json_writer.h" #include "src/core/util/upb_utils.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" #include "src/core/xds/xds_client/xds_bootstrap.h" #include "src/core/xds/xds_client/xds_client.h" #include "src/core/xds/xds_client/xds_resource_type.h" @@ -196,7 +197,7 @@ class CommonTlsConfigTest : public XdsCommonTypesTest { upb_proto) { ValidationErrors errors; CommonTlsContext common_tls_context = - CommonTlsContext::Parse(decode_context_, upb_proto, &errors); + CommonTlsContextParse(decode_context_, upb_proto, &errors); if (!errors.ok()) { return errors.status(absl::StatusCode::kInvalidArgument, "validation failed"); diff --git a/test/core/xds/xds_endpoint_resource_type_test.cc b/test/core/xds/xds_endpoint_resource_type_test.cc index 676cea36cfc..ad21d2d2d4f 100644 --- a/test/core/xds/xds_endpoint_resource_type_test.cc +++ b/test/core/xds/xds_endpoint_resource_type_test.cc @@ -45,6 +45,7 @@ #include "src/core/resolver/endpoint_addresses.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_endpoint.h" +#include "src/core/xds/grpc/xds_endpoint_parser.h" #include "src/core/xds/grpc/xds_health_status.h" #include "src/core/xds/xds_client/xds_bootstrap.h" #include "src/core/xds/xds_client/xds_client.h" diff --git a/test/core/xds/xds_http_filters_test.cc b/test/core/xds/xds_http_filters_test.cc index dd3dc0943bd..eb2339e4e33 100644 --- a/test/core/xds/xds_http_filters_test.cc +++ b/test/core/xds/xds_http_filters_test.cc @@ -14,8 +14,6 @@ // limitations under the License. // -#include "src/core/xds/grpc/xds_http_filters.h" - #include #include #include @@ -50,6 +48,8 @@ #include "src/core/lib/iomgr/error.h" #include "src/core/util/json/json_writer.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/grpc/xds_http_filter_registry.h" #include "src/core/xds/xds_client/xds_client.h" #include "src/proto/grpc/testing/xds/v3/address.pb.h" #include "src/proto/grpc/testing/xds/v3/cookie.pb.h" @@ -143,7 +143,7 @@ class XdsHttpFilterTest : public ::testing::Test { absl::StripPrefix(type, "type.googleapis.com/")); } - GrpcXdsBootstrap::GrpcXdsServer xds_server_; + GrpcXdsServer xds_server_; RefCountedPtr xds_client_; upb::DefPool upb_def_pool_; upb::Arena upb_arena_; diff --git a/test/core/xds/xds_lb_policy_registry_test.cc b/test/core/xds/xds_lb_policy_registry_test.cc index 54053afe0de..c1e0e0fdda3 100644 --- a/test/core/xds/xds_lb_policy_registry_test.cc +++ b/test/core/xds/xds_lb_policy_registry_test.cc @@ -75,9 +75,8 @@ absl::StatusOr ConvertXdsPolicy( std::string serialized_policy = policy.SerializeAsString(); upb::Arena arena; upb::DefPool def_pool; - XdsResourceType::DecodeContext context = { - nullptr, GrpcXdsBootstrap::GrpcXdsServer(), nullptr, def_pool.ptr(), - arena.ptr()}; + XdsResourceType::DecodeContext context = {nullptr, GrpcXdsServer(), nullptr, + def_pool.ptr(), arena.ptr()}; auto* upb_policy = envoy_config_cluster_v3_LoadBalancingPolicy_parse( serialized_policy.data(), serialized_policy.size(), arena.ptr()); ValidationErrors errors; diff --git a/test/core/xds/xds_listener_resource_type_test.cc b/test/core/xds/xds_listener_resource_type_test.cc index 84f0158de41..718202d1e59 100644 --- a/test/core/xds/xds_listener_resource_type_test.cc +++ b/test/core/xds/xds_listener_resource_type_test.cc @@ -49,6 +49,7 @@ #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_common_types.h" #include "src/core/xds/grpc/xds_listener.h" +#include "src/core/xds/grpc/xds_listener_parser.h" #include "src/core/xds/xds_client/xds_bootstrap.h" #include "src/core/xds/xds_client/xds_client.h" #include "src/core/xds/xds_client/xds_resource_type.h" diff --git a/test/core/xds/xds_route_config_resource_type_test.cc b/test/core/xds/xds_route_config_resource_type_test.cc index 21ceb8d15d4..b65a8516d02 100644 --- a/test/core/xds/xds_route_config_resource_type_test.cc +++ b/test/core/xds/xds_route_config_resource_type_test.cc @@ -53,6 +53,7 @@ #include "src/core/util/json/json_writer.h" #include "src/core/xds/grpc/xds_bootstrap_grpc.h" #include "src/core/xds/grpc/xds_route_config.h" +#include "src/core/xds/grpc/xds_route_config_parser.h" #include "src/core/xds/xds_client/xds_bootstrap.h" #include "src/core/xds/xds_client/xds_client.h" #include "src/core/xds/xds_client/xds_resource_type.h" diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index aed08bb8420..112a3c6d24a 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -2981,19 +2981,26 @@ src/core/xds/grpc/xds_client_grpc.cc \ src/core/xds/grpc/xds_client_grpc.h \ src/core/xds/grpc/xds_cluster.cc \ src/core/xds/grpc/xds_cluster.h \ +src/core/xds/grpc/xds_cluster_parser.cc \ +src/core/xds/grpc/xds_cluster_parser.h \ src/core/xds/grpc/xds_cluster_specifier_plugin.cc \ src/core/xds/grpc/xds_cluster_specifier_plugin.h \ src/core/xds/grpc/xds_common_types.cc \ src/core/xds/grpc/xds_common_types.h \ +src/core/xds/grpc/xds_common_types_parser.cc \ +src/core/xds/grpc/xds_common_types_parser.h \ src/core/xds/grpc/xds_enabled_server.h \ src/core/xds/grpc/xds_endpoint.cc \ src/core/xds/grpc/xds_endpoint.h \ +src/core/xds/grpc/xds_endpoint_parser.cc \ +src/core/xds/grpc/xds_endpoint_parser.h \ src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_health_status.h \ src/core/xds/grpc/xds_http_fault_filter.cc \ src/core/xds/grpc/xds_http_fault_filter.h \ -src/core/xds/grpc/xds_http_filters.cc \ -src/core/xds/grpc/xds_http_filters.h \ +src/core/xds/grpc/xds_http_filter.h \ +src/core/xds/grpc/xds_http_filter_registry.cc \ +src/core/xds/grpc/xds_http_filter_registry.h \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.h \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ @@ -3002,10 +3009,16 @@ src/core/xds/grpc/xds_lb_policy_registry.cc \ src/core/xds/grpc/xds_lb_policy_registry.h \ src/core/xds/grpc/xds_listener.cc \ src/core/xds/grpc/xds_listener.h \ +src/core/xds/grpc/xds_listener_parser.cc \ +src/core/xds/grpc/xds_listener_parser.h \ src/core/xds/grpc/xds_route_config.cc \ src/core/xds/grpc/xds_route_config.h \ +src/core/xds/grpc/xds_route_config_parser.cc \ +src/core/xds/grpc/xds_route_config_parser.h \ src/core/xds/grpc/xds_routing.cc \ src/core/xds/grpc/xds_routing.h \ +src/core/xds/grpc/xds_server_grpc.cc \ +src/core/xds/grpc/xds_server_grpc.h \ src/core/xds/grpc/xds_transport_grpc.cc \ src/core/xds/grpc/xds_transport_grpc.h \ src/core/xds/xds_client/xds_api.cc \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 7d92ca90642..6a60330a420 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -2761,18 +2761,25 @@ src/core/xds/grpc/xds_client_grpc.cc \ src/core/xds/grpc/xds_client_grpc.h \ src/core/xds/grpc/xds_cluster.cc \ src/core/xds/grpc/xds_cluster.h \ +src/core/xds/grpc/xds_cluster_parser.cc \ +src/core/xds/grpc/xds_cluster_parser.h \ src/core/xds/grpc/xds_cluster_specifier_plugin.cc \ src/core/xds/grpc/xds_cluster_specifier_plugin.h \ src/core/xds/grpc/xds_common_types.cc \ src/core/xds/grpc/xds_common_types.h \ +src/core/xds/grpc/xds_common_types_parser.cc \ +src/core/xds/grpc/xds_common_types_parser.h \ src/core/xds/grpc/xds_endpoint.cc \ src/core/xds/grpc/xds_endpoint.h \ +src/core/xds/grpc/xds_endpoint_parser.cc \ +src/core/xds/grpc/xds_endpoint_parser.h \ src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_health_status.h \ src/core/xds/grpc/xds_http_fault_filter.cc \ src/core/xds/grpc/xds_http_fault_filter.h \ -src/core/xds/grpc/xds_http_filters.cc \ -src/core/xds/grpc/xds_http_filters.h \ +src/core/xds/grpc/xds_http_filter.h \ +src/core/xds/grpc/xds_http_filter_registry.cc \ +src/core/xds/grpc/xds_http_filter_registry.h \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.h \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ @@ -2781,10 +2788,16 @@ src/core/xds/grpc/xds_lb_policy_registry.cc \ src/core/xds/grpc/xds_lb_policy_registry.h \ src/core/xds/grpc/xds_listener.cc \ src/core/xds/grpc/xds_listener.h \ +src/core/xds/grpc/xds_listener_parser.cc \ +src/core/xds/grpc/xds_listener_parser.h \ src/core/xds/grpc/xds_route_config.cc \ src/core/xds/grpc/xds_route_config.h \ +src/core/xds/grpc/xds_route_config_parser.cc \ +src/core/xds/grpc/xds_route_config_parser.h \ src/core/xds/grpc/xds_routing.cc \ src/core/xds/grpc/xds_routing.h \ +src/core/xds/grpc/xds_server_grpc.cc \ +src/core/xds/grpc/xds_server_grpc.h \ src/core/xds/grpc/xds_transport_grpc.cc \ src/core/xds/grpc/xds_transport_grpc.h \ src/core/xds/xds_client/xds_api.cc \