[reland][fuzzing] Client channel resolver fuzzer

This reverts commit 1624542ea4.
pull/33153/head
AJ Heller 2 years ago
parent 63ecc4ba3e
commit a40494918a
  1. 3
      bazel/cc_grpc_library.bzl
  2. 9
      src/core/ext/filters/client_channel/resolver/dns/event_engine/event_engine_client_channel_resolver.cc
  3. 46
      src/proto/grpc/service_config/BUILD
  4. 786
      src/proto/grpc/service_config/service_config.proto
  5. 47
      test/core/ext/filters/event_engine_client_channel_resolver/BUILD
  6. 282
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer.cc
  7. 23
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/crash-0dca225f566df998becb705fc3a6b33a2441b928
  8. 10
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/crash-1f078e66afdaf4f9db6ac5704c15d6ee7150bd80
  9. 8
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/crash-8a4668f3c812f8d7d0eb5e96493ba84fe77edfc2
  10. 0
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709
  11. 16
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/timeout-1860f73b73574e91ce7b3cd8519cc0ec9753c5d8
  12. 5
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_fuzzer_corpus/timeout-7a3822831087e0f79e2743c8f87650cc34a18898
  13. 80
      test/core/ext/filters/event_engine_client_channel_resolver/resolver_ops.proto

@ -74,7 +74,8 @@ def cc_grpc_library(
cc_proto_target = name if proto_only else "_" + name + "_cc_proto" cc_proto_target = name if proto_only else "_" + name + "_cc_proto"
proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(":") == -1] proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(":") == -1]
proto_deps += [dep.split(":")[0] + ":" + "_" + dep.split(":")[1] + "_only" for dep in deps if dep.find(":") != -1] proto_deps += [dep.split(":")[0] + ":" + "_" + dep.split(":")[1] + "_only" for dep in deps if dep.find(":") != -1 and dep.find("com_google_googleapis") == -1]
proto_deps += [dep for dep in deps if dep.find("com_google_googleapis") != -1]
if well_known_protos: if well_known_protos:
proto_deps += well_known_proto_libs() proto_deps += well_known_proto_libs()
proto_library( proto_library(

@ -334,6 +334,10 @@ void EventEngineClientChannelDNSResolver::EventEngineDNSRequestWrapper::
result = OnResolvedLocked(); result = OnResolvedLocked();
return; return;
} }
if (srv_records->empty()) {
result = OnResolvedLocked();
return;
}
// Do a subsequent hostname query since SRV records were returned // Do a subsequent hostname query since SRV records were returned
for (auto& srv_record : *srv_records) { for (auto& srv_record : *srv_records) {
GRPC_EVENT_ENGINE_RESOLVER_TRACE( GRPC_EVENT_ENGINE_RESOLVER_TRACE(
@ -496,6 +500,11 @@ absl::optional<Resolver::Result> EventEngineClientChannelDNSResolver::
absl::Status status = errors_.status( absl::Status status = errors_.status(
absl::StatusCode::kUnavailable, absl::StatusCode::kUnavailable,
absl::StrCat("errors resolving ", resolver_->name_to_resolve())); absl::StrCat("errors resolving ", resolver_->name_to_resolve()));
if (status.ok()) {
// If no errors were returned, but the results are empty, we still need to
// return an error. Validation errors may be empty.
status = absl::UnavailableError("No results from DNS queries");
}
GRPC_EVENT_ENGINE_RESOLVER_TRACE("%s", status.message().data()); GRPC_EVENT_ENGINE_RESOLVER_TRACE("%s", status.message().data());
result.addresses = status; result.addresses = status;
result.service_config = status; result.service_config = status;

@ -0,0 +1,46 @@
# Copyright 2023 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.
load("@rules_proto//proto:defs.bzl", "proto_library")
load("//bazel:grpc_build_system.bzl", "grpc_package", "grpc_proto_library")
licenses(["notice"])
grpc_package(
name = "src/proto/grpc/service_config",
visibility = "public",
)
grpc_proto_library(
name = "service_config_proto",
srcs = ["service_config.proto"],
has_services = False,
well_known_protos = True,
deps = [
"//src/proto/grpc/lookup/v1:rls_config_proto",
"@com_google_googleapis//google/rpc:code_proto",
],
)
proto_library(
name = "service_config_proto_descriptor",
srcs = ["service_config.proto"],
deps = [
"//src/proto/grpc/lookup/v1:rls_config_proto_descriptor",
"@com_google_googleapis//google/rpc:code_proto",
"@com_google_protobuf//:duration_proto",
"@com_google_protobuf//:struct_proto",
"@com_google_protobuf//:wrappers_proto",
],
)

@ -0,0 +1,786 @@
// Copyright 2016 The 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.
// A ServiceConfig is supplied when a service is deployed. It mostly contains
// parameters for how clients that connect to the service should behave (for
// example, the load balancing policy to use to pick between service replicas).
//
// The configuration options provided here act as overrides to automatically
// chosen option values. Service owners should be conservative in specifying
// options as the system is likely to choose better values for these options in
// the vast majority of cases. In other words, please specify a configuration
// option only if you really have to, and avoid copy-paste inclusion of configs.
//
// Note that gRPC uses the service config in JSON form, not in protobuf
// form. This proto definition is intended to help document the schema but
// will not actually be used directly by gRPC.
//
// Copied from https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto
// for testing purposes
syntax = "proto3";
package grpc.service_config;
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";
import "google/rpc/code.proto";
import "src/proto/grpc/lookup/v1/rls_config.proto";
option java_package = "io.grpc.serviceconfig";
option java_multiple_files = true;
option java_outer_classname = "ServiceConfigProto";
// Configuration for a method.
message MethodConfig {
// The names of the methods to which this configuration applies.
// - MethodConfig without names (empty list) will be skipped.
// - Each name entry must be unique across the entire ServiceConfig.
// - If the 'method' field is empty, this MethodConfig specifies the defaults
// for all methods for the specified service.
// - If the 'service' field is empty, the 'method' field must be empty, and
// this MethodConfig specifies the default for all methods (it's the default
// config).
//
// When determining which MethodConfig to use for a given RPC, the most
// specific match wins. For example, let's say that the service config
// contains the following MethodConfig entries:
//
// method_config { name { } ... }
// method_config { name { service: "MyService" } ... }
// method_config { name { service: "MyService" method: "Foo" } ... }
//
// MyService/Foo will use the third entry, because it exactly matches the
// service and method name. MyService/Bar will use the second entry, because
// it provides the default for all methods of MyService. AnotherService/Baz
// will use the first entry, because it doesn't match the other two.
//
// In JSON representation, value "", value `null`, and not present are the
// same. The following are the same Name:
// - { "service": "s" }
// - { "service": "s", "method": null }
// - { "service": "s", "method": "" }
message Name {
string service = 1; // Required. Includes proto package name.
string method = 2;
}
repeated Name name = 1;
// Whether RPCs sent to this method should wait until the connection is
// ready by default. If false, the RPC will abort immediately if there is
// a transient failure connecting to the server. Otherwise, gRPC will
// attempt to connect until the deadline is exceeded.
//
// The value specified via the gRPC client API will override the value
// set here. However, note that setting the value in the client API will
// also affect transient errors encountered during name resolution, which
// cannot be caught by the value here, since the service config is
// obtained by the gRPC client via name resolution.
google.protobuf.BoolValue wait_for_ready = 2;
// The default timeout in seconds for RPCs sent to this method. This can be
// overridden in code. If no reply is received in the specified amount of
// time, the request is aborted and a DEADLINE_EXCEEDED error status
// is returned to the caller.
//
// The actual deadline used will be the minimum of the value specified here
// and the value set by the application via the gRPC client API. If either
// one is not set, then the other will be used. If neither is set, then the
// request has no deadline.
google.protobuf.Duration timeout = 3;
// The maximum allowed payload size for an individual request or object in a
// stream (client->server) in bytes. The size which is measured is the
// serialized payload after per-message compression (but before stream
// compression) in bytes. This applies both to streaming and non-streaming
// requests.
//
// The actual value used is the minimum of the value specified here and the
// value set by the application via the gRPC client API. If either one is
// not set, then the other will be used. If neither is set, then the
// built-in default is used.
//
// If a client attempts to send an object larger than this value, it will not
// be sent and the client will see a ClientError.
// Note that 0 is a valid value, meaning that the request message
// must be empty.
google.protobuf.UInt32Value max_request_message_bytes = 4;
// The maximum allowed payload size for an individual response or object in a
// stream (server->client) in bytes. The size which is measured is the
// serialized payload after per-message compression (but before stream
// compression) in bytes. This applies both to streaming and non-streaming
// requests.
//
// The actual value used is the minimum of the value specified here and the
// value set by the application via the gRPC client API. If either one is
// not set, then the other will be used. If neither is set, then the
// built-in default is used.
//
// If a server attempts to send an object larger than this value, it will not
// be sent, and a ServerError will be sent to the client instead.
// Note that 0 is a valid value, meaning that the response message
// must be empty.
google.protobuf.UInt32Value max_response_message_bytes = 5;
// The retry policy for outgoing RPCs.
message RetryPolicy {
// The maximum number of RPC attempts, including the original attempt.
//
// This field is required and must be greater than 1.
// Any value greater than 5 will be treated as if it were 5.
uint32 max_attempts = 1;
// Exponential backoff parameters. The initial retry attempt will occur at
// random(0, initial_backoff). In general, the nth attempt will occur at
// random(0,
// min(initial_backoff*backoff_multiplier**(n-1), max_backoff)).
// Required. Must be greater than zero.
google.protobuf.Duration initial_backoff = 2;
// Required. Must be greater than zero.
google.protobuf.Duration max_backoff = 3;
float backoff_multiplier = 4; // Required. Must be greater than zero.
// The set of status codes which may be retried.
//
// This field is required and must be non-empty.
repeated google.rpc.Code retryable_status_codes = 5;
}
// The hedging policy for outgoing RPCs. Hedged RPCs may execute more than
// once on the server, so only idempotent methods should specify a hedging
// policy.
message HedgingPolicy {
// The hedging policy will send up to max_requests RPCs.
// This number represents the total number of all attempts, including
// the original attempt.
//
// This field is required and must be greater than 1.
// Any value greater than 5 will be treated as if it were 5.
uint32 max_attempts = 1;
// The first RPC will be sent immediately, but the max_requests-1 subsequent
// hedged RPCs will be sent at intervals of every hedging_delay. Set this
// to 0 to immediately send all max_requests RPCs.
google.protobuf.Duration hedging_delay = 2;
// The set of status codes which indicate other hedged RPCs may still
// succeed. If a non-fatal status code is returned by the server, hedged
// RPCs will continue. Otherwise, outstanding requests will be canceled and
// the error returned to the client application layer.
//
// This field is optional.
repeated google.rpc.Code non_fatal_status_codes = 3;
}
// Only one of retry_policy or hedging_policy may be set. If neither is set,
// RPCs will not be retried or hedged.
oneof retry_or_hedging_policy {
RetryPolicy retry_policy = 6;
HedgingPolicy hedging_policy = 7;
}
}
// Configuration for pick_first LB policy.
message PickFirstConfig {
// If set to true, instructs the LB policy to randomly shuffle the list of
// addresses received from the name resolver before attempting to connect to
// them.
bool shuffle_address_list = 1;
}
// Configuration for round_robin LB policy.
message RoundRobinConfig {}
// Configuration for weighted_round_robin LB policy.
message WeightedRoundRobinLbConfig {
// Whether to enable out-of-band utilization reporting collection from
// the endpoints. By default, per-request utilization reporting is used.
google.protobuf.BoolValue enable_oob_load_report = 1;
// Load reporting interval to request from the server. Note that the
// server may not provide reports as frequently as the client requests.
// Used only when enable_oob_load_report is true. Default is 10 seconds.
google.protobuf.Duration oob_reporting_period = 2;
// A given endpoint must report load metrics continuously for at least
// this long before the endpoint weight will be used. This avoids
// churn when the set of endpoint addresses changes. Takes effect
// both immediately after we establish a connection to an endpoint and
// after weight_expiration_period has caused us to stop using the most
// recent load metrics. Default is 10 seconds.
google.protobuf.Duration blackout_period = 3;
// If a given endpoint has not reported load metrics in this long,
// then we stop using the reported weight. This ensures that we do
// not continue to use very stale weights. Once we stop using a stale
// value, if we later start seeing fresh reports again, the
// blackout_period applies. Defaults to 3 minutes.
google.protobuf.Duration weight_expiration_period = 4;
// How often endpoint weights are recalculated. Values less than 100ms are
// capped at 100ms. Default is 1 second.
google.protobuf.Duration weight_update_period = 5;
// The multiplier used to adjust endpoint weights with the error rate
// calculated as eps/qps. Configuration is rejected if this value is negative.
// Default is 1.0.
google.protobuf.FloatValue error_utilization_penalty = 6;
}
// Configuration for outlier_detection LB policy
message OutlierDetectionLoadBalancingConfig {
// The time interval between ejection analysis sweeps. This can result in
// both new ejections as well as addresses being returned to service. Defaults
// to 10000ms or 10s.
google.protobuf.Duration interval = 1;
// The base time that as address is ejected for. The real time is equal to the
// base time multiplied by the number of times the address has been ejected.
// Defaults to 30000ms or 30s.
google.protobuf.Duration base_ejection_time = 2;
// The maximum time that an address is ejected for. If not specified, the default value (300000ms or 300s) or
// the base_ejection_time value is applied, whatever is larger.
google.protobuf.Duration max_ejection_time = 3;
// The maximum % of an address list that can be ejected due to outlier
// detection. Defaults to 10% but will eject at least one address regardless of the value.
google.protobuf.UInt32Value max_ejection_percent = 4;
// Parameters for the success rate ejection algorithm.
// This algorithm monitors the request success rate for all endpoints and
// ejects individual endpoints whose success rates are statistical outliers.
message SuccessRateEjection {
// This factor is used to determine the ejection threshold for success rate
// outlier ejection. The ejection threshold is the difference between the
// mean success rate, and the product of this factor and the standard
// deviation of the mean success rate: mean - (stdev *
// success_rate_stdev_factor). This factor is divided by a thousand to get a
// double. That is, if the desired factor is 1.9, the runtime value should
// be 1900. Defaults to 1900.
google.protobuf.UInt32Value stdev_factor = 1;
// The % chance that an address will be actually ejected when an outlier status
// is detected through success rate statistics. This setting can be used to
// disable ejection or to ramp it up slowly. Defaults to 100.
google.protobuf.UInt32Value enforcement_percentage = 2;
// The number of addresses that must have enough request volume to
// detect success rate outliers. If the number of addresses is less than this
// setting, outlier detection via success rate statistics is not performed
// for any addresses. Defaults to 5.
google.protobuf.UInt32Value minimum_hosts = 3;
// The minimum number of total requests that must be collected in one
// interval (as defined by the interval duration above) to include this address
// in success rate based outlier detection. If the volume is lower than this
// setting, outlier detection via success rate statistics is not performed
// for that address. Defaults to 100.
google.protobuf.UInt32Value request_volume = 4;
}
// Parameters for the failure percentage algorithm.
// This algorithm ejects individual endpoints whose failure rate is greater than
// some threshold, independently of any other endpoint.
message FailurePercentageEjection {
// The failure percentage to use when determining failure percentage-based outlier detection. If
// the failure percentage of a given address is greater than or equal to this value, it will be
// ejected. Defaults to 85.
google.protobuf.UInt32Value threshold = 1;
// The % chance that an address will be actually ejected when an outlier status is detected through
// failure percentage statistics. This setting can be used to disable ejection or to ramp it up
// slowly. Defaults to 100.
google.protobuf.UInt32Value enforcement_percentage = 2;
// The minimum number of addresses in order to perform failure percentage-based ejection.
// If the total number of addresses is less than this value, failure percentage-based
// ejection will not be performed. Defaults to 5.
google.protobuf.UInt32Value minimum_hosts = 3;
// The minimum number of total requests that must be collected in one interval (as defined by the
// interval duration above) to perform failure percentage-based ejection for this address. If the
// volume is lower than this setting, failure percentage-based ejection will not be performed for
// this host. Defaults to 50.
google.protobuf.UInt32Value request_volume = 4;
}
// If set, success rate ejections will be performed
SuccessRateEjection success_rate_ejection = 5;
// If set, failure rate ejections will be performed
FailurePercentageEjection failure_percentage_ejection = 6;
// The config for the child policy
repeated LoadBalancingConfig child_policy = 13;
}
// Configuration for grpclb LB policy.
message GrpcLbConfig {
// Optional. What LB policy to use for routing between the backend
// addresses. If unset, defaults to round_robin.
// Currently, the only supported values are round_robin and pick_first.
// Note that this will be used both in balancer mode and in fallback mode.
// Multiple LB policies can be specified; clients will iterate through
// the list in order and stop at the first policy that they support.
repeated LoadBalancingConfig child_policy = 1;
// Optional. If specified, overrides the name of the service to be sent to
// the balancer.
string service_name = 2;
// Optional. The timeout in seconds for receiving the server list from the LB
// server. Defaults to 10s.
google.protobuf.Duration initial_fallback_timeout = 3;
}
// Configuration for priority LB policy.
message PriorityLoadBalancingPolicyConfig {
// A map of name to child policy configuration.
// The names are used to allow the priority policy to update
// existing child policies instead of creating new ones every
// time it receives a config update.
message Child {
repeated LoadBalancingConfig config = 1;
// If true, will ignore reresolution requests from this child.
bool ignore_reresolution_requests = 2;
}
map<string, Child> children = 1;
// A list of child names in decreasing priority order
// (i.e., first element is the highest priority).
repeated string priorities = 2;
}
// Configuration for weighted_target LB policy.
message WeightedTargetLoadBalancingPolicyConfig {
message Target {
uint32 weight = 1;
repeated LoadBalancingConfig child_policy = 2;
}
map<string, Target> targets = 1;
}
// Config for RLS LB policy.
message RlsLoadBalancingPolicyConfig {
grpc.lookup.v1.RouteLookupConfig route_lookup_config = 1;
// Service config to use for the RLS channel.
ServiceConfig route_lookup_channel_service_config = 2;
repeated LoadBalancingConfig child_policy = 3;
// Field name to add to child policy config to contain the target name.
string child_policy_config_target_field_name = 4;
}
// Configuration for xds_cluster_manager_experimental LB policy.
message XdsClusterManagerLoadBalancingPolicyConfig {
message Child {
repeated LoadBalancingConfig child_policy = 1;
}
map<string, Child> children = 1;
}
// Configuration for the cds LB policy.
message CdsConfig {
string cluster = 1; // Required.
}
// Represents an xDS server.
message XdsServer {
string server_uri = 1 [json_name = "server_uri"]; // Required.
message ChannelCredentials {
string type = 1; // Required.
google.protobuf.Struct config = 2; // Optional JSON config.
}
// A list of channel creds to use. The first supported type will be used.
repeated ChannelCredentials channel_creds = 2 [json_name = "channel_creds"];
// A repeated list of server features.
repeated google.protobuf.Value server_features = 3
[json_name = "server_features"];
}
// Configuration for xds_cluster_resolver LB policy.
message XdsClusterResolverLoadBalancingPolicyConfig {
// Describes a discovery mechanism instance.
// For EDS or LOGICAL_DNS clusters, there will be exactly one
// DiscoveryMechanism, which will describe the cluster of the parent
// CDS policy.
// For aggregate clusters, there will be one DiscoveryMechanism for each
// underlying cluster.
message DiscoveryMechanism {
// Cluster name.
string cluster = 1;
// LRS server to send load reports to.
// If not present, load reporting will be disabled.
// If set to the empty string, load reporting will be sent to the same
// server that we obtained CDS data from.
// DEPRECATED: Use new lrs_load_reporting_server field instead.
google.protobuf.StringValue lrs_load_reporting_server_name = 2
[deprecated=true];
// LRS server to send load reports to.
// If not present, load reporting will be disabled.
// Supercedes lrs_load_reporting_server_name field.
XdsServer lrs_load_reporting_server = 7;
// Maximum number of outstanding requests can be made to the upstream
// cluster. Default is 1024.
google.protobuf.UInt32Value max_concurrent_requests = 3;
enum Type {
UNKNOWN = 0;
EDS = 1;
LOGICAL_DNS = 2;
};
Type type = 4;
// For type EDS only.
// EDS service name, as returned in CDS.
// May be unset if not specified in CDS.
string eds_service_name = 5;
// For type LOGICAL_DNS only.
// DNS name to resolve in "host:port" form.
string dns_hostname = 6;
// The configuration for outlier_detection child policies
// Within this message, the child_policy field will be ignored
OutlierDetectionLoadBalancingConfig outlier_detection = 8;
// The configuration for xds_override_host child policy
repeated OverrideHostLoadBalancingPolicyConfig.HealthStatus override_host_status = 9;
}
// Ordered list of discovery mechanisms.
// Must have at least one element.
// Results from each discovery mechanism are concatenated together in
// successive priorities.
repeated DiscoveryMechanism discovery_mechanisms = 1;
// xDS LB policy. Will be used as the child config of the xds_cluster_impl LB policy.
repeated LoadBalancingConfig xds_lb_policy = 2;
}
// Configuration for xds_cluster_impl LB policy.
message XdsClusterImplLoadBalancingPolicyConfig {
// Cluster name. Required.
string cluster = 1;
// EDS service name.
// Not set if cluster is not an EDS cluster or if it does not
// specify an EDS service name.
string eds_service_name = 2;
// Server to send load reports to.
// If unset, no load reporting is done.
// If set to empty string, load reporting will be sent to the same
// server as we are getting xds data from.
// DEPRECATED: Use new lrs_load_reporting_server field instead.
google.protobuf.StringValue lrs_load_reporting_server_name = 3
[deprecated=true];
// LRS server to send load reports to.
// If not present, load reporting will be disabled.
// Supercedes lrs_load_reporting_server_name field.
XdsServer lrs_load_reporting_server = 7;
// Maximum number of outstanding requests can be made to the upstream cluster.
// Default is 1024.
google.protobuf.UInt32Value max_concurrent_requests = 4;
// Drop configuration.
message DropCategory {
string category = 1;
uint32 requests_per_million = 2;
}
repeated DropCategory drop_categories = 5;
// Child policy.
repeated LoadBalancingConfig child_policy = 6;
}
// Configuration for eds LB policy.
message EdsLoadBalancingPolicyConfig {
// Cluster name. Required.
string cluster = 1;
// EDS service name, as returned in CDS.
// May be unset if not specified in CDS.
string eds_service_name = 2;
// Server to send load reports to.
// If unset, no load reporting is done.
// If set to empty string, load reporting will be sent to the same
// server as we are getting xds data from.
google.protobuf.StringValue lrs_load_reporting_server_name = 3;
// Locality-picking policy.
// This policy's config is expected to be in the format used
// by the weighted_target policy. Note that the config should include
// an empty value for the "targets" field; that empty value will be
// replaced by one that is dynamically generated based on the EDS data.
// Optional; defaults to "weighted_target".
repeated LoadBalancingConfig locality_picking_policy = 4;
// Endpoint-picking policy.
// This will be configured as the policy for each child in the
// locality-policy's config.
// Optional; defaults to "round_robin".
repeated LoadBalancingConfig endpoint_picking_policy = 5;
}
// Configuration for ring_hash LB policy.
message RingHashLoadBalancingConfig {
// A client-side option will cap these values to 4096. If either of these
// values are greater than the client-side cap, they will be treated
// as the client-side cap value.
uint64 min_ring_size = 1; // Optional, defaults to 1024, max 8M.
uint64 max_ring_size = 2; // Optional, defaults to 4096, max 8M.
}
// Configuration for lrs LB policy.
message LrsLoadBalancingPolicyConfig {
// Cluster name. Required.
string cluster_name = 1;
// EDS service name, as returned in CDS.
// May be unset if not specified in CDS.
string eds_service_name = 2;
// Server to send load reports to. Required.
// If set to empty string, load reporting will be sent to the same
// server as we are getting xds data from.
string lrs_load_reporting_server_name = 3;
// The locality for which this policy will report load. Required.
message Locality {
string region = 1;
string zone = 2;
string subzone = 3;
}
Locality locality = 4;
// Endpoint-picking policy.
repeated LoadBalancingConfig child_policy = 5;
}
// Configuration for the xds_wrr_locality load balancing policy.
message XdsWrrLocalityLoadBalancingPolicyConfig {
repeated LoadBalancingConfig child_policy = 1;
}
// Configuration for the least_request LB policy.
message LeastRequestLocalityLoadBalancingPolicyConfig {
uint64 choice_count = 1;
}
// Configuration for the override_host LB policy.
message OverrideHostLoadBalancingPolicyConfig {
enum HealthStatus {
UNKNOWN = 0;
HEALTHY = 1;
DRAINING = 3;
}
// valid health status for hosts that are considered when using
// xds_override_host_experimental policy.
// Default is [UNKNOWN, HEALTHY]
repeated HealthStatus override_host_status = 1;
repeated LoadBalancingConfig child_policy = 2;
}
// Configuration for xds LB policy.
message XdsConfig {
// Name of balancer to connect to.
string balancer_name = 1 [deprecated = true];
// Optional. What LB policy to use for intra-locality routing.
// If unset, will use whatever algorithm is specified by the balancer.
// Multiple LB policies can be specified; clients will iterate through
// the list in order and stop at the first policy that they support.
repeated LoadBalancingConfig child_policy = 2;
// Optional. What LB policy to use in fallback mode. If not
// specified, defaults to round_robin.
// Multiple LB policies can be specified; clients will iterate through
// the list in order and stop at the first policy that they support.
repeated LoadBalancingConfig fallback_policy = 3;
// Optional. Name to use in EDS query. If not present, defaults to
// the server name from the target URI.
string eds_service_name = 4;
// LRS server to send load reports to.
// If not present, load reporting will be disabled.
// If set to the empty string, load reporting will be sent to the same
// server that we obtained CDS data from.
google.protobuf.StringValue lrs_load_reporting_server_name = 5;
}
// Selects LB policy and provides corresponding configuration.
//
// In general, all instances of this field should be repeated. Clients will
// iterate through the list in order and stop at the first policy that they
// support. This allows the service config to specify custom policies that may
// not be known to all clients.
//
// - If the config for the first supported policy is invalid, the whole service
// config is invalid.
// - If the list doesn't contain any supported policy, the whole service config
// is invalid.
message LoadBalancingConfig {
// Exactly one LB policy may be configured.
oneof policy {
// For each new LB policy supported by gRPC, a new field must be added
// here. The field's name must be the LB policy name and its type is a
// message that provides whatever configuration parameters are needed
// by the LB policy. The configuration message will be passed to the
// LB policy when it is instantiated on the client.
//
// If the LB policy does not require any configuration parameters, the
// message for that LB policy may be empty.
//
// Note that if an LB policy contains another nested LB policy
// (e.g., a gslb policy picks the cluster and then delegates to
// a round_robin policy to pick the backend within that cluster), its
// configuration message may include a nested instance of the
// LoadBalancingConfig message to configure the nested LB policy.
PickFirstConfig pick_first = 4 [json_name = "pick_first"];
RoundRobinConfig round_robin = 1 [json_name = "round_robin"];
WeightedRoundRobinLbConfig weighted_round_robin = 20
[json_name = "weighted_round_robin"];
// gRPC lookaside load balancing.
// This will eventually be deprecated by the new xDS-based local
// balancing policy.
GrpcLbConfig grpclb = 3;
// REMAINING POLICIES ARE EXPERIMENTAL -- DO NOT USE
PriorityLoadBalancingPolicyConfig priority_experimental = 9
[json_name = "priority_experimental"];
WeightedTargetLoadBalancingPolicyConfig weighted_target_experimental = 10
[json_name = "weighted_target_experimental"];
OutlierDetectionLoadBalancingConfig outlier_detection = 15
[json_name = "outlier_detection_experimental"];
RlsLoadBalancingPolicyConfig rls = 19 [json_name = "rls_experimental"];
// xDS-based load balancing.
XdsClusterManagerLoadBalancingPolicyConfig xds_cluster_manager_experimental
= 14 [json_name = "xds_cluster_manager_experimental"];
CdsConfig cds_experimental = 6 [json_name = "cds_experimental"];
XdsClusterResolverLoadBalancingPolicyConfig
xds_cluster_resolver_experimental = 11
[json_name = "xds_cluster_resolver_experimental"];
XdsClusterImplLoadBalancingPolicyConfig xds_cluster_impl_experimental = 12
[json_name = "xds_cluster_impl_experimental"];
OverrideHostLoadBalancingPolicyConfig override_host_experimental = 18
[json_name = "override_host_experimental"];
XdsWrrLocalityLoadBalancingPolicyConfig xds_wrr_locality_experimental = 16
[json_name = "xds_wrr_locality_experimental"];
RingHashLoadBalancingConfig ring_hash_experimental = 13
[json_name = "ring_hash_experimental"];
LeastRequestLocalityLoadBalancingPolicyConfig least_request_experimental =
17 [json_name = "least_request_experimental"];
// Deprecated xDS-related policies.
LrsLoadBalancingPolicyConfig lrs_experimental = 8
[json_name = "lrs_experimental", deprecated = true];
EdsLoadBalancingPolicyConfig eds_experimental = 7
[json_name = "eds_experimental", deprecated = true];
XdsConfig xds = 2 [deprecated = true];
XdsConfig xds_experimental = 5 [json_name = "xds_experimental",
deprecated = true];
// Next available ID: 21
}
}
// A ServiceConfig represents information about a service but is not specific to
// any name resolver.
message ServiceConfig {
// Load balancing policy.
//
// Note that load_balancing_policy is deprecated in favor of
// load_balancing_config; the former will be used only if the latter
// is unset.
//
// If no LB policy is configured here, then the default is pick_first.
// If the policy name is set via the client API, that value overrides
// the value specified here.
//
// If the deprecated load_balancing_policy field is used, note that if the
// resolver returns at least one balancer address (as opposed to backend
// addresses), gRPC will use grpclb (see
// https://github.com/grpc/grpc/blob/master/doc/load-balancing.md),
// regardless of what policy is configured here. However, if the resolver
// returns at least one backend address in addition to the balancer
// address(es), the client may fall back to the requested policy if it
// is unable to reach any of the grpclb load balancers.
enum LoadBalancingPolicy {
UNSPECIFIED = 0;
ROUND_ROBIN = 1;
}
LoadBalancingPolicy load_balancing_policy = 1 [deprecated = true];
// Multiple LB policies can be specified; clients will iterate through
// the list in order and stop at the first policy that they support. If none
// are supported, the service config is considered invalid.
repeated LoadBalancingConfig load_balancing_config = 4;
// Per-method configuration.
repeated MethodConfig method_config = 2;
// If a RetryThrottlingPolicy is provided, gRPC will automatically throttle
// retry attempts and hedged RPCs when the client's ratio of failures to
// successes exceeds a threshold.
//
// For each server name, the gRPC client will maintain a token_count which is
// initially set to max_tokens. Every outgoing RPC (regardless of service or
// method invoked) will change token_count as follows:
//
// - Every failed RPC will decrement the token_count by 1.
// - Every successful RPC will increment the token_count by token_ratio.
//
// If token_count is less than or equal to max_tokens / 2, then RPCs will not
// be retried and hedged RPCs will not be sent.
message RetryThrottlingPolicy {
// The number of tokens starts at max_tokens. The token_count will always be
// between 0 and max_tokens.
//
// This field is required and must be greater than zero.
uint32 max_tokens = 1;
// The amount of tokens to add on each successful RPC. Typically this will
// be some number between 0 and 1, e.g., 0.1.
//
// This field is required and must be greater than zero. Up to 3 decimal
// places are supported.
float token_ratio = 2;
}
RetryThrottlingPolicy retry_throttling = 3;
message HealthCheckConfig {
// Service name to use in the health-checking request.
google.protobuf.StringValue service_name = 1;
}
HealthCheckConfig health_check_config = 5;
// next available tag: 6
}

@ -0,0 +1,47 @@
# Copyright 2023 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.
load("//bazel:grpc_build_system.bzl", "grpc_package")
load("//test/core/util:grpc_fuzzer.bzl", "grpc_proto_fuzzer")
grpc_package(name = "test/core/ext/filters/event_engine_client_channel_resolver")
licenses(["notice"])
grpc_proto_fuzzer(
name = "resolver_fuzzer",
size = "enormous",
srcs = ["resolver_fuzzer.cc"],
corpus = "resolver_fuzzer_corpus",
language = "C++",
proto = "resolver_ops.proto",
proto_deps = [
"//src/proto/grpc/service_config:service_config_proto",
],
tags = [
"no_mac",
"no_windows",
],
uses_event_engine = False,
uses_polling = False,
deps = [
"//:gpr",
"//:grpc",
"//src/core:channel_args",
"//test/core/event_engine:aborting_event_engine",
"//test/core/event_engine/fuzzing_event_engine",
"//test/core/util:grpc_test_util",
"//test/core/util:grpc_test_util_base",
],
)

@ -0,0 +1,282 @@
// Copyright 2023 The 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 <grpc/support/port_platform.h>
#include <algorithm>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "google/protobuf/json/json.h"
#include <grpc/event_engine/event_engine.h>
#include <grpc/grpc.h>
#include <grpc/support/log.h>
#include "src/core/ext/filters/client_channel/resolver/dns/event_engine/event_engine_client_channel_resolver.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/event_engine/tcp_socket_utils.h"
#include "src/core/lib/gprpp/crash.h"
#include "src/core/lib/gprpp/debug_location.h"
#include "src/core/lib/gprpp/notification.h"
#include "src/core/lib/gprpp/orphanable.h"
#include "src/core/lib/gprpp/work_serializer.h"
#include "src/core/lib/resolver/resolver.h"
#include "src/core/lib/resolver/resolver_factory.h"
#include "src/core/lib/uri/uri_parser.h"
#include "src/libfuzzer/libfuzzer_macro.h"
#include "src/proto/grpc/service_config/service_config.pb.h"
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h"
#include "test/core/event_engine/util/aborting_event_engine.h"
#include "test/core/ext/filters/event_engine_client_channel_resolver/resolver_ops.pb.h"
// TODO(hork): exercise Orphan on the client channel resolver, which will
// exercise the resolution cancellation path. Currently, all requests will get
// responses.
bool squelch = true;
namespace {
using event_engine_client_channel_resolver::TXTRecordType;
using grpc_core::EventEngineClientChannelDNSResolverFactory;
using grpc_event_engine::experimental::EventEngine;
using grpc_event_engine::experimental::FuzzingEventEngine;
using grpc_event_engine::experimental::URIToResolvedAddress;
constexpr char g_grpc_config_prefix[] = "grpc_config=";
absl::Status ErrorToAbslStatus(
const event_engine_client_channel_resolver::Error& error) {
// clamp error.code() in (0, 16]
return absl::Status(static_cast<absl::StatusCode>(error.code() % 16 + 1),
error.message());
}
class FuzzingResolverEventEngine
: public grpc_event_engine::experimental::AbortingEventEngine {
public:
explicit FuzzingResolverEventEngine(
const event_engine_client_channel_resolver::Msg& msg)
: runner_(FuzzingEventEngine::Options(),
fuzzing_event_engine::Actions()) {
// Set hostname responses
if (msg.has_hostname_error()) {
hostname_responses_ = ErrorToAbslStatus(msg.hostname_error());
} else if (msg.has_hostname_response()) {
hostname_responses_.emplace();
for (const auto& address : msg.hostname_response().addresses()) {
hostname_responses_->emplace_back(*URIToResolvedAddress(
absl::StrCat("ipv4:127.0.0.1:", address.port() % 65535)));
}
}
// Set SRV Responses
if (msg.has_srv_error()) {
srv_responses_ = ErrorToAbslStatus(msg.srv_error());
} else if (msg.has_srv_response()) {
srv_responses_.emplace();
for (const auto& srv_record : msg.srv_response().srv_records()) {
EventEngine::DNSResolver::SRVRecord final_r;
final_r.host = srv_record.host();
final_r.port = srv_record.port();
final_r.priority = srv_record.priority();
final_r.weight = srv_record.weight();
srv_responses_->emplace_back(final_r);
}
}
// Set TXT Responses
if (msg.has_txt_error()) {
txt_responses_ = ErrorToAbslStatus(msg.txt_error());
} else if (msg.has_txt_response()) {
txt_responses_.emplace();
for (const auto& txt_record : msg.txt_response().txt_records()) {
if (txt_record.has_enumerated_value()) {
switch (txt_record.enumerated_value()) {
case TXTRecordType::TXT_VALID:
txt_responses_->emplace_back(txt_valid_config_);
break;
case TXTRecordType::TXT_RANDOM_NON_CONFIG:
txt_responses_->emplace_back(txt_record.arbitrary_value());
break;
case TXTRecordType::TXT_RANDOM_PREFIXED_CONFIG:
txt_responses_->emplace_back(absl::StrCat(
g_grpc_config_prefix, txt_record.arbitrary_value()));
break;
default:
grpc_core::Crash("Invalid txt record type");
}
} else if (txt_record.has_fuzzed_service_config()) {
std::string fuzzed_config_json_str;
::google::protobuf::json::PrintOptions print_options;
auto status =
MessageToJsonString(txt_record.fuzzed_service_config(),
&fuzzed_config_json_str, print_options);
// Sometimes LLVM will generate protos that can't be dumped to JSON
// (Durations out of bounds, for example). These are ignored.
if (status.ok()) {
txt_responses_->emplace_back(fuzzed_config_json_str);
}
}
}
}
}
std::unique_ptr<DNSResolver> GetDNSResolver(
const DNSResolver::ResolverOptions& /* options */) override {
return std::make_unique<FuzzingDNSResolver>(this);
}
void Tick() { runner_.Tick(); }
private:
class FuzzingDNSResolver : public DNSResolver {
public:
explicit FuzzingDNSResolver(FuzzingResolverEventEngine* engine)
: engine_(engine) {}
LookupTaskHandle LookupHostname(LookupHostnameCallback on_resolve,
absl::string_view /* name */,
absl::string_view /* default_port */,
Duration /* timeout */) override {
auto finish =
[cb = std::move(on_resolve), runner = &engine_->runner_](
absl::StatusOr<std::vector<ResolvedAddress>> response) mutable {
runner->Run(
[cb = std::move(cb), response = std::move(response)]() mutable {
cb(response);
});
return EventEngine::DNSResolver::LookupTaskHandle::kInvalid;
};
return finish(engine_->hostname_responses_);
}
LookupTaskHandle LookupSRV(LookupSRVCallback on_resolve,
absl::string_view /* name */,
Duration /* timeout */) override {
auto finish =
[cb = std::move(on_resolve), runner = &engine_->runner_](
absl::StatusOr<std::vector<SRVRecord>> response) mutable {
runner->Run(
[cb = std::move(cb), response = std::move(response)]() mutable {
cb(response);
});
return EventEngine::DNSResolver::LookupTaskHandle::kInvalid;
};
return finish(engine_->srv_responses_);
}
LookupTaskHandle LookupTXT(LookupTXTCallback on_resolve,
absl::string_view /* name */,
Duration /* timeout */) override {
auto finish =
[cb = std::move(on_resolve), runner = &engine_->runner_](
absl::StatusOr<std::vector<std::string>> response) mutable {
runner->Run(
[cb = std::move(cb), response = std::move(response)]() mutable {
cb(response);
});
return EventEngine::DNSResolver::LookupTaskHandle::kInvalid;
};
return finish(engine_->txt_responses_);
}
bool CancelLookup(LookupTaskHandle /* handle */) override { return false; }
private:
FuzzingResolverEventEngine* engine_;
};
// members
FuzzingEventEngine runner_;
// responses
absl::StatusOr<std::vector<EventEngine::ResolvedAddress>> hostname_responses_;
absl::StatusOr<std::vector<EventEngine::DNSResolver::SRVRecord>>
srv_responses_;
absl::StatusOr<std::vector<std::string>> txt_responses_;
// base case for a valid gRPC config
const std::string txt_valid_config_ =
"grpc_config=[{\"serviceConfig\":{\"loadBalancingPolicy\":\"round_"
"robin\",\"methodConfig\":[{\"name\":[{\"method\":\"Foo\",\"service\":"
"\"SimpleService\"}],\"waitForReady\":true}]}}]";
};
grpc_core::ChannelArgs ConstructChannelArgs(
const event_engine_client_channel_resolver::Msg& msg,
std::shared_ptr<FuzzingResolverEventEngine> engine) {
// put the engine in channel args
return grpc_core::ChannelArgs()
.SetObject<EventEngine>(std::move(engine))
.Set(GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION,
!msg.resolver_args().request_service_config())
.Set(GRPC_ARG_DNS_ENABLE_SRV_QUERIES,
msg.resolver_args().enable_srv_queries());
}
class FuzzingResultHandler : public grpc_core::Resolver::ResultHandler {
public:
explicit FuzzingResultHandler(grpc_core::Notification* signal)
: signal_(signal) {}
void ReportResult(grpc_core::Resolver::Result /* result */) override {
signal_->Notify();
}
private:
grpc_core::Notification* signal_;
};
grpc_core::ResolverArgs ConstructResolverArgs(
const grpc_core::ChannelArgs& channel_args,
grpc_core::Notification* result_handler_notification,
std::shared_ptr<grpc_core::WorkSerializer> work_serializer) {
grpc_core::ResolverArgs resolver_args;
auto uri = grpc_core::URI::Parse("dns:localhost");
GPR_ASSERT(uri.ok());
resolver_args.uri = *uri;
resolver_args.args = channel_args;
resolver_args.pollset_set = nullptr;
resolver_args.work_serializer = std::move(work_serializer);
auto result_handler =
std::make_unique<FuzzingResultHandler>(result_handler_notification);
resolver_args.result_handler = std::move(result_handler);
return resolver_args;
}
} // namespace
DEFINE_PROTO_FUZZER(const event_engine_client_channel_resolver::Msg& msg) {
auto engine = std::make_shared<FuzzingResolverEventEngine>(msg);
auto channel_args = ConstructChannelArgs(msg, engine);
grpc_core::Notification result_handler_notification;
auto work_serializer = std::make_shared<grpc_core::WorkSerializer>();
auto resolver_args = ConstructResolverArgs(
channel_args, &result_handler_notification, work_serializer);
EventEngineClientChannelDNSResolverFactory resolver_factory;
auto resolver = resolver_factory.CreateResolver(std::move(resolver_args));
work_serializer->Run([resolver = resolver.get()]()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(
*work_serializer) { resolver->StartLocked(); },
DEBUG_LOCATION);
// wait for result (no need to check validity)
do {
engine->Tick();
} while (!result_handler_notification.WaitForNotificationWithTimeout(
absl::Milliseconds(33)));
}

@ -0,0 +1,23 @@
resolver_args {
request_service_config: true
enable_srv_queries: true
}
hostname_response {
}
txt_response {
txt_records {
fuzzed_service_config {
load_balancing_policy: ROUND_ROBIN
method_config {
timeout {
seconds: 8679573376966066176
}
max_response_message_bytes {
value: 10
}
}
retry_throttling {
}
}
}
}

@ -0,0 +1,10 @@
resolver_args {
request_service_config: true
}
hostname_response {
}
txt_response {
txt_records {
enumerated_value: TXT_RANDOM_PREFIXED_CONFIG
}
}

@ -0,0 +1,8 @@
resolver_args {
request_service_config: true
}
txt_response {
txt_records {
enumerated_value: TXT_RANDOM_PREFIXED_CONFIG
}
}

@ -0,0 +1,16 @@
resolver_args {
enable_srv_queries: true
}
hostname_response {
addresses {
port: 32
}
addresses {
port: 32
}
addresses {
port: 1996488736
}
}
srv_response {
}

@ -0,0 +1,80 @@
// 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.
syntax = "proto3";
import "src/proto/grpc/service_config/service_config.proto";
package event_engine_client_channel_resolver;
message ResolverArgs {
bool request_service_config = 2;
bool enable_srv_queries = 3;
}
message Error {
uint32 code = 1;
string message = 2;
}
message ResolvedAddress {
uint32 port = 1;
}
message ResolvedAddresses {
repeated ResolvedAddress addresses = 1;
}
message SRVRecord {
string host = 1;
uint32 port = 2;
uint32 priority = 3;
uint32 weight = 4;
}
message SRVRecords {
repeated SRVRecord srv_records = 1;
}
enum TXTRecordType {
TXT_VALID = 0;
TXT_RANDOM_NON_CONFIG = 1;
TXT_RANDOM_PREFIXED_CONFIG = 2;
}
message TXTRecord {
oneof record {
TXTRecordType enumerated_value = 1;
grpc.service_config.ServiceConfig fuzzed_service_config = 2;
}
// Random TXT record content, used for some TXTRecordTypes.
string arbitrary_value = 3;
}
message TXTRecords {
repeated TXTRecord txt_records = 1;
}
message Msg {
ResolverArgs resolver_args = 1;
oneof hostname {
ResolvedAddresses hostname_response = 2;
Error hostname_error = 3;
}
oneof srv {
SRVRecords srv_response = 4;
Error srv_error = 5;
}
oneof txt {
TXTRecords txt_response = 6;
Error txt_error = 7;
}
// TODO(hork): consider if cancellation responses can be fuzzed
}
Loading…
Cancel
Save