Update google.golang.org/grpc to address vulnerability: GO-2026-6061 (#551)

The weekly govulncheck run on main is failing:
[GO-2026-6061](https://pkg.go.dev/vuln/GO-2026-6061) (vulnerabilities in
the xDS RBAC engine and the HTTP/2 transport server) in
google.golang.org/grpc v1.81.0.

Bump grpc to the fixed v1.82.
This commit is contained in:
Haven Xia
2026-07-27 14:51:28 -04:00
committed by GitHub
parent 3e6da2eb29
commit 1afaaf5725
67 changed files with 2550 additions and 604 deletions
+5 -5
View File
@@ -48,8 +48,8 @@ require (
golang.org/x/sync v0.21.0
golang.org/x/sys v0.46.0
google.golang.org/api v0.274.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d
google.golang.org/grpc v1.81.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
k8s.io/api v0.36.1
k8s.io/apiextensions-apiserver v0.36.1
@@ -67,7 +67,7 @@ require (
cloud.google.com/go/auth v0.19.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/longrunning v0.9.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef // indirect
@@ -165,7 +165,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeromq/goczmq v4.1.0+incompatible // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
@@ -180,7 +180,7 @@ require (
golang.org/x/time v0.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gotest.tools/v3 v3.5.2 // indirect
+10 -10
View File
@@ -26,8 +26,8 @@ cloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYX
cloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
@@ -331,8 +331,8 @@ github.com/zeromq/goczmq v4.1.0+incompatible h1:cGVQaU6kIwwrGso0Pgbl84tzAz/h7FJ3
github.com/zeromq/goczmq v4.1.0+incompatible/go.mod h1:1uZybAJoSRCvZMH2rZxEwWBSmC4T7CB/xQOfChwPEzg=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
@@ -403,12 +403,12 @@ google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA=
google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -0,0 +1,371 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.10
// protoc v6.33.2
// source: envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto
package gcp_authnv3
import (
_ "github.com/cncf/xds/go/udpa/annotations"
_ "github.com/envoyproxy/go-control-plane/envoy/annotations"
v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
_ "github.com/envoyproxy/protoc-gen-validate/validate"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Filter configuration.
// [#next-free-field: 7]
type GcpAuthnFilterConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The HTTP URI to fetch tokens from GCE Metadata Server(https://cloud.google.com/compute/docs/metadata/overview).
// The URL format is "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=[AUDIENCE]"
//
// This field is deprecated because it does not match the API surface provided by the google auth libraries.
// Control planes should not attempt to override the metadata server URI.
// The cluster and timeout can be configured using the “cluster“ and “timeout“ fields instead.
// For backward compatibility, the cluster and timeout configured in this field will be used
// if the new “cluster“ and “timeout“ fields are not set.
//
// Deprecated: Marked as deprecated in envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto.
HttpUri *v3.HttpUri `protobuf:"bytes,1,opt,name=http_uri,json=httpUri,proto3" json:"http_uri,omitempty"`
// Retry policy for fetching tokens.
// Not supported by all data planes.
RetryPolicy *v3.RetryPolicy `protobuf:"bytes,2,opt,name=retry_policy,json=retryPolicy,proto3" json:"retry_policy,omitempty"`
// Token cache configuration. This field is optional.
CacheConfig *TokenCacheConfig `protobuf:"bytes,3,opt,name=cache_config,json=cacheConfig,proto3" json:"cache_config,omitempty"`
// Request header location to extract the token. By default (i.e. if this field is not specified), the token
// is extracted to the Authorization HTTP header, in the format "Authorization: Bearer <token>".
// Not supported by all data planes.
TokenHeader *TokenHeader `protobuf:"bytes,4,opt,name=token_header,json=tokenHeader,proto3" json:"token_header,omitempty"`
// Cluster to send traffic to the GCE metadata server. Not supported
// by all data planes; a data plane may instead have its own mechanism
// for contacting the metadata server.
Cluster string `protobuf:"bytes,5,opt,name=cluster,proto3" json:"cluster,omitempty"`
// Timeout for fetching the tokens from the GCE metadata server.
// Not supported by all data planes.
Timeout *durationpb.Duration `protobuf:"bytes,6,opt,name=timeout,proto3" json:"timeout,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GcpAuthnFilterConfig) Reset() {
*x = GcpAuthnFilterConfig{}
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GcpAuthnFilterConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GcpAuthnFilterConfig) ProtoMessage() {}
func (x *GcpAuthnFilterConfig) ProtoReflect() protoreflect.Message {
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GcpAuthnFilterConfig.ProtoReflect.Descriptor instead.
func (*GcpAuthnFilterConfig) Descriptor() ([]byte, []int) {
return file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescGZIP(), []int{0}
}
// Deprecated: Marked as deprecated in envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto.
func (x *GcpAuthnFilterConfig) GetHttpUri() *v3.HttpUri {
if x != nil {
return x.HttpUri
}
return nil
}
func (x *GcpAuthnFilterConfig) GetRetryPolicy() *v3.RetryPolicy {
if x != nil {
return x.RetryPolicy
}
return nil
}
func (x *GcpAuthnFilterConfig) GetCacheConfig() *TokenCacheConfig {
if x != nil {
return x.CacheConfig
}
return nil
}
func (x *GcpAuthnFilterConfig) GetTokenHeader() *TokenHeader {
if x != nil {
return x.TokenHeader
}
return nil
}
func (x *GcpAuthnFilterConfig) GetCluster() string {
if x != nil {
return x.Cluster
}
return ""
}
func (x *GcpAuthnFilterConfig) GetTimeout() *durationpb.Duration {
if x != nil {
return x.Timeout
}
return nil
}
// Audience is the URL of the receiving service that performs token authentication.
// It will be provided to the filter through cluster's typed_filter_metadata.
type Audience struct {
state protoimpl.MessageState `protogen:"open.v1"`
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Audience) Reset() {
*x = Audience{}
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Audience) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Audience) ProtoMessage() {}
func (x *Audience) ProtoReflect() protoreflect.Message {
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Audience.ProtoReflect.Descriptor instead.
func (*Audience) Descriptor() ([]byte, []int) {
return file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescGZIP(), []int{1}
}
func (x *Audience) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
// Token Cache configuration.
type TokenCacheConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The number of cache entries. The maximum number of entries is INT64_MAX as it is constrained by underlying cache implementation.
// Default value 0 (i.e., proto3 defaults) disables the cache by default. Other default values will enable the cache.
CacheSize *wrapperspb.UInt64Value `protobuf:"bytes,1,opt,name=cache_size,json=cacheSize,proto3" json:"cache_size,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TokenCacheConfig) Reset() {
*x = TokenCacheConfig{}
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TokenCacheConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TokenCacheConfig) ProtoMessage() {}
func (x *TokenCacheConfig) ProtoReflect() protoreflect.Message {
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TokenCacheConfig.ProtoReflect.Descriptor instead.
func (*TokenCacheConfig) Descriptor() ([]byte, []int) {
return file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescGZIP(), []int{2}
}
func (x *TokenCacheConfig) GetCacheSize() *wrapperspb.UInt64Value {
if x != nil {
return x.CacheSize
}
return nil
}
type TokenHeader struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The HTTP header's name.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The header's prefix. The format is "value_prefix<token>"
// For example, for "Authorization: Bearer <token>", value_prefix="Bearer " with a space at the
// end.
ValuePrefix string `protobuf:"bytes,2,opt,name=value_prefix,json=valuePrefix,proto3" json:"value_prefix,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TokenHeader) Reset() {
*x = TokenHeader{}
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TokenHeader) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TokenHeader) ProtoMessage() {}
func (x *TokenHeader) ProtoReflect() protoreflect.Message {
mi := &file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TokenHeader.ProtoReflect.Descriptor instead.
func (*TokenHeader) Descriptor() ([]byte, []int) {
return file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescGZIP(), []int{3}
}
func (x *TokenHeader) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *TokenHeader) GetValuePrefix() string {
if x != nil {
return x.ValuePrefix
}
return ""
}
var File_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto protoreflect.FileDescriptor
const file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDesc = "" +
"\n" +
":envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto\x12*envoy.extensions.filters.http.gcp_authn.v3\x1a\x1fenvoy/config/core/v3/base.proto\x1a#envoy/config/core/v3/http_uri.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a#envoy/annotations/deprecation.proto\x1a\x1dudpa/annotations/status.proto\x1a\x17validate/validate.proto\"\xc1\x03\n" +
"\x14GcpAuthnFilterConfig\x12E\n" +
"\bhttp_uri\x18\x01 \x01(\v2\x1d.envoy.config.core.v3.HttpUriB\v\x92dž\xd8\x04\x033.0\x18\x01R\ahttpUri\x12D\n" +
"\fretry_policy\x18\x02 \x01(\v2!.envoy.config.core.v3.RetryPolicyR\vretryPolicy\x12_\n" +
"\fcache_config\x18\x03 \x01(\v2<.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfigR\vcacheConfig\x12Z\n" +
"\ftoken_header\x18\x04 \x01(\v27.envoy.extensions.filters.http.gcp_authn.v3.TokenHeaderR\vtokenHeader\x12\x18\n" +
"\acluster\x18\x05 \x01(\tR\acluster\x12E\n" +
"\atimeout\x18\x06 \x01(\v2\x19.google.protobuf.DurationB\x10\xfaB\r\xaa\x01\n" +
"\x1a\x06\b\x80\x80\x80\x80\x102\x00R\atimeout\"%\n" +
"\bAudience\x12\x19\n" +
"\x03url\x18\x01 \x01(\tB\a\xfaB\x04r\x02\x10\x01R\x03url\"`\n" +
"\x10TokenCacheConfig\x12L\n" +
"\n" +
"cache_size\x18\x01 \x01(\v2\x1c.google.protobuf.UInt64ValueB\x0f\xfaB\f2\n" +
"\x18\xff\xff\xff\xff\xff\xff\xff\xff\x7fR\tcacheSize\"`\n" +
"\vTokenHeader\x12!\n" +
"\x04name\x18\x01 \x01(\tB\r\xfaB\n" +
"r\b\x10\x01\xc8\x01\x00\xc0\x01\x01R\x04name\x12.\n" +
"\fvalue_prefix\x18\x02 \x01(\tB\v\xfaB\br\x06\xc8\x01\x00\xc0\x01\x02R\vvaluePrefixB\xb2\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" +
"8io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3B\rGcpAuthnProtoP\x01Z]github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/gcp_authn/v3;gcp_authnv3b\x06proto3"
var (
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescOnce sync.Once
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescData []byte
)
func file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescGZIP() []byte {
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescOnce.Do(func() {
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDesc), len(file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDesc)))
})
return file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDescData
}
var file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_goTypes = []any{
(*GcpAuthnFilterConfig)(nil), // 0: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig
(*Audience)(nil), // 1: envoy.extensions.filters.http.gcp_authn.v3.Audience
(*TokenCacheConfig)(nil), // 2: envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig
(*TokenHeader)(nil), // 3: envoy.extensions.filters.http.gcp_authn.v3.TokenHeader
(*v3.HttpUri)(nil), // 4: envoy.config.core.v3.HttpUri
(*v3.RetryPolicy)(nil), // 5: envoy.config.core.v3.RetryPolicy
(*durationpb.Duration)(nil), // 6: google.protobuf.Duration
(*wrapperspb.UInt64Value)(nil), // 7: google.protobuf.UInt64Value
}
var file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_depIdxs = []int32{
4, // 0: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig.http_uri:type_name -> envoy.config.core.v3.HttpUri
5, // 1: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig.retry_policy:type_name -> envoy.config.core.v3.RetryPolicy
2, // 2: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig.cache_config:type_name -> envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig
3, // 3: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig.token_header:type_name -> envoy.extensions.filters.http.gcp_authn.v3.TokenHeader
6, // 4: envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig.timeout:type_name -> google.protobuf.Duration
7, // 5: envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig.cache_size:type_name -> google.protobuf.UInt64Value
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_init() }
func file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_init() {
if File_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDesc), len(file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_rawDesc)),
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_goTypes,
DependencyIndexes: file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_depIdxs,
MessageInfos: file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_msgTypes,
}.Build()
File_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto = out.File
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_goTypes = nil
file_envoy_extensions_filters_http_gcp_authn_v3_gcp_authn_proto_depIdxs = nil
}
@@ -0,0 +1,649 @@
//go:build !disable_pgv
// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto
package gcp_authnv3
import (
"bytes"
"errors"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
"google.golang.org/protobuf/types/known/anypb"
)
// ensure the imports are used
var (
_ = bytes.MinRead
_ = errors.New("")
_ = fmt.Print
_ = utf8.UTFMax
_ = (*regexp.Regexp)(nil)
_ = (*strings.Reader)(nil)
_ = net.IPv4len
_ = time.Duration(0)
_ = (*url.URL)(nil)
_ = (*mail.Address)(nil)
_ = anypb.Any{}
_ = sort.Sort
)
// Validate checks the field values on GcpAuthnFilterConfig with the rules
// defined in the proto definition for this message. If any rules are
// violated, the first error encountered is returned, or nil if there are no violations.
func (m *GcpAuthnFilterConfig) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on GcpAuthnFilterConfig with the rules
// defined in the proto definition for this message. If any rules are
// violated, the result is a list of violation errors wrapped in
// GcpAuthnFilterConfigMultiError, or nil if none found.
func (m *GcpAuthnFilterConfig) ValidateAll() error {
return m.validate(true)
}
func (m *GcpAuthnFilterConfig) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
if all {
switch v := interface{}(m.GetHttpUri()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "HttpUri",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "HttpUri",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetHttpUri()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return GcpAuthnFilterConfigValidationError{
field: "HttpUri",
reason: "embedded message failed validation",
cause: err,
}
}
}
if all {
switch v := interface{}(m.GetRetryPolicy()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "RetryPolicy",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "RetryPolicy",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetRetryPolicy()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return GcpAuthnFilterConfigValidationError{
field: "RetryPolicy",
reason: "embedded message failed validation",
cause: err,
}
}
}
if all {
switch v := interface{}(m.GetCacheConfig()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "CacheConfig",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "CacheConfig",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetCacheConfig()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return GcpAuthnFilterConfigValidationError{
field: "CacheConfig",
reason: "embedded message failed validation",
cause: err,
}
}
}
if all {
switch v := interface{}(m.GetTokenHeader()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "TokenHeader",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, GcpAuthnFilterConfigValidationError{
field: "TokenHeader",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetTokenHeader()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return GcpAuthnFilterConfigValidationError{
field: "TokenHeader",
reason: "embedded message failed validation",
cause: err,
}
}
}
// no validation rules for Cluster
if d := m.GetTimeout(); d != nil {
dur, err := d.AsDuration(), d.CheckValid()
if err != nil {
err = GcpAuthnFilterConfigValidationError{
field: "Timeout",
reason: "value is not a valid duration",
cause: err,
}
if !all {
return err
}
errors = append(errors, err)
} else {
lt := time.Duration(4294967296*time.Second + 0*time.Nanosecond)
gte := time.Duration(0*time.Second + 0*time.Nanosecond)
if dur < gte || dur >= lt {
err := GcpAuthnFilterConfigValidationError{
field: "Timeout",
reason: "value must be inside range [0s, 1193046h28m16s)",
}
if !all {
return err
}
errors = append(errors, err)
}
}
}
if len(errors) > 0 {
return GcpAuthnFilterConfigMultiError(errors)
}
return nil
}
// GcpAuthnFilterConfigMultiError is an error wrapping multiple validation
// errors returned by GcpAuthnFilterConfig.ValidateAll() if the designated
// constraints aren't met.
type GcpAuthnFilterConfigMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m GcpAuthnFilterConfigMultiError) Error() string {
msgs := make([]string, 0, len(m))
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m GcpAuthnFilterConfigMultiError) AllErrors() []error { return m }
// GcpAuthnFilterConfigValidationError is the validation error returned by
// GcpAuthnFilterConfig.Validate if the designated constraints aren't met.
type GcpAuthnFilterConfigValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e GcpAuthnFilterConfigValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e GcpAuthnFilterConfigValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e GcpAuthnFilterConfigValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e GcpAuthnFilterConfigValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e GcpAuthnFilterConfigValidationError) ErrorName() string {
return "GcpAuthnFilterConfigValidationError"
}
// Error satisfies the builtin error interface
func (e GcpAuthnFilterConfigValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sGcpAuthnFilterConfig.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = GcpAuthnFilterConfigValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = GcpAuthnFilterConfigValidationError{}
// Validate checks the field values on Audience with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *Audience) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on Audience with the rules defined in
// the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in AudienceMultiError, or nil
// if none found.
func (m *Audience) ValidateAll() error {
return m.validate(true)
}
func (m *Audience) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
if utf8.RuneCountInString(m.GetUrl()) < 1 {
err := AudienceValidationError{
field: "Url",
reason: "value length must be at least 1 runes",
}
if !all {
return err
}
errors = append(errors, err)
}
if len(errors) > 0 {
return AudienceMultiError(errors)
}
return nil
}
// AudienceMultiError is an error wrapping multiple validation errors returned
// by Audience.ValidateAll() if the designated constraints aren't met.
type AudienceMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m AudienceMultiError) Error() string {
msgs := make([]string, 0, len(m))
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m AudienceMultiError) AllErrors() []error { return m }
// AudienceValidationError is the validation error returned by
// Audience.Validate if the designated constraints aren't met.
type AudienceValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e AudienceValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e AudienceValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e AudienceValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e AudienceValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e AudienceValidationError) ErrorName() string { return "AudienceValidationError" }
// Error satisfies the builtin error interface
func (e AudienceValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sAudience.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = AudienceValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = AudienceValidationError{}
// Validate checks the field values on TokenCacheConfig with the rules defined
// in the proto definition for this message. If any rules are violated, the
// first error encountered is returned, or nil if there are no violations.
func (m *TokenCacheConfig) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on TokenCacheConfig with the rules
// defined in the proto definition for this message. If any rules are
// violated, the result is a list of violation errors wrapped in
// TokenCacheConfigMultiError, or nil if none found.
func (m *TokenCacheConfig) ValidateAll() error {
return m.validate(true)
}
func (m *TokenCacheConfig) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
if wrapper := m.GetCacheSize(); wrapper != nil {
if wrapper.GetValue() > 9223372036854775807 {
err := TokenCacheConfigValidationError{
field: "CacheSize",
reason: "value must be less than or equal to 9223372036854775807",
}
if !all {
return err
}
errors = append(errors, err)
}
}
if len(errors) > 0 {
return TokenCacheConfigMultiError(errors)
}
return nil
}
// TokenCacheConfigMultiError is an error wrapping multiple validation errors
// returned by TokenCacheConfig.ValidateAll() if the designated constraints
// aren't met.
type TokenCacheConfigMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m TokenCacheConfigMultiError) Error() string {
msgs := make([]string, 0, len(m))
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m TokenCacheConfigMultiError) AllErrors() []error { return m }
// TokenCacheConfigValidationError is the validation error returned by
// TokenCacheConfig.Validate if the designated constraints aren't met.
type TokenCacheConfigValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e TokenCacheConfigValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e TokenCacheConfigValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e TokenCacheConfigValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e TokenCacheConfigValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e TokenCacheConfigValidationError) ErrorName() string { return "TokenCacheConfigValidationError" }
// Error satisfies the builtin error interface
func (e TokenCacheConfigValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sTokenCacheConfig.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = TokenCacheConfigValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = TokenCacheConfigValidationError{}
// Validate checks the field values on TokenHeader with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *TokenHeader) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on TokenHeader with the rules defined in
// the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in TokenHeaderMultiError, or
// nil if none found.
func (m *TokenHeader) ValidateAll() error {
return m.validate(true)
}
func (m *TokenHeader) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
if utf8.RuneCountInString(m.GetName()) < 1 {
err := TokenHeaderValidationError{
field: "Name",
reason: "value length must be at least 1 runes",
}
if !all {
return err
}
errors = append(errors, err)
}
if !_TokenHeader_Name_Pattern.MatchString(m.GetName()) {
err := TokenHeaderValidationError{
field: "Name",
reason: "value does not match regex pattern \"^[^\\x00\\n\\r]*$\"",
}
if !all {
return err
}
errors = append(errors, err)
}
if !_TokenHeader_ValuePrefix_Pattern.MatchString(m.GetValuePrefix()) {
err := TokenHeaderValidationError{
field: "ValuePrefix",
reason: "value does not match regex pattern \"^[^\\x00\\n\\r]*$\"",
}
if !all {
return err
}
errors = append(errors, err)
}
if len(errors) > 0 {
return TokenHeaderMultiError(errors)
}
return nil
}
// TokenHeaderMultiError is an error wrapping multiple validation errors
// returned by TokenHeader.ValidateAll() if the designated constraints aren't met.
type TokenHeaderMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m TokenHeaderMultiError) Error() string {
msgs := make([]string, 0, len(m))
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m TokenHeaderMultiError) AllErrors() []error { return m }
// TokenHeaderValidationError is the validation error returned by
// TokenHeader.Validate if the designated constraints aren't met.
type TokenHeaderValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e TokenHeaderValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e TokenHeaderValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e TokenHeaderValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e TokenHeaderValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e TokenHeaderValidationError) ErrorName() string { return "TokenHeaderValidationError" }
// Error satisfies the builtin error interface
func (e TokenHeaderValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sTokenHeader.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = TokenHeaderValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = TokenHeaderValidationError{}
var _TokenHeader_Name_Pattern = regexp.MustCompile("^[^\x00\n\r]*$")
var _TokenHeader_ValuePrefix_Pattern = regexp.MustCompile("^[^\x00\n\r]*$")
@@ -0,0 +1,358 @@
//go:build vtprotobuf
// +build vtprotobuf
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// source: envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.proto
package gcp_authnv3
import (
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
durationpb "github.com/planetscale/vtprotobuf/types/known/durationpb"
wrapperspb "github.com/planetscale/vtprotobuf/types/known/wrapperspb"
proto "google.golang.org/protobuf/proto"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *GcpAuthnFilterConfig) MarshalVTStrict() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GcpAuthnFilterConfig) MarshalToVTStrict(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVTStrict(dAtA[:size])
}
func (m *GcpAuthnFilterConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Timeout != nil {
size, err := (*durationpb.Duration)(m.Timeout).MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0x32
}
if len(m.Cluster) > 0 {
i -= len(m.Cluster)
copy(dAtA[i:], m.Cluster)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Cluster)))
i--
dAtA[i] = 0x2a
}
if m.TokenHeader != nil {
size, err := m.TokenHeader.MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0x22
}
if m.CacheConfig != nil {
size, err := m.CacheConfig.MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0x1a
}
if m.RetryPolicy != nil {
if vtmsg, ok := interface{}(m.RetryPolicy).(interface {
MarshalToSizedBufferVTStrict([]byte) (int, error)
}); ok {
size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
} else {
encoded, err := proto.Marshal(m.RetryPolicy)
if err != nil {
return 0, err
}
i -= len(encoded)
copy(dAtA[i:], encoded)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
}
i--
dAtA[i] = 0x12
}
if m.HttpUri != nil {
if vtmsg, ok := interface{}(m.HttpUri).(interface {
MarshalToSizedBufferVTStrict([]byte) (int, error)
}); ok {
size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
} else {
encoded, err := proto.Marshal(m.HttpUri)
if err != nil {
return 0, err
}
i -= len(encoded)
copy(dAtA[i:], encoded)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
}
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *Audience) MarshalVTStrict() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Audience) MarshalToVTStrict(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVTStrict(dAtA[:size])
}
func (m *Audience) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Url) > 0 {
i -= len(m.Url)
copy(dAtA[i:], m.Url)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Url)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *TokenCacheConfig) MarshalVTStrict() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *TokenCacheConfig) MarshalToVTStrict(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVTStrict(dAtA[:size])
}
func (m *TokenCacheConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.CacheSize != nil {
size, err := (*wrapperspb.UInt64Value)(m.CacheSize).MarshalToSizedBufferVTStrict(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *TokenHeader) MarshalVTStrict() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *TokenHeader) MarshalToVTStrict(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVTStrict(dAtA[:size])
}
func (m *TokenHeader) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.ValuePrefix) > 0 {
i -= len(m.ValuePrefix)
copy(dAtA[i:], m.ValuePrefix)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ValuePrefix)))
i--
dAtA[i] = 0x12
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *GcpAuthnFilterConfig) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.HttpUri != nil {
if size, ok := interface{}(m.HttpUri).(interface {
SizeVT() int
}); ok {
l = size.SizeVT()
} else {
l = proto.Size(m.HttpUri)
}
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.RetryPolicy != nil {
if size, ok := interface{}(m.RetryPolicy).(interface {
SizeVT() int
}); ok {
l = size.SizeVT()
} else {
l = proto.Size(m.RetryPolicy)
}
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.CacheConfig != nil {
l = m.CacheConfig.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.TokenHeader != nil {
l = m.TokenHeader.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Cluster)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.Timeout != nil {
l = (*durationpb.Duration)(m.Timeout).SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *Audience) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Url)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *TokenCacheConfig) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.CacheSize != nil {
l = (*wrapperspb.UInt64Value)(m.CacheSize).SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *TokenHeader) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.ValuePrefix)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
+1 -1
View File
@@ -5,7 +5,7 @@ package gcp // import "go.opentelemetry.io/contrib/detectors/gcp"
// Version is the current release version of the GCP resource detector.
func Version() string {
return "1.42.0"
return "1.43.0"
// This string is updated by the pre_release.sh script during release
}
@@ -238,6 +238,8 @@ type CommonLanguageSettings struct {
// The destination where API teams want this client library to be published.
Destinations []ClientLibraryDestination `protobuf:"varint,2,rep,packed,name=destinations,proto3,enum=google.api.ClientLibraryDestination" json:"destinations,omitempty"`
// Configuration for which RPCs should be generated in the GAPIC client.
//
// Note: This field should not be used in most cases.
SelectiveGapicGeneration *SelectiveGapicGeneration `protobuf:"bytes,3,opt,name=selective_gapic_generation,json=selectiveGapicGeneration,proto3" json:"selective_gapic_generation,omitempty"`
}
@@ -1249,6 +1251,8 @@ func (x *MethodSettings) GetBatching() *BatchingConfigProto {
// This message is used to configure the generation of a subset of the RPCs in
// a service for client libraries.
//
// Note: This feature should not be used in most cases.
type SelectiveGapicGeneration struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
+2 -2
View File
@@ -60,7 +60,7 @@ func Register(b Builder) {
if !envconfig.CaseSensitiveBalancerRegistries {
name = strings.ToLower(name)
if name != b.Name() {
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", b.Name())
logger.Warningf("Balancer registered with name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", b.Name())
}
}
m[name] = b
@@ -85,7 +85,7 @@ func Get(name string) Builder {
if !envconfig.CaseSensitiveBalancerRegistries {
lowerName := strings.ToLower(name)
if lowerName != name {
logger.Warningf("Balancer retrieved for name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", name)
logger.Warningf("Balancer retrieved for name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", name)
}
name = lowerName
}
@@ -19,7 +19,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/lb/v1/load_balancer.proto
+1 -1
View File
@@ -35,9 +35,9 @@ import (
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/pickfirst/internal"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/experimental/balancer/weight"
expstats "google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/balancer/weight"
"google.golang.org/grpc/internal/envconfig"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/pretty"
+1 -1
View File
@@ -42,7 +42,7 @@ import (
"google.golang.org/grpc/balancer/lazy"
"google.golang.org/grpc/balancer/pickfirst"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal/balancer/weight"
"google.golang.org/grpc/experimental/balancer/weight"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/pretty"
iringhash "google.golang.org/grpc/internal/ringhash"
+48 -76
View File
@@ -29,7 +29,6 @@ import (
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/buffer"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/pretty"
@@ -39,6 +38,12 @@ import (
var newAdaptiveThrottler = func() adaptiveThrottler { return adaptive.New() }
// newConnectivityStateSubscriber is a variable that can be overridden in tests
// to wrap the connectivity state subscriber for testing purposes.
var newConnectivityStateSubscriber = func(sub grpcsync.Subscriber) grpcsync.Subscriber {
return sub
}
type adaptiveThrottler interface {
ShouldThrottle() bool
RegisterBackendResponse(throttled bool)
@@ -57,12 +62,11 @@ type controlChannel struct {
// hammering the RLS service while it is overloaded or down.
throttler adaptiveThrottler
cc *grpc.ClientConn
client rlsgrpc.RouteLookupServiceClient
logger *internalgrpclog.PrefixLogger
connectivityStateCh *buffer.Unbounded
unsubscribe func()
monitorDoneCh chan struct{}
cc *grpc.ClientConn
client rlsgrpc.RouteLookupServiceClient
logger *internalgrpclog.PrefixLogger
dropConnStateSubscriber func()
seenTransientFailure bool
}
// newControlChannel creates a controlChannel to rlsServerName and uses
@@ -70,11 +74,9 @@ type controlChannel struct {
// gRPC channel.
func newControlChannel(rlsServerName, serviceConfig string, rpcTimeout time.Duration, bOpts balancer.BuildOptions, backToReadyFunc func()) (*controlChannel, error) {
ctrlCh := &controlChannel{
rpcTimeout: rpcTimeout,
backToReadyFunc: backToReadyFunc,
throttler: newAdaptiveThrottler(),
connectivityStateCh: buffer.NewUnbounded(),
monitorDoneCh: make(chan struct{}),
rpcTimeout: rpcTimeout,
backToReadyFunc: backToReadyFunc,
throttler: newAdaptiveThrottler(),
}
ctrlCh.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[rls-control-channel %p] ", ctrlCh))
@@ -88,11 +90,11 @@ func newControlChannel(rlsServerName, serviceConfig string, rpcTimeout time.Dura
}
// Subscribe to connectivity state before connecting to avoid missing initial
// updates, which are only delivered to active subscribers.
ctrlCh.unsubscribe = internal.SubscribeToConnectivityStateChanges.(func(cc *grpc.ClientConn, s grpcsync.Subscriber) func())(ctrlCh.cc, ctrlCh)
subscribe := internal.SubscribeToConnectivityStateChanges.(func(cc *grpc.ClientConn, s grpcsync.Subscriber) func())
ctrlCh.dropConnStateSubscriber = subscribe(ctrlCh.cc, newConnectivityStateSubscriber(ctrlCh))
ctrlCh.cc.Connect()
ctrlCh.client = rlsgrpc.NewRouteLookupServiceClient(ctrlCh.cc)
ctrlCh.logger.Infof("Control channel created to RLS server at: %v", rlsServerName)
go ctrlCh.monitorConnectivityState()
return ctrlCh, nil
}
@@ -101,7 +103,37 @@ func (cc *controlChannel) OnMessage(msg any) {
if !ok {
panic(fmt.Sprintf("Unexpected message type %T , wanted connectectivity.State type", msg))
}
cc.connectivityStateCh.Put(st)
switch st {
case connectivity.Ready:
// Only reset backoff when transitioning from TRANSIENT_FAILURE to READY.
// This indicates the RLS server has recovered from being unreachable, so
// we reset backoff state in all cache entries to allow pending RPCs to
// proceed immediately. We skip benign transitions like READY → IDLE → READY
// since those don't represent actual failures.
if cc.seenTransientFailure {
if cc.logger.V(2) {
cc.logger.Infof("Control channel back to READY after TRANSIENT_FAILURE")
}
cc.seenTransientFailure = false
if cc.backToReadyFunc != nil {
cc.backToReadyFunc()
}
} else {
if cc.logger.V(2) {
cc.logger.Infof("Control channel is READY")
}
}
case connectivity.TransientFailure:
// Track that we've entered TRANSIENT_FAILURE state so we know to reset
// backoffs when we recover to READY.
cc.logger.Warningf("Control channel is TRANSIENT_FAILURE")
cc.seenTransientFailure = true
default:
if cc.logger.V(2) {
cc.logger.Infof("Control channel connectivity state is %s", st)
}
}
}
// dialOpts constructs the dial options for the control plane channel.
@@ -148,68 +180,8 @@ func (cc *controlChannel) dialOpts(bOpts balancer.BuildOptions, serviceConfig st
return dopts, nil
}
func (cc *controlChannel) monitorConnectivityState() {
cc.logger.Infof("Starting connectivity state monitoring goroutine")
defer close(cc.monitorDoneCh)
// Since we use two mechanisms to deal with RLS server being down:
// - adaptive throttling for the channel as a whole
// - exponential backoff on a per-request basis
// we need a way to avoid double-penalizing requests by counting failures
// toward both mechanisms when the RLS server is unreachable.
//
// To accomplish this, we monitor the state of the control plane channel. If
// the state has been TRANSIENT_FAILURE since the last time it was in state
// READY, and it then transitions into state READY, we push on a channel
// which is being read by the LB policy.
//
// The LB the policy will iterate through the cache to reset the backoff
// timeouts in all cache entries. Specifically, this means that it will
// reset the backoff state and cancel the pending backoff timer. Note that
// when cancelling the backoff timer, just like when the backoff timer fires
// normally, a new picker is returned to the channel, to force it to
// re-process any wait-for-ready RPCs that may still be queued if we failed
// them while we were in backoff. However, we should optimize this case by
// returning only one new picker, regardless of how many backoff timers are
// cancelled.
// Wait for the control channel to become READY for the first time.
for s, ok := <-cc.connectivityStateCh.Get(); s != connectivity.Ready; s, ok = <-cc.connectivityStateCh.Get() {
if !ok {
return
}
cc.connectivityStateCh.Load()
if s == connectivity.Shutdown {
return
}
}
cc.connectivityStateCh.Load()
cc.logger.Infof("Connectivity state is READY")
for {
s, ok := <-cc.connectivityStateCh.Get()
if !ok {
return
}
cc.connectivityStateCh.Load()
if s == connectivity.Shutdown {
return
}
if s == connectivity.Ready {
cc.logger.Infof("Control channel back to READY")
cc.backToReadyFunc()
}
cc.logger.Infof("Connectivity state is %s", s)
}
}
func (cc *controlChannel) close() {
cc.unsubscribe()
cc.connectivityStateCh.Close()
<-cc.monitorDoneCh
cc.dropConnStateSubscriber()
cc.cc.Close()
cc.logger.Infof("Shutdown")
}
+5 -18
View File
@@ -181,16 +181,9 @@ func (g *altsTC) ClientHandshake(ctx context.Context, addr string, rawConn net.C
}
// Do not close hsConn since it is shared with other handshakes.
// Possible context leak:
// The cancel function for the child context we create will only be
// called a non-nil error is returned.
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer func() {
if err != nil {
cancel()
}
}()
defer cancel()
opts := handshaker.DefaultClientHandshakerOptions()
opts.TargetName = addr
@@ -204,11 +197,8 @@ func (g *altsTC) ClientHandshake(ctx context.Context, addr string, rawConn net.C
if err != nil {
return nil, nil, err
}
defer func() {
if err != nil {
chs.Close()
}
}()
// Close the handshaker since we have obtained a connection.
defer chs.Close()
secConn, authInfo, err := chs.ClientHandshake(ctx)
if err != nil {
return nil, nil, err
@@ -247,15 +237,12 @@ func (g *altsTC) ServerHandshake(rawConn net.Conn) (_ net.Conn, _ credentials.Au
if err != nil {
return nil, nil, err
}
defer func() {
if err != nil {
shs.Close()
}
}()
secConn, authInfo, err := shs.ServerHandshake(ctx)
if err != nil {
return nil, nil, err
}
// Close the handshaker since we have obtained a connection.
defer shs.Close()
altsAuthInfo, ok := authInfo.(AuthInfo)
if !ok {
return nil, nil, errors.New("server-side auth info is not of type alts.AuthInfo")
@@ -17,7 +17,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/gcp/handshaker.proto
+18 -4
View File
@@ -173,10 +173,8 @@ func newJoinDialOption(opts ...DialOption) DialOption {
// If this option is set to true every connection will release the buffer after
// flushing the data on the wire.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
// Deprecated: shared write buffer is enabled by default. WithSharedWriteBuffer
// will be removed in a future release.
func WithSharedWriteBuffer(val bool) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.SharedWriteBuffer = val
@@ -229,6 +227,14 @@ func WithInitialConnWindowSize(s int32) DialOption {
// WithStaticStreamWindowSize returns a DialOption which sets the initial
// stream window size to the value provided and disables dynamic flow control.
//
// Note that this also disables dynamic flow control for the connection,
// falling back to a default static connection-level window of 64KB. To
// use a larger connection-level window, you must also use the
// [WithStaticConnWindowSize] DialOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func WithStaticStreamWindowSize(s int32) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.InitialWindowSize = s
@@ -239,6 +245,14 @@ func WithStaticStreamWindowSize(s int32) DialOption {
// WithStaticConnWindowSize returns a DialOption which sets the initial
// connection window size to the value provided and disables dynamic flow
// control.
//
// Note that this also disables dynamic flow control for individual streams,
// falling back to a default static connection-level window of 64KB. To
// explicitly configure the stream-level window size, you must also use the
// [WithStaticStreamWindowSize] DialOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func WithStaticConnWindowSize(s int32) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.InitialConnWindowSize = s
+3
View File
@@ -66,6 +66,9 @@ type Compressor interface {
// Decompress reads data from r, decompresses it, and provides the
// uncompressed data via the returned io.Reader. If an error occurs while
// initializing the decompressor, that error is returned instead.
//
// The returned io.Reader may optionally implement io.ReadCloser, and if it
// does, gRPC will call Close() exactly once.
Decompress(r io.Reader) (io.Reader, error)
// Name is the name of the compression codec and is used to set the content
// coding header. The result must be static; the result cannot change
+9 -5
View File
@@ -81,6 +81,8 @@ func (z *writer) Close() error {
return z.Writer.Close()
}
var _ io.Closer = &reader{}
type reader struct {
*gzip.Reader
pool *sync.Pool
@@ -102,14 +104,16 @@ func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
return z, nil
}
func (z *reader) Read(p []byte) (n int, err error) {
n, err = z.Reader.Read(p)
if err == io.EOF {
z.pool.Put(z)
}
func (r *reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
return n, err
}
func (r *reader) Close() error {
defer r.pool.Put(r)
return r.Reader.Close()
}
func (c *compressor) Name() string {
return Name
}
@@ -0,0 +1,47 @@
/*
*
* Copyright 2026 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.
*
*/
// Package hostname contains utilities for the endpoint hostname attribute
// (used for per-endpoint :authority / SNI override).
//
// # Experimental
//
// Notice: All APIs in this package are EXPERIMENTAL and may be changed
// or removed in a later release.
package hostname
import "google.golang.org/grpc/resolver"
type hostnameKey struct{}
// Set returns a copy of the given endpoint with the hostname attribute
// set. If hostname is empty the endpoint is returned unmodified.
func Set(endpoint resolver.Endpoint, hostname string) resolver.Endpoint {
if hostname == "" {
return endpoint
}
endpoint.Attributes = endpoint.Attributes.WithValue(hostnameKey{}, hostname)
return endpoint
}
// FromEndpoint returns the hostname attribute of endpoint. If this
// attribute is not set, it returns the empty string.
func FromEndpoint(endpoint resolver.Endpoint) string {
h, _ := endpoint.Attributes.Value(hostnameKey{}).(string)
return h
}
@@ -16,23 +16,23 @@
*
*/
// Package weight contains utilities to manage endpoint weights. Weights are
// used by LB policies such as ringhash to distribute load across multiple
// endpoints.
// Package weight contains utilities to manage endpoint weights.
// Weights may be used by LB policies to distribute load across
// multiple endpoints.
//
// # Experimental
//
// Notice: All APIs in this package are EXPERIMENTAL and may be changed
// or removed in a later release.
package weight
import (
"fmt"
"google.golang.org/grpc/resolver"
)
import "google.golang.org/grpc/resolver"
// attributeKey is the type used as the key to store EndpointInfo in the
// Attributes field of resolver.Endpoint.
type attributeKey struct{}
// EndpointInfo will be stored in the Attributes field of Endpoints in order to
// use the ringhash balancer.
// EndpointInfo will be stored in the Attributes field of Endpoints.
type EndpointInfo struct {
Weight uint32
}
@@ -43,22 +43,16 @@ func (a EndpointInfo) Equal(o any) bool {
return ok && oa.Weight == a.Weight
}
// Set returns a copy of endpoint in which the Attributes field is updated with
// EndpointInfo.
// Set returns a copy of endpoint in which the Attributes field is
// updated with EndpointInfo.
func Set(endpoint resolver.Endpoint, epInfo EndpointInfo) resolver.Endpoint {
endpoint.Attributes = endpoint.Attributes.WithValue(attributeKey{}, epInfo)
return endpoint
}
// String returns a human-readable representation of EndpointInfo.
// This method is intended for logging, testing, and debugging purposes only.
// Do not rely on the output format, as it is not guaranteed to remain stable.
func (a EndpointInfo) String() string {
return fmt.Sprintf("Weight: %d", a.Weight)
}
// FromEndpoint returns the EndpointInfo stored in the Attributes field of an
// endpoint. It returns an empty EndpointInfo if attribute is not found.
// FromEndpoint returns the EndpointInfo stored in the Attributes
// field of an endpoint. It returns an empty EndpointInfo if attribute
// is not found.
func FromEndpoint(endpoint resolver.Endpoint) EndpointInfo {
v := endpoint.Attributes.Value(attributeKey{})
ei, _ := v.(EndpointInfo)
+1 -1
View File
@@ -17,7 +17,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/health/v1/health.proto
+85 -20
View File
@@ -59,6 +59,15 @@ var (
// unconditionally.
XDSEndpointHashKeyBackwardCompat = boolFromEnv("GRPC_XDS_ENDPOINT_HASH_KEY_BACKWARD_COMPAT", false)
// LabelServerGoroutines controls setting [runtime/pprof.Labels] on the
// goroutines spawned by [grpc.Server] type.
// For now, this is limited to the goroutines spawned to handle incoming
// requests on the server.
// Set "GRPC_GO_SERVER_GOROUTINE_LABELS" to "grpc.method=true" to
// enable this grpc.method label, or "all" to enable all valid labels.
// This variable is a bit-field.
LabelServerGoroutines = goroutineLabelsFromEnv("GRPC_GO_SERVER_GOROUTINE_LABELS", 0)
// RingHashSetRequestHashKey is set if the ring hash balancer can get the
// request hash header by setting the "requestHashHeader" field, according
// to gRFC A76. It can be disabled by setting the environment variable
@@ -78,12 +87,12 @@ var (
EnableDefaultPortForProxyTarget = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_DEFAULT_PORT_FOR_PROXY_TARGET", true)
// CaseSensitiveBalancerRegistries is set if the balancer registry should be
// case-sensitive. This is disabled by default, but can be enabled by setting
// case-sensitive. This is enabled by default, but can be disabled by setting
// the env variable "GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES"
// to "true".
// to "false".
//
// TODO: After 2 releases, we will enable the env var by default.
CaseSensitiveBalancerRegistries = boolFromEnv("GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES", false)
// This env varible will be removed in release v1.82.0.
CaseSensitiveBalancerRegistries = boolFromEnv("GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES", true)
// XDSAuthorityRewrite indicates whether xDS authority rewriting is enabled.
// This feature is defined in gRFC A81 and is enabled by setting the
@@ -104,22 +113,6 @@ var (
// to "false".
XDSRecoverPanicInResourceParsing = boolFromEnv("GRPC_GO_EXPERIMENTAL_XDS_RESOURCE_PANIC_RECOVERY", true)
// DisableStrictPathChecking indicates whether strict path checking is
// disabled. This feature can be disabled by setting the environment
// variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to "true".
//
// When strict path checking is enabled, gRPC will reject requests with
// paths that do not conform to the gRPC over HTTP/2 specification found at
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
//
// When disabled, gRPC will allow paths that do not contain a leading slash.
// Enabling strict path checking is recommended for security reasons, as it
// prevents potential path traversal vulnerabilities.
//
// A future release will remove this environment variable, enabling strict
// path checking behavior unconditionally.
DisableStrictPathChecking = boolFromEnv("GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING", false)
// EnablePriorityLBChildPolicyCache controls whether the priority balancer
// should cache child balancers that are removed from the LB policy config,
// for a period of 15 minutes. This is disabled by default, but can be
@@ -127,6 +120,18 @@ var (
// GRPC_EXPERIMENTAL_ENABLE_PRIORITY_LB_CHILD_POLICY_CACHE to true.
EnablePriorityLBChildPolicyCache = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_PRIORITY_LB_CHILD_POLICY_CACHE", false)
// Enable8KBDefaultHeaderListSize indicates that default maximum header list
// size is restricted to 8KB. This is disabled by default, but can be enabled
// by setting the environment variable
// "GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE" to "true".
// When disabled, the default maximum header list size of 16MB is used.
//
// When enabled, RPCs with a total size of headers exceeding 8KB will fail
// unless explicitly configured otherwise by the user.
//
// TODO: In release v1.82.0, env var will be enabled by default.
Enable8KBDefaultHeaderListSize = boolFromEnv("GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE", false)
// EnableHTTPFramerReadBufferPooling enables the use of the
// readyreader.Reader interface to perform non-memory-pinning reads,
// provided the underlying net.Conn supports it. This reduces memory usage
@@ -136,6 +141,17 @@ var (
// feature if unforeseen issues arise, and it will be removed in a future
// release.
EnableHTTPFramerReadBufferPooling = boolFromEnv("GRPC_GO_EXPERIMENTAL_HTTP_FRAMER_READ_BUFFER_POOLING", true)
// ControlBufferThrottleLimit is the maximum number of control frames that can
// be queued in the control buffer before throttling is applied. The value
// must be between 1 and 10,000, and is set to 100 by default.
//
// This environment variable serves as an escape hatch to increase the
// throttling limit if unforeseen issues arise, and it will be removed in a
// future release.
//
// TODO: Remove this env var once v1.83.0 is release.
ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000)
)
func boolFromEnv(envVar string, def bool) bool {
@@ -160,3 +176,52 @@ func uint64FromEnv(envVar string, def, min, max uint64) uint64 {
}
return v
}
// GoroutineLabels is a bitfield indicating which goroutine labels are enabled.
type GoroutineLabels uint16
func goroutineLabelsFromEnv(envVar string, def GoroutineLabels) GoroutineLabels {
val := def
v := os.Getenv(envVar)
if strings.EqualFold(v, "all") {
return AllGoroutineLabels
} else if strings.EqualFold(v, "none") {
return 0
}
for s := range strings.SplitSeq(v, ",") {
s = strings.TrimSpace(s)
if len(s) == 0 {
continue
}
pre, post, ok := strings.Cut(s, "=")
if !ok {
// no equals sign
continue
}
post = strings.TrimSpace(post)
pre = strings.TrimSpace(pre)
bitDesignator := GoroutineLabels(0)
switch {
case strings.EqualFold(pre, "grpc.method"):
bitDesignator = GoroutineLabelServerMethod
default:
continue
}
if strings.EqualFold(post, "true") {
val |= bitDesignator
} else if strings.EqualFold(post, "false") {
val &^= bitDesignator
}
}
return val
}
const (
// GoroutineLabelServerMethod sets the grpc.method label on new
// server-side gRPC streams.
GoroutineLabelServerMethod GoroutineLabels = 1 << iota
)
// AllGoroutineLabels is an or'd together bitfield of all valid GoroutineLabels
// constant values (above).
const AllGoroutineLabels = GoroutineLabelServerMethod
+10
View File
@@ -89,4 +89,14 @@ var (
// filtered and prefix-propagated to the LRS server. For more details, see:
// https://github.com/grpc/proposal/blob/master/A85-lrs-custom-metrics-changes.md
XDSORCAToLRSPropEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION", false)
// XDSClientExtProcEnabled indicates whether ExtProc filter is enabled on
// the client side. For more details, see:
// https://github.com/grpc/proposal/blob/master/A93-xds-ext-proc.md
XDSClientExtProcEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_EXT_PROC_ON_CLIENT", false)
// GCPAuthenticationFilterEnabled enables the xDS GCP Authentication
// filter. For more details, see:
// https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md
GCPAuthenticationFilterEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER", false)
)
-1
View File
@@ -39,7 +39,6 @@ func div(d, r time.Duration) int64 {
//
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
func EncodeDuration(t time.Duration) string {
// TODO: This is simplistic and not bandwidth efficient. Improve it.
if t <= 0 {
return "0n"
}
@@ -14,7 +14,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/lookup/v1/rls.proto
+17 -7
View File
@@ -106,14 +106,24 @@ type ClientStream interface {
// ClientInterceptor is an interceptor for gRPC client streams.
type ClientInterceptor interface {
// NewStream produces a ClientStream for an RPC which may optionally use
// the provided function to produce a stream for delegation. Note:
// RPCInfo.Context should not be used (will be nil).
// NewStream creates a ClientStream for an RPC.
//
// done is invoked when the RPC is finished using its connection, or could
// not be assigned a connection. RPC operations may still occur on
// ClientStream after done is called, since the interceptor is invoked by
// application-layer operations. done must never be nil when called.
// Implementations must delegate stream creation to the provided newStream
// function. To intercept or override stream behavior, implementations
// may wrap the ClientStream returned by the delegate.
//
// Note: RPCInfo.Context is currently unused and will be nil.
//
// The done function is invoked when the RPC has finished using its
// underlying connection or if a connection could not be assigned. Because
// interceptors operate at the application layer, RPC operations may
// continue on the ClientStream even after done has been called. The
// caller must ensure done is non-nil.
//
// To ensure RPC completion notifications propagate through the entire
// interceptor chain, implementations must ensure that the done function
// passed to the delegate newStream invokes the done function passed to
// NewStream.
NewStream(ctx context.Context, ri RPCInfo, done func(), newStream func(ctx context.Context, done func()) (ClientStream, error)) (ClientStream, error)
// Close closes the interceptor. Once called, no new calls to NewStream are
// accepted. Ongoing calls to NewStream are allowed to complete.
+46 -14
View File
@@ -19,24 +19,56 @@
// Package stats provides internal stats related functionality.
package stats
import "context"
import (
"context"
"maps"
)
// Labels are the labels for metrics.
type Labels struct {
// TelemetryLabels are the telemetry labels to record.
TelemetryLabels map[string]string
// LabelCallback is a function that is executed when telemetry
// label keys are updated.
type LabelCallback func(map[string]string)
type telemetryLabelCallbackKey struct{}
// UpdateLabels executes registered telemetry callbacks with the update labels. Labels
// are copied before being processed by any callbacks to ensure mutations are not
// shared among derived contexts.
//
// It is the responsibility of the registrant to handle conflicts or label resets.
func UpdateLabels(ctx context.Context, update map[string]string) {
executeTelemetryLabelCallbacks(ctx, update)
}
type labelsKey struct{}
// RegisterTelemetryLabelCallback registers a callback function that is executed whenever
// telemetry labels are updated.
func RegisterTelemetryLabelCallback(ctx context.Context, callback LabelCallback) context.Context {
if callback == nil {
return ctx
}
callbacks, ok := ctx.Value(telemetryLabelCallbackKey{}).([]LabelCallback)
if !ok {
return context.WithValue(ctx, telemetryLabelCallbackKey{}, []LabelCallback{callback})
}
return context.WithValue(ctx, telemetryLabelCallbackKey{}, append(append([]LabelCallback(nil), callbacks...), callback))
// GetLabels returns the Labels stored in the context, or nil if there is one.
func GetLabels(ctx context.Context) *Labels {
labels, _ := ctx.Value(labelsKey{}).(*Labels)
return labels
}
// SetLabels sets the Labels in the context.
func SetLabels(ctx context.Context, labels *Labels) context.Context {
// could also append
return context.WithValue(ctx, labelsKey{}, labels)
// executeTelemetryLabelCallback runs the registered callbacks in the order they were
// registered on the context with the provided labels. If no callbacks are registered
// it does nothing.
//
// To ensure callbacks do not mutate the state of the provided label map it is copied
// before execution.
func executeTelemetryLabelCallbacks(ctx context.Context, labels map[string]string) {
callbacks, ok := ctx.Value(telemetryLabelCallbackKey{}).([]LabelCallback)
if !ok {
return
}
labelsCopy := map[string]string{}
maps.Copy(labelsCopy, labels)
for _, callback := range callbacks {
callback(labelsCopy)
}
}
+35 -1
View File
@@ -19,6 +19,7 @@
package transport
import (
"fmt"
"sync/atomic"
"golang.org/x/net/http2"
@@ -28,6 +29,12 @@ import (
"google.golang.org/grpc/status"
)
// nonGRPCDataMaxLen is the maximum length of nonGRPCDataBuf.
//
// NOTE: If changed this value, you MUST update the corresponding test in:
// - /test/end2end_test.go:TestHTTPServerSendsNonGRPCHeaderSurfaceFurtherData
const nonGRPCDataMaxLen = 1024
// ClientStream implements streaming functionality for a gRPC client.
type ClientStream struct {
Stream // Embed for common stream functionality.
@@ -46,7 +53,11 @@ type ClientStream struct {
// headerValid indicates whether a valid header was received. Only
// meaningful after headerChan is closed (always call waitOnHeader() before
// reading its value).
headerValid bool
headerValid bool
nonGRPCStatus *status.Status // the initial status from the non-gRPC response header, finalized with collected data before closing.
nonGRPCDataBuf []byte // stores the data of a non-gRPC response.
noHeaders bool // set if the client never received headers (set only after the stream is done).
headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times.
bytesReceived atomic.Bool // indicates whether any bytes have been received on this stream
@@ -54,6 +65,29 @@ type ClientStream struct {
statsHandler stats.Handler // nil for internal streams (e.g., health check, ORCA) where telemetry is not supported.
}
func (s *ClientStream) startNonGRPCDataCollection(st *status.Status) {
s.nonGRPCStatus = st
s.nonGRPCDataBuf = make([]byte, 0, nonGRPCDataMaxLen)
}
// finalizeNonGRPCStatus builds the terminal status by appending the collected
// response body to the original non-gRPC status message.
func (s *ClientStream) finalizeNonGRPCStatus() *status.Status {
msg := fmt.Sprintf("%s\ndata: %q", s.nonGRPCStatus.Message(), s.nonGRPCDataBuf)
return status.New(s.nonGRPCStatus.Code(), msg)
}
// handleNonGRPCData collects non-gRPC body from the given data frame.
// It returns non-nil value when the stream should be closed with it.
func (s *ClientStream) handleNonGRPCData(f *parsedDataFrame) *status.Status {
n := min(f.data.Len(), nonGRPCDataMaxLen-len(s.nonGRPCDataBuf))
s.nonGRPCDataBuf = append(s.nonGRPCDataBuf, f.data.ReadOnlyData()[0:n]...)
if len(s.nonGRPCDataBuf) >= nonGRPCDataMaxLen || f.StreamEnded() {
return s.finalizeNonGRPCStatus()
}
return nil
}
// Read reads an n byte message from the input stream.
func (s *ClientStream) Read(n int) (mem.BufferSlice, error) {
b, err := s.Stream.read(n)
+97 -120
View File
@@ -29,6 +29,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/mem"
)
@@ -96,61 +97,70 @@ func (il *itemList) isEmpty() bool {
return il.head == nil
}
// maxQueuedControlBufferItems is the maximum number of frames (other than
// HEADERS and DATA) that we will buffer before preventing new reads from
// occurring on the transport. These are control frames sent in response to
// client requests, or frames that result in work being scheduled, such as
// RST_STREAM due to bad headers or settings acks.
var maxQueuedControlBufferItems = int(envconfig.ControlBufferThrottleLimit)
type cbItem interface {
isThrottled() bool
}
// throttledItem represents every item in the controlBuffer to which the overall
// throttling limit applies, other than outgoing HEADERS and DATA frames.
type throttledItem struct{}
func (throttledItem) isThrottled() bool { return true }
// The following defines various control items which could flow through
// the control buffer of transport. They represent different aspects of
// control tasks, e.g., flow control, settings, streaming resetting, etc.
// maxQueuedTransportResponseFrames is the most queued "transport response"
// frames we will buffer before preventing new reads from occurring on the
// transport. These are control frames sent in response to client requests,
// such as RST_STREAM due to bad headers or settings acks.
const maxQueuedTransportResponseFrames = 50
type cbItem interface {
isTransportResponseFrame() bool
}
// registerStream is used to register an incoming stream with loopy writer.
type registerStream struct {
throttledItem
streamID uint32
wq *writeQuota
}
func (*registerStream) isTransportResponseFrame() bool { return false }
// headerFrame is also used to register stream on the client-side.
type headerFrame struct {
type clientHeaders struct {
streamID uint32
hf []hpack.HeaderField
endStream bool // Valid on server side.
initStream func(uint32) error // Used only on the client side.
initStream func(uint32) error
onWrite func()
wq *writeQuota // write quota for the stream created.
cleanup *cleanupStream // Valid on the server side.
onOrphaned func(error) // Valid on client-side
wq *writeQuota
onOrphaned func(error)
}
func (h *headerFrame) isTransportResponseFrame() bool {
return h.cleanup != nil && h.cleanup.rst // Results in a RST_STREAM
func (*clientHeaders) isThrottled() bool { return false }
type serverHeaders struct {
streamID uint32
hf []hpack.HeaderField
endStream bool
onWrite func()
cleanup *cleanupStream
}
func (h *serverHeaders) isThrottled() bool { return false }
type cleanupStream struct {
throttledItem
streamID uint32
rst bool
rstCode http2.ErrCode
onWrite func()
}
func (c *cleanupStream) isTransportResponseFrame() bool { return c.rst } // Results in a RST_STREAM
type earlyAbortStream struct {
throttledItem
streamID uint32
rst bool
hf []hpack.HeaderField // Pre-built header fields
}
func (*earlyAbortStream) isTransportResponseFrame() bool { return false }
type dataFrame struct {
streamID uint32
endStream bool
@@ -162,70 +172,60 @@ type dataFrame struct {
onEachWrite func()
}
func (*dataFrame) isTransportResponseFrame() bool { return false }
func (*dataFrame) isThrottled() bool { return false }
type incomingWindowUpdate struct {
throttledItem
streamID uint32
increment uint32
}
func (*incomingWindowUpdate) isTransportResponseFrame() bool { return false }
type outgoingWindowUpdate struct {
throttledItem
streamID uint32
increment uint32
}
func (*outgoingWindowUpdate) isTransportResponseFrame() bool {
return false // window updates are throttled by thresholds
}
type incomingSettings struct {
throttledItem
ss []http2.Setting
}
func (*incomingSettings) isTransportResponseFrame() bool { return true } // Results in a settings ACK
type outgoingSettings struct {
throttledItem
ss []http2.Setting
}
func (*outgoingSettings) isTransportResponseFrame() bool { return false }
type incomingGoAway struct {
throttledItem
}
func (*incomingGoAway) isTransportResponseFrame() bool { return false }
type goAway struct {
throttledItem
code http2.ErrCode
debugData []byte
headsUp bool
closeConn error // if set, loopyWriter will exit with this error
}
func (*goAway) isTransportResponseFrame() bool { return false }
type ping struct {
throttledItem
ack bool
data [8]byte
}
func (*ping) isTransportResponseFrame() bool { return true }
type outFlowControlSizeRequest struct {
throttledItem
resp chan uint32
}
func (*outFlowControlSizeRequest) isTransportResponseFrame() bool { return false }
// closeConnection is an instruction to tell the loopy writer to flush the
// framer and exit, which will cause the transport's connection to be closed
// (by the client or server). The transport itself will close after the reader
// encounters the EOF caused by the connection closure.
type closeConnection struct{}
func (closeConnection) isTransportResponseFrame() bool { return false }
type closeConnection struct {
throttledItem
}
type outStreamState int
@@ -379,9 +379,9 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) {
c.consumerWaiting = false
}
c.list.enqueue(it)
if it.isTransportResponseFrame() {
if it.isThrottled() {
c.transportResponseFrames++
if c.transportResponseFrames == maxQueuedTransportResponseFrames {
if c.transportResponseFrames == maxQueuedControlBufferItems {
// We are adding the frame that puts us over the threshold; create
// a throttling channel.
ch := make(chan struct{})
@@ -436,8 +436,8 @@ func (c *controlBuffer) getOnceLocked() (any, error) {
return nil, nil
}
h := c.list.dequeue().(cbItem)
if h.isTransportResponseFrame() {
if c.transportResponseFrames == maxQueuedTransportResponseFrames {
if h.isThrottled() {
if c.transportResponseFrames == maxQueuedControlBufferItems {
// We are removing the frame that put us over the
// threshold; close and clear the throttling channel.
ch := c.trfChan.Swap(nil)
@@ -464,10 +464,8 @@ func (c *controlBuffer) finish() {
// is still not aware of these yet.
for head := c.list.dequeueAll(); head != nil; head = head.next {
switch v := head.it.(type) {
case *headerFrame:
if v.onOrphaned != nil { // It will be nil on the server-side.
v.onOrphaned(ErrConnClosing)
}
case *clientHeaders:
v.onOrphaned(ErrConnClosing)
case *dataFrame:
if !v.processing {
v.data.Free()
@@ -680,42 +678,38 @@ func (l *loopyWriter) registerStreamHandler(h *registerStream) {
l.estdStreams[h.streamID] = str
}
func (l *loopyWriter) headerHandler(h *headerFrame) error {
if l.side == serverSide {
str, ok := l.estdStreams[h.streamID]
if !ok {
if l.logger.V(logLevel) {
l.logger.Infof("Unrecognized streamID %d in loopyWriter", h.streamID)
}
return nil
func (l *loopyWriter) serverHeaderHandler(hdr *serverHeaders) error {
str, ok := l.estdStreams[hdr.streamID]
if !ok {
if l.logger.V(logLevel) {
l.logger.Infof("Unrecognized streamID %d in loopyWriter", hdr.streamID)
}
// Case 1.A: Server is responding back with headers.
if !h.endStream {
return l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite)
}
// else: Case 1.B: Server wants to close stream.
return nil
}
if str.state != empty { // either active or waiting on stream quota.
// add it str's list of items.
str.itl.enqueue(h)
return nil
}
if err := l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite); err != nil {
return err
}
return l.cleanupStreamHandler(h.cleanup)
// Case 1: Server is responding back with headers.
if !hdr.endStream {
return l.writeHeader(hdr.streamID, hdr.endStream, hdr.hf, hdr.onWrite)
}
// Case 2: Client wants to originate stream.
str := &outStream{
id: h.streamID,
state: empty,
itl: &itemList{},
wq: h.wq,
// Case 2: Server is closing the stream.
if str.state != empty { // either active or waiting on stream quota.
str.itl.enqueue(hdr)
return nil
}
return l.originateStream(str, h)
if err := l.writeHeader(hdr.streamID, hdr.endStream, hdr.hf, hdr.onWrite); err != nil {
return err
}
return l.cleanupStreamHandler(hdr.cleanup)
}
func (l *loopyWriter) originateStream(str *outStream, hdr *headerFrame) error {
func (l *loopyWriter) clientHeaderHandler(hdr *clientHeaders) error {
str := &outStream{
id: hdr.streamID,
state: empty,
itl: &itemList{},
wq: hdr.wq,
}
// l.draining is set when handling GoAway. In which case, we want to avoid
// creating new streams.
if l.draining {
@@ -726,7 +720,7 @@ func (l *loopyWriter) originateStream(str *outStream, hdr *headerFrame) error {
if err := hdr.initStream(str.id); err != nil {
return err
}
if err := l.writeHeader(str.id, hdr.endStream, hdr.hf, hdr.onWrite); err != nil {
if err := l.writeHeader(str.id, false, hdr.hf, hdr.onWrite); err != nil {
return err
}
l.estdStreams[str.id] = str
@@ -882,8 +876,10 @@ func (l *loopyWriter) handle(i any) error {
return l.incomingSettingsHandler(i)
case *outgoingSettings:
return l.outgoingSettingsHandler(i)
case *headerFrame:
return l.headerHandler(i)
case *clientHeaders:
return l.clientHeaderHandler(i)
case *serverHeaders:
return l.serverHeaderHandler(i)
case *registerStream:
l.registerStreamHandler(i)
case *cleanupStream:
@@ -956,39 +952,16 @@ func (l *loopyWriter) processData() (bool, error) {
// from data is copied to h to make as big as the maximum possible HTTP2 frame
// size.
if len(dataItem.h) == 0 && reader.Remaining() == 0 { // Empty data frame
// Client sends out empty data frame with endStream = true
if err := l.framer.writeData(dataItem.streamID, dataItem.endStream, nil); err != nil {
return false, err
}
str.itl.dequeue() // remove the empty data item from stream
reader.Close()
if str.itl.isEmpty() {
str.state = empty
} else if trailer, ok := str.itl.peek().(*headerFrame); ok { // the next item is trailers.
if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
return false, err
}
if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
return false, err
}
} else {
l.activeStreams.enqueue(str)
}
return false, nil
}
isEmpty := len(dataItem.h) == 0 && reader.Remaining() == 0
// Figure out the maximum size we can send
maxSize := http2MaxFrameLen
if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { // stream-level flow control.
strQuota := int(l.oiws) - str.bytesOutStanding
if strQuota <= 0 && !isEmpty { // stream-level flow control.
str.state = waitingOnStreamQuota
return false, nil
} else if maxSize > strQuota {
maxSize = strQuota
}
if maxSize > int(l.sendQuota) { // connection-level flow control.
maxSize = int(l.sendQuota)
}
maxSize = min(maxSize, max(strQuota, 0))
maxSize = min(maxSize, int(l.sendQuota)) // connection-level flow control.
// Compute how much of the header and data we can send within quota and max frame length
hSize := min(maxSize, len(dataItem.h))
dSize := min(maxSize-hSize, reader.Remaining())
@@ -1039,19 +1012,23 @@ func (l *loopyWriter) processData() (bool, error) {
reader.Close()
str.itl.dequeue()
}
return false, l.updateStreamAfterWrite(str)
}
func (l *loopyWriter) updateStreamAfterWrite(str *outStream) error {
if str.itl.isEmpty() {
str.state = empty
} else if trailer, ok := str.itl.peek().(*headerFrame); ok { // The next item is trailers.
} else if trailer, ok := str.itl.peek().(*serverHeaders); ok { // the next item is trailers.
if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
return false, err
return err
}
if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
return false, err
return err
}
} else if int(l.oiws)-str.bytesOutStanding <= 0 { // Ran out of stream quota.
str.state = waitingOnStreamQuota
} else { // Otherwise add it back to the list of active streams.
l.activeStreams.enqueue(str)
}
return false, nil
return nil
}
+4 -6
View File
@@ -115,7 +115,6 @@ func (f *trInFlow) getSize() uint32 {
return atomic.LoadUint32(&f.effectiveWindowSize)
}
// TODO(mmukhi): Simplify this code.
// inFlow deals with inbound flow control
type inFlow struct {
mu sync.Mutex
@@ -174,14 +173,14 @@ func (f *inFlow) maybeAdjust(n uint32) uint32 {
// onData is invoked when some data frame is received. It updates pendingData.
func (f *inFlow) onData(n uint32) error {
f.mu.Lock()
defer f.mu.Unlock()
f.pendingData += n
if f.pendingData+f.pendingUpdate > f.limit+f.delta {
limit := f.limit
rcvd := f.pendingData + f.pendingUpdate
f.mu.Unlock()
return fmt.Errorf("received %d-bytes data exceeding the limit %d bytes", rcvd, limit)
}
f.mu.Unlock()
return nil
}
@@ -189,8 +188,9 @@ func (f *inFlow) onData(n uint32) error {
// to be sent to the peer.
func (f *inFlow) onRead(n uint32) uint32 {
f.mu.Lock()
defer f.mu.Unlock()
if f.pendingData == 0 {
f.mu.Unlock()
return 0
}
f.pendingData -= n
@@ -205,9 +205,7 @@ func (f *inFlow) onRead(n uint32) uint32 {
if f.pendingUpdate >= f.limit/4 {
wu := f.pendingUpdate
f.pendingUpdate = 0
f.mu.Unlock()
return wu
}
f.mu.Unlock()
return 0
}
+2 -2
View File
@@ -479,8 +479,8 @@ func (ht *serverHandlerTransport) runStream() {
func (ht *serverHandlerTransport) incrMsgRecv() {}
func (ht *serverHandlerTransport) Drain(string) {
panic("Drain() is not implemented")
func (ht *serverHandlerTransport) Drain(s string) {
ht.Close(errors.New(s))
}
// mapRecvMsgError returns the non-nil err into the appropriate
+50 -8
View File
@@ -39,6 +39,7 @@ import (
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz"
icredentials "google.golang.org/grpc/internal/credentials"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/grpcutil"
@@ -318,7 +319,13 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
}
writeBufSize := opts.WriteBufferSize
readBufSize := opts.ReadBufferSize
// The default header list size is moving from 16MB to 8KB. The 8KB limit
// is only used if Enable8KBDefaultHeaderListSize is true; otherwise, the
// old 16MB default is used. User-specified options always take precedence.
maxHeaderListSize := defaultClientMaxHeaderListSize
if envconfig.Enable8KBDefaultHeaderListSize {
maxHeaderListSize = upcomingDefaultHeaderListSize
}
if opts.MaxHeaderListSize != nil {
maxHeaderListSize = *opts.MaxHeaderListSize
}
@@ -799,9 +806,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s
close(s.headerChan)
}
}
hdr := &headerFrame{
hf: headerFields,
endStream: false,
hdr := &clientHeaders{
hf: headerFields,
initStream: func(uint32) error {
t.mu.Lock()
// TODO: handle transport closure in loopy instead and remove this
@@ -879,8 +885,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s
return false
}
}
if sz > int64(upcomingDefaultHeaderListSize) {
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In a future release, this will be restricted to %d bytes.", sz, upcomingDefaultHeaderListSize, upcomingDefaultHeaderListSize)
if !envconfig.Enable8KBDefaultHeaderListSize && sz > int64(upcomingDefaultHeaderListSize) {
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In release v1.82.0, GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE will be enabled by default, enforcing this limit.", sz, upcomingDefaultHeaderListSize)
}
return true
}
@@ -1224,10 +1230,30 @@ func (t *http2Client) handleData(f *parsedDataFrame) {
t.closeStream(s, io.EOF, true, http2.ErrCodeFlowControl, status.New(codes.Internal, err.Error()), nil, false)
return
}
if s.nonGRPCStatus != nil {
// The frame should be handled as a non-gRPC response body
st := s.handleNonGRPCData(f)
if st != nil {
t.closeStream(s, st.Err(), true, http2.ErrCodeProtocol, st, nil, true)
return
}
if w := s.fc.onRead(size); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{
streamID: s.id,
increment: w,
})
}
return
}
dataLen := f.data.Len()
if f.Header().Flags.Has(http2.FlagDataPadded) {
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
t.controlBuf.put(&outgoingWindowUpdate{
streamID: s.id,
increment: w,
})
}
}
if dataLen > 0 {
@@ -1468,6 +1494,17 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
return
}
// If we are collecting non-gRPC response data and receive a trailing
// HEADERS frame with END_STREAM, finalize the buffered data and close
// the stream.
if s.nonGRPCStatus != nil {
if endStream {
st := s.finalizeNonGRPCStatus()
t.closeStream(s, st.Err(), true, http2.ErrCodeProtocol, st, nil, true)
}
return
}
var (
// If a gRPC Response-Headers has already been received, then it means
// that the peer is speaking gRPC and we are in gRPC mode.
@@ -1568,7 +1605,12 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
}
se := status.New(grpcErrorCode, strings.Join(errs, "; "))
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, endStream)
if endStream {
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, true)
return
}
s.startNonGRPCDataCollection(se)
return
}
@@ -1839,7 +1881,7 @@ func (t *http2Client) getOutFlowWindow() int64 {
resp := make(chan uint32, 1)
timer := time.NewTimer(time.Second)
defer timer.Stop()
t.controlBuf.put(&outFlowControlSizeRequest{resp})
t.controlBuf.put(&outFlowControlSizeRequest{resp: resp})
select {
case sz := <-resp:
return int64(sz)
+20 -9
View File
@@ -38,11 +38,13 @@ import (
"google.golang.org/protobuf/proto"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/internal/pretty"
istatus "google.golang.org/grpc/internal/status"
"google.golang.org/grpc/internal/syscall"
transportinternal "google.golang.org/grpc/internal/transport/internal"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/codes"
@@ -165,7 +167,13 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
}
writeBufSize := config.WriteBufferSize
readBufSize := config.ReadBufferSize
// The default header list size is moving from 16MB to 8KB. The 8KB limit
// is only used if Enable8KBDefaultHeaderListSize is true; otherwise, the
// old 16MB default is used. User-specified options always take precedence.
maxHeaderListSize := defaultServerMaxHeaderListSize
if envconfig.Enable8KBDefaultHeaderListSize {
maxHeaderListSize = upcomingDefaultHeaderListSize
}
if config.MaxHeaderListSize != nil {
maxHeaderListSize = *config.MaxHeaderListSize
}
@@ -802,7 +810,10 @@ func (t *http2Server) handleData(f *parsedDataFrame) {
dataLen := f.data.Len()
if f.Header().Flags.Has(http2.FlagDataPadded) {
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
t.controlBuf.put(&outgoingWindowUpdate{
streamID: s.id,
increment: w,
})
}
}
if dataLen > 0 {
@@ -948,8 +959,8 @@ func (t *http2Server) checkForHeaderListSize(hf []hpack.HeaderField) bool {
return false
}
}
if sz > int64(upcomingDefaultHeaderListSize) {
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In a future release, this will be restricted to %d bytes.", sz, upcomingDefaultHeaderListSize, upcomingDefaultHeaderListSize)
if !envconfig.Enable8KBDefaultHeaderListSize && sz > int64(upcomingDefaultHeaderListSize) {
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In release v1.82.0, GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE will be enabled by default, enforcing this limit.", sz, upcomingDefaultHeaderListSize)
}
return true
}
@@ -1039,7 +1050,7 @@ func (t *http2Server) writeHeaderLocked(s *ServerStream) error {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress})
}
headerFields = appendHeaderFieldsFromMD(headerFields, s.header)
hf := &headerFrame{
hf := &serverHeaders{
streamID: s.id,
hf: headerFields,
endStream: false,
@@ -1107,7 +1118,7 @@ func (t *http2Server) writeStatus(s *ServerStream, st *status.Status) error {
// Attach the trailer metadata.
headerFields = appendHeaderFieldsFromMD(headerFields, s.trailer)
trailingHeader := &headerFrame{
trailingHeader := &serverHeaders{
streamID: s.id,
hf: headerFields,
endStream: true,
@@ -1317,7 +1328,7 @@ func (t *http2Server) deleteStream(s *ServerStream, eosReceived bool) {
}
// finishStream closes the stream and puts the trailing headerFrame into controlbuf.
func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *headerFrame, eosReceived bool) {
func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *serverHeaders, eosReceived bool) {
// In case stream sending and receiving are invoked in separate
// goroutines (e.g., bi-directional streaming), cancel needs to be
// called to interrupt the potential blocking on other goroutines.
@@ -1441,14 +1452,14 @@ func (t *http2Server) socketMetrics() *channelz.EphemeralSocketMetrics {
func (t *http2Server) incrMsgSent() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesSent.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Store(transportinternal.TimeNowFunc())
}
}
func (t *http2Server) incrMsgRecv() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesReceived.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Store(transportinternal.TimeNowFunc())
}
}
@@ -1456,7 +1467,7 @@ func (t *http2Server) getOutFlowWindow() int64 {
resp := make(chan uint32, 1)
timer := time.NewTimer(time.Second)
defer timer.Stop()
t.controlBuf.put(&outFlowControlSizeRequest{resp})
t.controlBuf.put(&outFlowControlSizeRequest{resp: resp})
select {
case sz := <-resp:
return int64(sz)
@@ -1,6 +1,6 @@
/*
*
* Copyright 2021 gRPC authors.
* Copyright 2026 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,10 @@
*
*/
package grpcutil
// Package internal contains functionality internal to the transport package.
package internal
import "regexp"
// FullMatchWithRegex returns whether the full text matches the regex provided.
func FullMatchWithRegex(re *regexp.Regexp, text string) bool {
if len(text) == 0 {
return re.MatchString(text)
}
re.Longest()
rem := re.FindString(text)
return len(rem) == len(text)
}
// TimeNowFunc is a variable that can be set to override the default behavior of
// getting the current time in nanoseconds. It is used in transport code to set
// channelz timestamps, and is exposed here for testing purposes.
var TimeNowFunc func() int64
+5
View File
@@ -35,6 +35,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/transport/internal"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/metadata"
@@ -46,6 +47,10 @@ import (
const logLevel = 2
func init() {
internal.TimeNowFunc = func() int64 { return time.Now().UnixNano() }
}
// recvMsg represents the received msg from the transport. All transport
// protocol specific info has been removed.
type recvMsg struct {
@@ -24,7 +24,8 @@ import (
"maps"
"slices"
"google.golang.org/grpc/internal/balancer/weight"
"google.golang.org/grpc/experimental/balancer/hostname"
"google.golang.org/grpc/experimental/balancer/weight"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/hierarchy"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
@@ -181,7 +182,7 @@ func buildClusterImplConfigForDNS(g *nameGenerator, config *xdsresource.ClusterC
// LB policies that rely on locality information (like weighted_target)
// continue to work.
localityStr := xdsinternal.LocalityString(clients.Locality{})
retEndpoint = xdsresource.SetHostname(hierarchy.SetInEndpoint(retEndpoint, []string{pName, localityStr}), clusterUpdate.DNSHostName)
retEndpoint = hostname.Set(hierarchy.SetInEndpoint(retEndpoint, []string{pName, localityStr}), clusterUpdate.DNSHostName)
// Set the locality weight to 1. This is required because the child policy
// like weighted_target which relies on locality weights to distribute
// traffic. These policies may drop traffic if the weight is 0.
@@ -575,18 +575,18 @@ func (b *clusterImplBalancer) NewSubConn(addrs []resolver.Address, opts balancer
newAddrs[i] = xdsinternal.SetXDSHandshakeClusterName(addr, clusterName)
newAddrs[i] = xds.SetHandshakeInfo(newAddrs[i], &b.xdsHIPtr)
hostname := xdsresource.Hostname(addr)
host := xdsresource.Hostname(addr)
// If the hostname contains a port, strip it. Per [RFC 6066, Section
// 3](https://www.rfc-editor.org/rfc/rfc6066.html#section-3), the SNI
// may only contain a qualified DNS hostname, which excludes port
// numbers.
h, _, err := net.SplitHostPort(hostname)
h, _, err := net.SplitHostPort(host)
if err == nil {
hostname = h
host = h
}
// Store hostname in the address attributes, so that it can be used in
// the client handshake.
newAddrs[i] = xds.SetAddressHostname(newAddrs[i], hostname)
newAddrs[i] = xds.SetAddressHostname(newAddrs[i], host)
}
var sc balancer.SubConn
scw := &scWrapper{}
+9 -21
View File
@@ -20,7 +20,6 @@ package clusterimpl
import (
"context"
"maps"
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
"google.golang.org/grpc/balancer"
@@ -95,24 +94,10 @@ type picker struct {
metrics *xdsresource.LRSReportEndpointMetricsConfig
}
func telemetryLabels(ctx context.Context) map[string]string {
if ctx == nil {
return nil
}
labels := stats.GetLabels(ctx)
if labels == nil {
return nil
}
return labels.TelemetryLabels
}
func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
// Unconditionally set labels if present, even dropped or queued RPC's can
// Unconditionally update labels if present, even dropped or queued RPC's can
// use these labels.
labels := telemetryLabels(info.Ctx)
if labels != nil {
maps.Copy(labels, d.telemetryLabels)
}
stats.UpdateLabels(info.Ctx, d.telemetryLabels)
// Don't drop unless the inner picker is READY. Similar to
// https://github.com/grpc/grpc-go/issues/2622.
@@ -167,10 +152,13 @@ func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
return pr, err
}
if labels != nil {
labels["grpc.lb.locality"] = xdsinternal.LocalityString(lID)
labels["grpc.lb.backend_service"] = d.clusterName
}
stats.UpdateLabels(
info.Ctx,
map[string]string{
"grpc.lb.locality": xdsinternal.LocalityString(lID),
"grpc.lb.backend_service": d.clusterName,
},
)
if d.loadStore != nil {
locality := clients.Locality{Region: lID.Region, Zone: lID.Zone, SubZone: lID.SubZone}
+88
View File
@@ -0,0 +1,88 @@
/*
*
* Copyright 2026 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.
*
*/
package httpfilter
import (
"fmt"
"regexp"
"google.golang.org/grpc/internal/xds/matcher"
v3mutationpb "github.com/envoyproxy/go-control-plane/envoy/config/common/mutation_rules/v3"
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
)
// HeaderMutationRules specifies the rules for what modifications an external
// processing server may make to headers sent on the data plane RPC.
type HeaderMutationRules struct {
// AllowExpr specifies a regular expression that matches the headers that can
// be mutated.
AllowExpr *regexp.Regexp
// DisallowExpr specifies a regular expression that matches the headers that
// cannot be mutated. This overrides the above allowExpr if a header matches
// both.
DisallowExpr *regexp.Regexp
// DisallowAll specifies that no header mutations are allowed. This overrides
// all other settings.
DisallowAll bool
// DisallowIsError specifies whether to return an error if a header mutation
// is disallowed. If true, the data plane RPC will be failed with a grpc
// status code of Unknown.
DisallowIsError bool
}
// ConvertStringMatchers converts a slice of protobuf StringMatcher messages to
// a slice of matcher.StringMatcher.
func ConvertStringMatchers(patterns []*v3matcherpb.StringMatcher) ([]matcher.StringMatcher, error) {
matchers := make([]matcher.StringMatcher, 0, len(patterns))
for _, p := range patterns {
sm, err := matcher.StringMatcherFromProto(p)
if err != nil {
return nil, err
}
matchers = append(matchers, sm)
}
return matchers, nil
}
// HeaderMutationRulesFromProto converts a protobuf HeaderMutationRules proto
// message to a HeaderMutationRules struct.
func HeaderMutationRulesFromProto(mr *v3mutationpb.HeaderMutationRules) (HeaderMutationRules, error) {
var rules HeaderMutationRules
if mr == nil {
return rules, nil
}
if allowExpr := mr.GetAllowExpression(); allowExpr != nil {
re, err := matcher.CompileSafeRegex(allowExpr.GetRegex())
if err != nil {
return rules, fmt.Errorf("httpfilter: %v", err)
}
rules.AllowExpr = re
}
if disallowExpr := mr.GetDisallowExpression(); disallowExpr != nil {
re, err := matcher.CompileSafeRegex(disallowExpr.GetRegex())
if err != nil {
return rules, fmt.Errorf("httpfilter: %v", err)
}
rules.DisallowExpr = re
}
rules.DisallowAll = mr.GetDisallowAll().GetValue()
rules.DisallowIsError = mr.GetDisallowIsError().GetValue()
return rules, nil
}
+10
View File
@@ -31,6 +31,16 @@ type FilterConfig interface {
isFilterConfig()
}
// DisabledFilterConfig represents a disabled filter override. It implements the
// FilterConfig interface and can be returned by ParseFilterConfigOverride to
// indicate that the filter should be disabled. It is not used as a config for
// any filter, and is only used as a marker in the override configuration. For
// more information, see
// envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_filters#route-based-filter-chain
type DisabledFilterConfig struct{}
func (DisabledFilterConfig) isFilterConfig() {}
// Builder defines the parsing functionality of an HTTP filter. A Builder may
// optionally implement either ClientFilterBuilder or ServerFilterBuilder or
// both, indicating it is capable of working on the client side or server side
+1 -2
View File
@@ -24,7 +24,6 @@ import (
"strconv"
"strings"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/metadata"
)
@@ -94,7 +93,7 @@ func (hrm *HeaderRegexMatcher) Match(md metadata.MD) bool {
if !ok {
return false
}
return grpcutil.FullMatchWithRegex(hrm.re, v) != hrm.invert
return hrm.re.MatchString(v) != hrm.invert
}
func (hrm *HeaderRegexMatcher) String() string {
+14 -4
View File
@@ -27,7 +27,6 @@ import (
"strings"
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"google.golang.org/grpc/internal/grpcutil"
)
// StringMatcher contains match criteria for matching a string, and is an
@@ -70,7 +69,7 @@ func (sm StringMatcher) Match(input string) bool {
}
return strings.Contains(input, *sm.containsMatch)
case sm.regexMatch != nil:
return grpcutil.FullMatchWithRegex(sm.regexMatch, input)
return sm.regexMatch.MatchString(input)
}
return false
}
@@ -116,11 +115,11 @@ func StringMatcherFromProto(matcherProto *v3matcherpb.StringMatcher) (StringMatc
matcher.suffixMatch = newStrPtr(&mt.Suffix, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_SafeRegex:
regex := matcherProto.GetSafeRegex().GetRegex()
re, err := regexp.Compile(regex)
re, err := CompileSafeRegex(regex)
if err != nil {
return StringMatcher{}, fmt.Errorf("safe_regex matcher %q is invalid", regex)
}
matcher.regexMatch = re
matcher = NewRegexStringMatcher(re)
case *v3matcherpb.StringMatcher_Contains:
if matcherProto.GetContains() == "" {
return StringMatcher{}, errors.New("empty contains is not allowed in StringMatcher")
@@ -217,3 +216,14 @@ func (sm StringMatcher) Equal(other StringMatcher) bool {
}
return true
}
// CompileSafeRegex attempts to compile the provided pattern as a regular
// expression. It first compiles the unanchored pattern to catch syntax errors
// and then compiles and returns the explicitly anchored pattern to guarantee
// full-string matching.
func CompileSafeRegex(pattern string) (*regexp.Regexp, error) {
if _, err := regexp.Compile(pattern); err != nil {
return nil, err
}
return regexp.Compile(fmt.Sprintf("^(?:%s)$", pattern))
}
+77 -23
View File
@@ -21,7 +21,6 @@ import (
"fmt"
"net"
"net/netip"
"regexp"
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
@@ -79,7 +78,7 @@ func (pm *policyMatcher) match(data *rpcData) bool {
func matchersFromPermissions(permissions []*v3rbacpb.Permission) ([]matcher, error) {
var matchers []matcher
for _, permission := range permissions {
switch permission.GetRule().(type) {
switch p := permission.GetRule().(type) {
case *v3rbacpb.Permission_AndRules:
mList, err := matchersFromPermissions(permission.GetAndRules().Rules)
if err != nil {
@@ -121,16 +120,24 @@ func matchersFromPermissions(permissions []*v3rbacpb.Permission) ([]matcher, err
if err != nil {
return nil, err
}
if len(mList) != 1 {
return nil, fmt.Errorf("NotRule must contain exactly one rule")
}
matchers = append(matchers, &notMatcher{matcherToNot: mList[0]})
case *v3rbacpb.Permission_Metadata:
// Never matches - so no-op if not inverted, always match if
// inverted.
if permission.GetMetadata().GetInvert() { // Test metadata being no-op and also metadata with invert always matching
if permission.GetMetadata().GetInvert() {
matchers = append(matchers, &alwaysMatcher{})
} else {
matchers = append(matchers, &neverMatcher{})
}
case *v3rbacpb.Permission_RequestedServerName:
// Not supported in gRPC RBAC currently - a permission typed as
// requested server name in the initial config will be a no-op.
m, err := newRequestedServerNameMatcher(permission.GetRequestedServerName())
if err != nil {
return nil, err
}
matchers = append(matchers, m)
default:
return nil, fmt.Errorf("unsupported permission rule type: %T", p)
}
}
return matchers, nil
@@ -139,7 +146,7 @@ func matchersFromPermissions(permissions []*v3rbacpb.Permission) ([]matcher, err
func matchersFromPrincipals(principals []*v3rbacpb.Principal) ([]matcher, error) {
var matchers []matcher
for _, principal := range principals {
switch principal.GetIdentifier().(type) {
switch p := principal.GetIdentifier().(type) {
case *v3rbacpb.Principal_AndIds:
mList, err := matchersFromPrincipals(principal.GetAndIds().Ids)
if err != nil {
@@ -179,16 +186,29 @@ func matchersFromPrincipals(principals []*v3rbacpb.Principal) ([]matcher, error)
return nil, err
}
matchers = append(matchers, m)
case *v3rbacpb.Principal_Metadata:
if principal.GetMetadata().GetInvert() {
matchers = append(matchers, &alwaysMatcher{})
} else {
matchers = append(matchers, &neverMatcher{})
}
case *v3rbacpb.Principal_NotId:
mList, err := matchersFromPrincipals([]*v3rbacpb.Principal{{Identifier: principal.GetNotId().Identifier}})
if err != nil {
return nil, err
}
if len(mList) != 1 {
return nil, fmt.Errorf("NotId must contain exactly one identifier")
}
matchers = append(matchers, &notMatcher{matcherToNot: mList[0]})
case *v3rbacpb.Principal_SourceIp:
// The source ip principal identifier is deprecated. Thus, a
// principal typed as a source ip in the identifier will be a no-op.
// The config should use DirectRemoteIp instead.
// The source ip principal identifier is deprecated, but gRPC RBAC
// treats it as equivalent to direct_remote_ip as per A41.
m, err := newRemoteIPMatcher(principal.GetSourceIp())
if err != nil {
return nil, err
}
matchers = append(matchers, m)
case *v3rbacpb.Principal_RemoteIp:
// RBAC in gRPC treats direct_remote_ip and remote_ip as logically
// equivalent, as per A41.
@@ -197,9 +217,8 @@ func matchersFromPrincipals(principals []*v3rbacpb.Principal) ([]matcher, error)
return nil, err
}
matchers = append(matchers, m)
case *v3rbacpb.Principal_Metadata:
// Not supported in gRPC RBAC currently - a principal typed as
// Metadata in the initial config will be a no-op.
default:
return nil, fmt.Errorf("unsupported principal identifier type: %T", p)
}
}
return matchers, nil
@@ -249,6 +268,16 @@ func (am *alwaysMatcher) match(*rpcData) bool {
return true
}
// neverMatcher is a matcher that will never match. This logically represents a
// permission or principal that is unsupported in gRPC. neverMatcher implements
// the matcher interface.
type neverMatcher struct {
}
func (nm *neverMatcher) match(*rpcData) bool {
return false
}
// notMatcher is a matcher that nots an underlying matcher. notMatcher
// implements the matcher interface.
type notMatcher struct {
@@ -271,7 +300,7 @@ func newHeaderMatcher(headerMatcherConfig *v3route_componentspb.HeaderMatcher) (
case *v3route_componentspb.HeaderMatcher_ExactMatch:
m = internalmatcher.NewHeaderExactMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetExactMatch(), headerMatcherConfig.InvertMatch)
case *v3route_componentspb.HeaderMatcher_SafeRegexMatch:
regex, err := regexp.Compile(headerMatcherConfig.GetSafeRegexMatch().Regex)
regex, err := internalmatcher.CompileSafeRegex(headerMatcherConfig.GetSafeRegexMatch().GetRegex())
if err != nil {
return nil, err
}
@@ -398,6 +427,25 @@ func (pm *portMatcher) match(data *rpcData) bool {
return data.destinationPort == pm.destinationPort
}
// requestedServerNameMatcher matches on if the given string matcher matches
// on "", as per A41-xds-rbac.md. requestedServerNameMatcher implements
// the matcher interface.
type requestedServerNameMatcher struct {
stringMatcher internalmatcher.StringMatcher
}
func newRequestedServerNameMatcher(stringMatcherProto *v3matcherpb.StringMatcher) (*requestedServerNameMatcher, error) {
stringMatcher, err := internalmatcher.StringMatcherFromProto(stringMatcherProto)
if err != nil {
return nil, err
}
return &requestedServerNameMatcher{stringMatcher: stringMatcher}, nil
}
func (r *requestedServerNameMatcher) match(*rpcData) bool {
return r.stringMatcher.Match("")
}
// authenticatedMatcher matches on the name of the Principal. If set, the URI
// SAN or DNS SAN in that order is used from the certificate, otherwise the
// subject field is used. If unset, it applies to any user that is
@@ -435,17 +483,23 @@ func (am *authenticatedMatcher) match(data *rpcData) bool {
return am.stringMatcher.Match("")
}
cert := data.certs[0]
// The order of matching as per the RBAC documentation (see package-level comments)
// is as follows: URI SANs, DNS SANs, and then subject name.
for _, uriSAN := range cert.URIs {
if am.stringMatcher.Match(uriSAN.String()) {
return true
// Use the first non-empty identity source in priority order:
// URI SANs, then DNS SANs, then Subject.
if len(cert.URIs) > 0 {
for _, uriSAN := range cert.URIs {
if am.stringMatcher.Match(uriSAN.String()) {
return true
}
}
return false
}
for _, dnsSAN := range cert.DNSNames {
if am.stringMatcher.Match(dnsSAN) {
return true
if len(cert.DNSNames) > 0 {
for _, dnsSAN := range cert.DNSNames {
if am.stringMatcher.Match(dnsSAN) {
return true
}
}
return false
}
return am.stringMatcher.Match(cert.Subject.String())
}
+9 -15
View File
@@ -24,7 +24,6 @@ import (
"math/bits"
rand "math/rand/v2"
"strings"
"sync/atomic"
"time"
xxhash "github.com/cespare/xxhash/v2"
@@ -196,16 +195,13 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP
return nil, annotateErrorWithNodeID(status.Errorf(codes.Internal, "error retrieving cluster for match: %v (%T)", cluster, cluster), cs.xdsNodeID)
}
// Add a ref to the selected cluster, as this RPC needs this cluster until
// it is committed.
var ref *int32
// Add a ref to the selected cluster/plugin, as this RPC needs this
// cluster/plugin until it is committed.
if info, ok := cs.clusters[cluster.name]; ok {
ref = &info.refCount
info.refCount.Add(1)
} else if info, ok := cs.plugins[cluster.name]; ok {
info.refCount.Add(1)
}
if info, ok := cs.plugins[cluster.name]; ok {
ref = &info.refCount
}
atomic.AddInt32(ref, 1)
lbCtx := clustermanager.SetPickedCluster(rpcInfo.Context, cluster.name)
lbCtx = xdsresource.NewContextWithXDSConfig(lbCtx, cs.xdsConfig)
@@ -221,8 +217,7 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP
// When the RPC is committed, the cluster is no longer required.
// Decrease its ref.
if info, ok := cs.clusters[cluster.name]; ok {
ref := &info.refCount
if v := atomic.AddInt32(ref, -1); v == 0 {
if v := info.refCount.Add(-1); v == 0 {
// We call unsubscribe rather than sendNewServiceConfig to
// prevent redundant updates. If the reference count in the
// dependency manager drops to zero, it will automatically
@@ -233,8 +228,7 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP
}
}
if info, ok := cs.plugins[cluster.name]; ok {
ref := &info.refCount
if v := atomic.AddInt32(ref, -1); v == 0 {
if v := info.refCount.Add(-1); v == 0 {
// This entry will be removed from activePlugins when
// producing a new service config update.
cs.sendNewServiceConfig()
@@ -350,12 +344,12 @@ func (cs *configSelector) stop() {
// after a new one is active, we must trigger a subsequent update to delete
// the now-unused clusters.
for _, ci := range cs.clusters {
if v := atomic.AddInt32(&ci.refCount, -1); v == 0 {
if v := ci.refCount.Add(-1); v == 0 {
ci.unsubscribe()
}
}
for _, ci := range cs.plugins {
if v := atomic.AddInt32(&ci.refCount, -1); v == 0 {
if v := ci.refCount.Add(-1); v == 0 {
cs.sendNewServiceConfig()
}
}
+41 -7
View File
@@ -415,9 +415,26 @@ func (r *xdsResolver) newConfigSelector() (_ *configSelector, err error) {
for i, rt := range r.xdsConfig.VirtualHost.Routes {
clusters := rinternal.NewWRR.(func() wrr.WRR)()
interceptors := []iresolver.ClientInterceptor{}
// TODO: Carve out the common logic between the ClusterSpecifierPlugin
// and WeightedClusters.
if rt.ClusterSpecifierPlugin != "" {
clusterName := clusterSpecifierPluginPrefix + rt.ClusterSpecifierPlugin
clusters.Add(&routeCluster{name: clusterName}, 1)
interceptor, err := r.newInterceptor(r.xdsConfig.Listener.APIListener.HTTPFilters, nil, rt.HTTPFilterConfigOverride, r.xdsConfig.VirtualHost.HTTPFilterConfigOverride)
if err != nil {
// Clean up any interceptors that were successfully built
// for the current route before this error occurred. Note
// that this is not handled by the call to cs.stop() in the
// deferred function.
for _, i := range interceptors {
i.Close()
}
return nil, err
}
clusters.Add(&routeCluster{
name: clusterName,
interceptor: interceptor,
}, 1)
interceptors = append(interceptors, interceptor)
ci := r.addOrGetActiveClusterInfo(clusterName, "")
ci.cfg = xdsChildConfig{ChildPolicy: balancerConfig(r.xdsConfig.RouteConfig.ClusterSpecifierPlugins[rt.ClusterSpecifierPlugin])}
cs.plugins[clusterName] = ci
@@ -464,10 +481,10 @@ func (r *xdsResolver) newConfigSelector() (_ *configSelector, err error) {
// errors may occur. Note: cs.clusters are pointers to entries in
// activeClusters.
for _, ci := range cs.clusters {
atomic.AddInt32(&ci.refCount, 1)
ci.refCount.Add(1)
}
for _, ci := range cs.plugins {
atomic.AddInt32(&ci.refCount, 1)
ci.refCount.Add(1)
}
// Cleanup filter instances that are no longer specified in the current
@@ -496,13 +513,13 @@ func (r *xdsResolver) newConfigSelector() (_ *configSelector, err error) {
// Only executed in the context of a serializer callback.
func (r *xdsResolver) pruneActiveClustersAndPlugins() {
for cluster, ci := range r.activeClusters {
if atomic.LoadInt32(&ci.refCount) == 0 {
if ci.refCount.Load() == 0 {
ci.unsubscribe()
delete(r.activeClusters, cluster)
}
}
for cluster, ci := range r.activePlugins {
if atomic.LoadInt32(&ci.refCount) == 0 {
if ci.refCount.Load() == 0 {
delete(r.activePlugins, cluster)
}
}
@@ -535,8 +552,8 @@ func (r *xdsResolver) addOrGetActiveClusterInfo(key string, name string) *cluste
}
type clusterInfo struct {
// number of references to this cluster; accessed atomically
refCount int32
// refCount is the number of references to this cluster.
refCount atomic.Int32
// cfg is the child configuration for this cluster, containing either the
// csp config or the cds cluster config.
cfg xdsChildConfig
@@ -596,6 +613,23 @@ func (r *xdsResolver) newInterceptor(filters []xdsresource.HTTPFilter, clusterOv
if override == nil {
override = virtualHostOverride[filter.Name]
}
// Determine the effective disabled state of the filter. The base
// configuration's disabled state is used unless an override is present.
// If an override is present, the filter is disabled if the override is
// a DisabledFilterConfig.
disabled := filter.Disabled
if override != nil {
_, disabled = override.(httpfilter.DisabledFilterConfig)
}
if disabled {
if r.logger.V(2) {
r.logger.Infof("Filter %q has been disabled.", filter.Name)
}
continue
}
builder, ok := filter.Filter.(httpfilter.ClientFilterBuilder)
if !ok {
// Should not happen if it passed xdsClient validation.
+78 -25
View File
@@ -122,17 +122,7 @@ func (fcm *filterChainManager) filterChainFromConfig(config *xdsresource.Network
func (fcm *filterChainManager) stop() {
for _, fc := range fcm.filterChains {
urc := fc.usableRouteConfiguration.Load()
if urc.err != nil {
continue
}
for _, vh := range urc.vhs {
for _, r := range vh.routes {
if r.interceptor != nil {
r.interceptor.Close()
}
}
}
fc.usableRouteConfiguration.Load().stop()
fc.stop()
}
}
@@ -172,11 +162,20 @@ type sourcePrefixEntry struct {
// Listener resource. This struct contains the active state of a filter chain,
// which includes the usable route configuration.
type filterChain struct {
securityCfg *xdsresource.SecurityConfig
httpFilters []xdsresource.HTTPFilter
serverFilters []httpfilter.ServerFilter // Server filters with reference counts, stored for cleanup purposes.
routeConfigName string
inlineRouteConfig *xdsresource.RouteConfigUpdate
// The following fields are set at initialization time, and are not
// updated afterwards.
securityCfg *xdsresource.SecurityConfig
httpFilters []xdsresource.HTTPFilter
routeConfigName string
inlineRouteConfig *xdsresource.RouteConfigUpdate
// Server filters with reference counts. Is updated when a usable route
// configuration is set, and when the filter chain is stopped. Both these
// operations always happen with the listener wrapper's lock held.
serverFilters []httpfilter.ServerFilter
// The usable route configuration, is passed to the connWrapper, and is used
// to route incoming RPCs. Therefore, this needs to be accessed atomically.
usableRouteConfiguration *atomic.Pointer[usableRouteConfiguration]
}
@@ -188,6 +187,16 @@ type usableRouteConfiguration struct {
nodeID string // For logging purposes. Populated by the listener wrapper.
}
func (rc *usableRouteConfiguration) stop() {
for _, vh := range rc.vhs {
for _, r := range vh.routes {
if r.interceptor != nil {
r.interceptor.Close()
}
}
}
}
// virtualHostWithInterceptors captures information present in a VirtualHost
// update, and also contains routes with instantiated HTTP Filters.
type virtualHostWithInterceptors struct {
@@ -397,32 +406,61 @@ func (fc *filterChain) stop() {
// state across resource updates.
type serverFilterProvider func(filter xdsresource.HTTPFilter) (httpfilter.ServerFilter, error)
// constructUsableRouteConfiguration takes Route Configuration and converts it
// updateUsableRouteConfiguration takes Route Configuration and converts it
// into matchable route configuration, with instantiated HTTP Filters per route.
func (fc *filterChain) constructUsableRouteConfiguration(config xdsresource.RouteConfigUpdate, provider serverFilterProvider) *usableRouteConfiguration {
vhs := make([]virtualHostWithInterceptors, 0, len(config.VirtualHosts))
func (fc *filterChain) updateUsableRouteConfiguration(config *xdsresource.RouteConfigUpdate, updateErr error, provider serverFilterProvider, nodeID string) {
if updateErr != nil {
urc := &usableRouteConfiguration{err: updateErr, nodeID: nodeID}
fc.applyConfiguration(urc, nil)
return
}
var serverFilters []httpfilter.ServerFilter
vhs := make([]virtualHostWithInterceptors, 0, len(config.VirtualHosts))
for _, vh := range config.VirtualHosts {
vhwi, sfs, err := fc.convertVirtualHost(vh, provider)
if err != nil {
for _, sf := range serverFilters {
sf.Close()
}
// Close interceptors from successfully converted virtual hosts.
for _, v := range vhs {
for _, r := range v.routes {
if r.interceptor != nil {
r.interceptor.Close()
r.interceptor = nil
}
}
}
// Non nil if (lds + rds) fails, shouldn't happen since validated by
// xDS Client, treat as L7 error but shouldn't happen.
return &usableRouteConfiguration{err: fmt.Errorf("virtual host construction: %v", err)}
urc := &usableRouteConfiguration{err: fmt.Errorf("virtual host construction: %v", err), nodeID: nodeID}
fc.applyConfiguration(urc, nil)
return
}
vhs = append(vhs, vhwi)
serverFilters = append(serverFilters, sfs...)
}
// Release references to old server filters before replacing with new ones.
for _, sf := range fc.serverFilters {
sf.Close()
}
urc := &usableRouteConfiguration{vhs: vhs, nodeID: nodeID}
fc.applyConfiguration(urc, serverFilters)
}
func (fc *filterChain) applyConfiguration(urc *usableRouteConfiguration, serverFilters []httpfilter.ServerFilter) {
// Swap in the new configuration first so new RPCs use it immediately.
oldURC := fc.usableRouteConfiguration.Swap(urc)
oldFilters := fc.serverFilters
fc.serverFilters = serverFilters
return &usableRouteConfiguration{vhs: vhs}
// Stop the old interceptors before releasing the filters they might depend on.
if oldURC != nil {
oldURC.stop()
}
// Release references to old server filters.
for _, sf := range oldFilters {
sf.Close()
}
}
func (fc *filterChain) convertVirtualHost(virtualHost *xdsresource.VirtualHost, provider serverFilterProvider) (_ virtualHostWithInterceptors, _ []httpfilter.ServerFilter, err error) {
@@ -441,6 +479,13 @@ func (fc *filterChain) convertVirtualHost(virtualHost *xdsresource.VirtualHost,
rs[i].matcher = xdsresource.RouteToMatcher(r)
interceptor, sfs, err := fc.newInterceptor(r.HTTPFilterConfigOverride, virtualHost.HTTPFilterConfigOverride, provider)
if err != nil {
// Close interceptors from successfully converted routes.
for _, route := range rs {
if route.interceptor != nil {
route.interceptor.Close()
route.interceptor = nil
}
}
return virtualHostWithInterceptors{}, nil, err
}
serverFilters = append(serverFilters, sfs...)
@@ -478,6 +523,14 @@ func (fc *filterChain) newInterceptor(routeOverride, virtualHostOverride map[str
override = virtualHostOverride[filter.Name]
}
disabled := filter.Disabled
if override != nil {
_, disabled = override.(httpfilter.DisabledFilterConfig)
}
if disabled {
continue
}
serverFilter, err := provider(filter)
if err != nil {
return nil, nil, err
+6 -16
View File
@@ -154,7 +154,6 @@ func (l *listenerWrapper) maybeUpdateFilterChains() {
}
l.mu.Lock()
l.switchModeLocked(connectivity.ServingModeServing, nil)
// "Updates to a Listener cause all older connections on that Listener to be
// gracefully shut down with a grace period of 10 minutes for long-lived
// RPC's, such that clients will reconnect and have the updated
@@ -189,6 +188,7 @@ func (l *listenerWrapper) maybeUpdateFilterChains() {
delete(l.httpFilters, key)
}
}
l.switchModeLocked(connectivity.ServingModeServing, nil)
l.mu.Unlock()
go func() {
@@ -210,14 +210,10 @@ func (l *listenerWrapper) handleRDSUpdate(routeName string, rcu rdsWatcherUpdate
continue
}
if rcu.err != nil && rcu.data == nil { // Either NACK before update, or resource not found triggers this conditional.
urc := &usableRouteConfiguration{err: rcu.err}
urc.nodeID = l.xdsNodeID
fc.usableRouteConfiguration.Store(urc)
fc.updateUsableRouteConfiguration(nil, rcu.err, l.getOrCreateServerFilterLocked, l.xdsNodeID)
continue
}
urc := fc.constructUsableRouteConfiguration(*rcu.data, l.getOrCreateServerFilterLocked)
urc.nodeID = l.xdsNodeID
fc.usableRouteConfiguration.Store(urc)
fc.updateUsableRouteConfiguration(rcu.data, nil, l.getOrCreateServerFilterLocked, l.xdsNodeID)
}
}
l.mu.Unlock()
@@ -235,21 +231,15 @@ func (l *listenerWrapper) handleRDSUpdate(routeName string, rcu rdsWatcherUpdate
func (l *listenerWrapper) instantiateFilterChainRoutingConfigurationsLocked() {
for _, fc := range l.activeFilterChainManager.filterChains {
if fc.inlineRouteConfig != nil {
urc := fc.constructUsableRouteConfiguration(*fc.inlineRouteConfig, l.getOrCreateServerFilterLocked)
urc.nodeID = l.xdsNodeID
fc.usableRouteConfiguration.Store(urc) // Can't race with an RPC coming in but no harm making atomic.
fc.updateUsableRouteConfiguration(fc.inlineRouteConfig, nil, l.getOrCreateServerFilterLocked, l.xdsNodeID)
continue
} // Inline configuration constructed once here, will remain for lifetime of filter chain.
rcu := l.rdsHandler.updates[fc.routeConfigName]
if rcu.err != nil && rcu.data == nil {
urc := &usableRouteConfiguration{err: rcu.err}
urc.nodeID = l.xdsNodeID
fc.usableRouteConfiguration.Store(urc)
fc.updateUsableRouteConfiguration(nil, rcu.err, l.getOrCreateServerFilterLocked, l.xdsNodeID)
continue
}
urc := fc.constructUsableRouteConfiguration(*rcu.data, l.getOrCreateServerFilterLocked)
urc.nodeID = l.xdsNodeID
fc.usableRouteConfiguration.Store(urc) // Can't race with an RPC coming in but no harm making atomic.
fc.updateUsableRouteConfiguration(rcu.data, nil, l.getOrCreateServerFilterLocked, l.xdsNodeID)
}
}
@@ -0,0 +1,49 @@
/*
*
* Copyright 2026 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.
*/
package xdsresource
import (
"time"
"google.golang.org/grpc/metadata"
)
// GRPCServiceConfig contains the configuration for an external server. It is
// the parsed configuration for the GrpcService proto message.
// See: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/grpc_service.proto
type GRPCServiceConfig struct {
// TargetURI is the name of the external server.
TargetURI string
// ChannelCredentials specifies the configuration for the transport
// credentials to use to connect to the external server, as a JSON string.
ChannelCredentials string
// CallCredentials specifies the configuration for the per-RPC credentials to
// use when making calls to the external server, as a JSON string.
CallCredentials string
// Timeout is the RPC timeout for the call to the external server. If unset,
// the timeout depends on the usage of this external server. For example,
// cases like ext_authz and ext_proc, where there is a 1:1 mapping between the
// data plane RPC and the external server call, the timeout will be capped by
// the timeout on the data plane RPC. For cases like RLQS where there is a
// side channel to the external server, an unset timeout will result in no
// timeout being applied to the external server call.
Timeout time.Duration
// InitialMetadata is the additional metadata to include in all RPCs sent to
// the external server.
InitialMetadata metadata.MD
}
@@ -20,8 +20,6 @@ package xdsresource
import (
"regexp"
"strings"
"google.golang.org/grpc/internal/grpcutil"
)
type pathMatcher interface {
@@ -94,7 +92,7 @@ func newPathRegexMatcher(re *regexp.Regexp) *pathRegexMatcher {
}
func (prm *pathRegexMatcher) match(path string) bool {
return grpcutil.FullMatchWithRegex(prm.re, path)
return prm.re.MatchString(path)
}
func (prm *pathRegexMatcher) String() string {
@@ -21,15 +21,20 @@ import (
"fmt"
"net/netip"
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/protobuf/types/known/anypb"
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
v3gcpauthnpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/gcp_authn/v3"
)
func init() {
if envconfig.XDSHTTPConnectEnabled {
registerMetadataConverter("type.googleapis.com/envoy.config.core.v3.Address", proxyAddressConvertor{})
}
if envconfig.GCPAuthenticationFilterEnabled {
registerMetadataConverter("type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience", audienceConverter{})
}
}
var (
@@ -100,3 +105,28 @@ func (proxyAddressConvertor) convert(anyProto *anypb.Any) (any, error) {
}
return ProxyAddressMetadataValue{Address: parseAddress(socketaddress)}, nil
}
// AudienceMetadataValue holds the audience parsed from the
// envoy.extensions.filters.http.gcp_authn.v3.Audience proto message, as
// specified in gRFC A83.
type AudienceMetadataValue struct {
// Audience is the URL of the receiving service that performs token
// authentication.
Audience string
}
// audienceConverter implements the metadataConverter interface to
// handle the conversion of envoy.extensions.filters.http.gcp_authn.v3.Audience
// protobuf messages into an internal representation.
type audienceConverter struct{}
func (audienceConverter) convert(anyProto *anypb.Any) (any, error) {
audienceProto := &v3gcpauthnpb.Audience{}
if err := anyProto.UnmarshalTo(audienceProto); err != nil {
return nil, fmt.Errorf("failed to unmarshal the envoy.extensions.filters.http.gcp_authn.v3.Audience resource from Any proto: %v", err)
}
if audienceProto.GetUrl() == "" {
return nil, fmt.Errorf("empty url field in audience metadata")
}
return AudienceMetadataValue{Audience: audienceProto.GetUrl()}, nil
}
@@ -83,6 +83,9 @@ type ClusterUpdate struct {
// LRSReportEndpointMetrics specifies the subset of ORCA metrics that
// should be propagated to the LRS server.
LRSReportEndpointMetrics *LRSReportEndpointMetricsConfig
// Metadata contains the metadata from the cluster resource.
Metadata map[string]any
}
// LRSReportEndpointMetricsConfig holds the configuration for propagating ORCA
@@ -69,6 +69,9 @@ type HTTPFilter struct {
Filter httpfilter.Builder
// Config contains the filter's configuration
Config httpfilter.FilterConfig
// Disabled specifies if the filter is disabled. For more information, see
// envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_filters#route-based-filter-chain
Disabled bool
}
// InboundListenerConfig contains information about the inbound listener, i.e
@@ -215,6 +215,14 @@ func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster, serv
}
}
var metadata map[string]any
if envconfig.GCPAuthenticationFilterEnabled {
var err error
if metadata, err = validateAndConstructMetadata(cluster.GetMetadata()); err != nil {
return ClusterUpdate{}, err
}
}
ret := ClusterUpdate{
ClusterName: cluster.GetName(),
SecurityCfg: sc,
@@ -223,6 +231,7 @@ func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster, serv
OutlierDetection: od,
TelemetryLabels: telemetryLabels,
LRSReportEndpointMetrics: lrsReportEndpointMetrics,
Metadata: metadata,
}
if lrs := cluster.GetLrsServer(); lrs != nil {
@@ -26,6 +26,7 @@ import (
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"google.golang.org/grpc/experimental/balancer/hostname"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/pretty"
xdsinternal "google.golang.org/grpc/internal/xds"
@@ -36,27 +37,12 @@ import (
"google.golang.org/protobuf/types/known/anypb"
)
// hostnameKeyType is the key to store the hostname attribute in
// a resolver.Endpoint.
type hostnameKeyType struct{}
// SetHostname returns a copy of the given endpoint with hostname added
// as an attribute.
func SetHostname(endpoint resolver.Endpoint, hostname string) resolver.Endpoint {
// Only set if non-empty; xds_cluster_impl uses this to trigger :authority
// rewriting.
if hostname == "" {
return endpoint
}
endpoint.Attributes = endpoint.Attributes.WithValue(hostnameKeyType{}, hostname)
return endpoint
}
// Hostname returns the hostname from the BalancerAttributes of the given
// Address. If this attribute is not set, it returns the empty string.
// Hostname returns the hostname from the BalancerAttributes of the
// given Address. If this attribute is not set, it returns the empty
// string.
func Hostname(addr resolver.Address) string {
hostname, _ := addr.BalancerAttributes.Value(hostnameKeyType{}).(string)
return hostname
ep := resolver.Endpoint{Attributes: addr.BalancerAttributes}
return hostname.FromEndpoint(ep)
}
func unmarshalEndpointsResource(r *anypb.Any) (string, EndpointsUpdate, error) {
@@ -166,7 +152,7 @@ func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint, uniqueEndpointAddrs
}
}
endpoint := resolver.Endpoint{Addresses: address}
endpoint = SetHostname(endpoint, lbEndpoint.GetEndpoint().GetHostname())
endpoint = hostname.Set(endpoint, lbEndpoint.GetEndpoint().GetHostname())
endpoint = ringhash.SetHashKey(endpoint, hashKey)
endpoints = append(endpoints, Endpoint{
ResolverEndpoint: endpoint,
@@ -268,6 +254,8 @@ func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate,
return ret, nil
}
// validateAndConstructMetadata processes the metadata from the xDS resource
// and returns a map of parsed metadata values.
func validateAndConstructMetadata(metadataProto *v3corepb.Metadata) (map[string]any, error) {
if metadataProto == nil {
return nil, nil
@@ -30,6 +30,7 @@ import (
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/xds/clients/xdsclient"
"google.golang.org/grpc/internal/xds/httpfilter"
"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
@@ -178,6 +179,7 @@ func processHTTPFilterOverrides(cfgs map[string]*anypb.Any) (map[string]httpfilt
m := make(map[string]httpfilter.FilterConfig)
for name, cfg := range cfgs {
optional := false
disabled := false
s := new(v3routepb.FilterConfig)
if cfg.MessageIs(s) {
if err := cfg.UnmarshalTo(s); err != nil {
@@ -185,6 +187,14 @@ func processHTTPFilterOverrides(cfgs map[string]*anypb.Any) (map[string]httpfilt
}
cfg = s.GetConfig()
optional = s.GetIsOptional()
if envconfig.XDSClientExtProcEnabled {
disabled = s.GetDisabled()
}
}
if disabled {
m[name] = httpfilter.DisabledFilterConfig{}
continue
}
httpFilter, config, err := validateHTTPFilterConfig(cfg, false, optional)
@@ -235,8 +245,12 @@ func processHTTPFilters(filters []*v3httppb.HttpFilter, server bool) ([]HTTPFilt
return nil, fmt.Errorf("HTTP filter %q not supported client-side", name)
}
disabled := false
if envconfig.XDSClientExtProcEnabled {
disabled = filter.GetDisabled()
}
// Save name/config
ret = append(ret, HTTPFilter{Name: name, Filter: httpFilter, Config: config})
ret = append(ret, HTTPFilter{Name: name, Filter: httpFilter, Config: config, Disabled: disabled})
}
// "Validation will fail if a terminal filter is not the last filter in the
// chain or if a non-terminal filter is the last filter in the chain." - A39
@@ -241,7 +241,7 @@ func routesProtoToSlice(routes []*v3routepb.Route, csps map[string]clusterspecif
route.Path = &pt.Path
case *v3routepb.RouteMatch_SafeRegex:
regex := pt.SafeRegex.GetRegex()
re, err := regexp.Compile(regex)
re, err := matcher.CompileSafeRegex(regex)
if err != nil {
return nil, nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex)
}
@@ -261,7 +261,7 @@ func routesProtoToSlice(routes []*v3routepb.Route, csps map[string]clusterspecif
header.ExactMatch = &ht.ExactMatch
case *v3routepb.HeaderMatcher_SafeRegexMatch:
regex := ht.SafeRegexMatch.GetRegex()
re, err := regexp.Compile(regex)
re, err := matcher.CompileSafeRegex(regex)
if err != nil {
return nil, nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex)
}
@@ -21,7 +21,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/reflection/v1/reflection.proto
@@ -18,7 +18,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// grpc/reflection/v1alpha/reflection.proto is a deprecated file.
+36 -6
View File
@@ -128,6 +128,16 @@ func NewGZIPDecompressor() Decompressor {
}
func (d *gzipDecompressor) Do(r io.Reader) ([]byte, error) {
return d.doWithMaxSize(r, math.MaxInt64)
}
// doWithMaxSize behaves like Do but caps the size of the decompressed
// payload at maxMessageSize+1 bytes. The Decompressor interface does not
// allow extra parameters, so callers inside the package type-assert to
// *gzipDecompressor to invoke this method directly. The +1 byte makes it
// possible for the caller to detect that the limit was exceeded and
// return ResourceExhausted instead of materializing an unbounded payload.
func (d *gzipDecompressor) doWithMaxSize(r io.Reader, maxMessageSize int64) ([]byte, error) {
var z *gzip.Reader
switch maybeZ := d.pool.Get().(type) {
case nil:
@@ -148,7 +158,11 @@ func (d *gzipDecompressor) Do(r io.Reader) ([]byte, error) {
z.Close()
d.pool.Put(z)
}()
return io.ReadAll(z)
var src io.Reader = z
if maxMessageSize < math.MaxInt64 {
src = io.LimitReader(z, maxMessageSize+1)
}
return io.ReadAll(src)
}
func (d *gzipDecompressor) Type() string {
@@ -830,15 +844,15 @@ func compress(in mem.BufferSlice, cp Compressor, compressor encoding.Compressor,
if compressor != nil {
z, err := compressor.Compress(w)
if err != nil {
return nil, 0, wrapErr(err)
return nil, compressionNone, wrapErr(err)
}
for _, b := range in {
if _, err := z.Write(b.ReadOnlyData()); err != nil {
return nil, 0, wrapErr(err)
return nil, compressionNone, wrapErr(err)
}
}
if err := z.Close(); err != nil {
return nil, 0, wrapErr(err)
return nil, compressionNone, wrapErr(err)
}
} else {
// This is obviously really inefficient since it fully materializes the data, but
@@ -848,7 +862,7 @@ func compress(in mem.BufferSlice, cp Compressor, compressor encoding.Compressor,
buf := in.MaterializeToBuffer(pool)
defer buf.Free()
if err := cp.Do(w, buf.ReadOnlyData()); err != nil {
return nil, 0, wrapErr(err)
return nil, compressionNone, wrapErr(err)
}
}
return out, compressionMade, nil
@@ -971,7 +985,20 @@ func recvAndDecompress(p *parser, s recvCompressor, dc Decompressor, maxReceiveM
func decompress(compressor encoding.Compressor, d mem.BufferSlice, dc Decompressor, maxReceiveMessageSize int, pool mem.BufferPool) (mem.BufferSlice, error) {
if dc != nil {
r := d.Reader()
uncompressed, err := dc.Do(r)
// For the built-in gzip decompressor, bound the decompressed output
// at maxReceiveMessageSize+1 so that a small but highly compressed
// payload (a "zip bomb") cannot expand to gigabytes in memory before
// the post-decompression size check below has a chance to fire. The
// Decompressor interface does not accept an extra size parameter,
// so we type-assert to invoke a size-aware helper. Third-party
// Decompressor implementations keep the original Do behavior.
var uncompressed []byte
var err error
if gd, ok := dc.(*gzipDecompressor); ok {
uncompressed, err = gd.doWithMaxSize(r, int64(maxReceiveMessageSize))
} else {
uncompressed, err = dc.Do(r)
}
if err != nil {
r.Close() // ensure buffers are reused
return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err)
@@ -989,6 +1016,9 @@ func decompress(compressor encoding.Compressor, d mem.BufferSlice, dc Decompress
r.Close() // ensure buffers are reused
return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the message: %v", err)
}
if closer, ok := dcReader.(io.Closer); ok {
defer closer.Close()
}
// Read at most one byte more than the limit from the decompressor.
// Unless the limit is MaxInt64, in which case, that's impossible, so
+27 -25
View File
@@ -28,6 +28,7 @@ import (
"net/http"
"reflect"
"runtime"
"runtime/pprof"
"strings"
"sync"
"sync/atomic"
@@ -150,8 +151,6 @@ type Server struct {
serverWorkerChannel chan func()
serverWorkerChannelClose func()
strictPathCheckingLogEmitted atomic.Bool
}
type serverOptions struct {
@@ -250,10 +249,8 @@ func newJoinServerOption(opts ...ServerOption) ServerOption {
// If this option is set to true every connection will release the buffer after
// flushing the data on the wire.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
// Deprecated: shared write buffer is enabled by default. SharedWriteBuffer
// will be removed in a future release.
func SharedWriteBuffer(val bool) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.sharedWriteBuffer = val
@@ -302,6 +299,14 @@ func InitialConnWindowSize(s int32) ServerOption {
// window size to the value provided and disables dynamic flow control.
// The lower bound for window size is 64K and any value smaller than that
// will be ignored.
//
// Note that this also disables dynamic flow control for the connection,
// falling back to a default static connection-level window of 64KB. To
// use a larger connection-level window, you must also use the
// [StaticConnWindowSize] ServerOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func StaticStreamWindowSize(s int32) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.initialWindowSize = s
@@ -313,6 +318,14 @@ func StaticStreamWindowSize(s int32) ServerOption {
// window size to the value provided and disables dynamic flow control.
// The lower bound for window size is 64K and any value smaller than that
// will be ignored.
//
// Note that this also disables dynamic flow control for individual streams,
// falling back to a default static connection-level window of 64KB. To
// explicitly configure the stream-level window size, you must also use the
// [StaticStreamWindowSize] ServerOption.
//
// Most users should not configure static flow control windows unless
// operating in a memory-constrained environment.
func StaticConnWindowSize(s int32) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.initialConnWindowSize = s
@@ -1787,6 +1800,12 @@ func (s *Server) handleMalformedMethodName(stream *transport.ServerStream, ti *t
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) {
ctx := stream.Context()
ctx = contextWithServer(ctx, s)
if envconfig.LabelServerGoroutines&envconfig.GoroutineLabelServerMethod != 0 {
// This method always runs in its own goroutine, so we can set a
// goroutine label without needing to restore a previous context.
ctx = pprof.WithLabels(ctx, pprof.Labels("grpc.method", stream.Method()))
pprof.SetGoroutineLabels(ctx)
}
var ti *traceInfo
if EnableTracing {
tr := newTrace("grpc.Recv."+methodFamily(stream.Method()), stream.Method())
@@ -1803,28 +1822,11 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Ser
}
}
sm := stream.Method()
if sm == "" {
sm, found := strings.CutPrefix(stream.Method(), "/")
if !found {
s.handleMalformedMethodName(stream, ti)
return
}
if sm[0] != '/' {
// TODO(easwars): Add a link to the CVE in the below log messages once
// published.
if envconfig.DisableStrictPathChecking {
if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream received malformed method name %q. Allowing it because the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING is set to true, but this option will be removed in a future release.", sm)
}
} else {
if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream rejected malformed method name %q. To temporarily allow such requests, set the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to true. Note that this is not recommended as it may allow requests to bypass security policies.", sm)
}
s.handleMalformedMethodName(stream, ti)
return
}
} else {
sm = sm[1:]
}
pos := strings.LastIndex(sm, "/")
if pos == -1 {
s.handleMalformedMethodName(stream, ti)
+18 -28
View File
@@ -18,6 +18,7 @@ package opentelemetry
import (
"context"
"maps"
"sync/atomic"
"time"
@@ -76,7 +77,7 @@ func getOrCreateCallInfo(ctx context.Context, cc *grpc.ClientConn, method string
target: cc.CanonicalTarget(),
method: determineMethod(method, opts...),
}
ctx = setCallInfo(ctx, ci)
ctx = context.WithValue(ctx, callInfoKey{}, ci)
}
return ctx, ci
}
@@ -157,47 +158,36 @@ func (h *clientMetricsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo
// HandleConn exists to satisfy stats.Handler.
func (h *clientMetricsHandler) HandleConn(context.Context, stats.ConnStats) {}
// getOrCreateRPCAttemptInfo retrieves or creates an rpc attemptInfo object
// and ensures it is set in the context along with the rpcInfo.
func getOrCreateRPCAttemptInfo(ctx context.Context) (context.Context, *attemptInfo) {
ri := getRPCInfo(ctx)
if ri != nil {
return ctx, ri.ai
}
ri = &rpcInfo{ai: &attemptInfo{}}
return setRPCInfo(ctx, ri), ri.ai
}
// TagRPC implements per RPC attempt context management for metrics.
func (h *clientMetricsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
// Numerous stats handlers can be used for the same channel. The cluster
// impl balancer which writes to this will only write once, thus have this
// stats handler's per attempt scoped context point to the same optional
// labels map if set.
var labels *istats.Labels
if labels = istats.GetLabels(ctx); labels == nil {
labels = &istats.Labels{
ctx, ri := getOrCreateClientRPCInfo(ctx)
ai := ri.ai
if ai.xdsLabels == nil {
ai.xdsLabels = map[string]string{
// The defaults for all the per call labels from a plugin that
// executes on the callpath that this OpenTelemetry component
// currently supports.
TelemetryLabels: map[string]string{
"grpc.lb.locality": "",
"grpc.lb.backend_service": "",
},
"grpc.lb.locality": "",
"grpc.lb.backend_service": "",
}
ctx = istats.SetLabels(ctx, labels)
}
ctx, ai := getOrCreateRPCAttemptInfo(ctx)
// Numerous stats handlers can be used for the same channel. This callback
// ensures that all label updates are propagated to the rpc attempt info across
// derived contexts.
ctx = istats.RegisterTelemetryLabelCallback(ctx, func(labels map[string]string) {
maps.Copy(ai.xdsLabels, labels)
})
ai.startTime = time.Now()
ai.xdsLabels = labels.TelemetryLabels
ai.method = removeLeadingSlash(info.FullMethodName)
return setRPCInfo(ctx, &rpcInfo{ai: ai})
return ctx
}
// HandleRPC handles per RPC stats implementation.
func (h *clientMetricsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ri := getRPCInfo(ctx)
ri := clientRPCInfo(ctx)
if ri == nil {
logger.Error("ctx passed into client side stats handler metrics event handling has no client attempt data present")
return
+25 -6
View File
@@ -21,6 +21,7 @@ import (
"log"
"strings"
"go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
@@ -83,7 +84,10 @@ func (h *clientTracingHandler) finishTrace(err error, ts trace.Span) {
// It creates a new outgoing carrier which serializes information about this
// span into gRPC Metadata, if TextMapPropagator is provided in the trace
// options. if TextMapPropagator is not provided, it returns the context as is.
func (h *clientTracingHandler) traceTagRPC(ctx context.Context, ai *attemptInfo, nameResolutionDelayed bool) (context.Context, *attemptInfo) {
//
// Note: The passed attemptInfo pointer (ai) is mutated in-place. Fields such as
// ai.traceSpan are updated directly. No new attemptInfo is returned.
func (h *clientTracingHandler) traceTagRPC(ctx context.Context, ai *attemptInfo, nameResolutionDelayed bool) context.Context {
// Add a "Delayed name resolution complete" event to the call span
// if there was name resolution delay. In case of multiple retry attempts,
// ensure that event is added only once.
@@ -98,7 +102,7 @@ func (h *clientTracingHandler) traceTagRPC(ctx context.Context, ai *attemptInfo,
carrier := otelinternaltracing.NewOutgoingCarrier(ctx)
h.options.TraceOptions.TextMapPropagator.Inject(ctx, carrier)
ai.traceSpan = span
return carrier.Context(), ai
return carrier.Context()
}
// createCallTraceSpan creates a call span to put in the provided context using
@@ -120,17 +124,32 @@ func (h *clientTracingHandler) HandleConn(context.Context, stats.ConnStats) {}
// TagRPC implements per RPC attempt context management for traces.
func (h *clientTracingHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
ctx, ai := getOrCreateRPCAttemptInfo(ctx)
ctx, ai = h.traceTagRPC(ctx, ai, info.NameResolutionDelay)
return setRPCInfo(ctx, &rpcInfo{ai: ai})
ctx, ri := getOrCreateClientRPCInfo(ctx)
ci := getCallInfo(ctx)
if ci == nil {
logger.Error("context passed into client side stats handler (TagRPC) has no call info")
return ctx
}
ctx = h.traceTagRPC(ctx, ri.ai, info.NameResolutionDelay)
return ctx
}
// HandleRPC handles per RPC tracing implementation.
func (h *clientTracingHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ri := getRPCInfo(ctx)
ri := clientRPCInfo(ctx)
if ri == nil {
logger.Error("ctx passed into client side tracing handler trace event handling has no client attempt data present")
return
}
// Client-specific Begin attributes.
if begin, ok := rs.(*stats.Begin); ok {
ci := getCallInfo(ctx)
previousRPCAttempts := ci.previousRPCAttempts.Add(1) - 1
ri.ai.traceSpan.SetAttributes(
attribute.Int64("previous-rpc-attempts", int64(previousRPCAttempts)),
attribute.Bool("transparent-retry", begin.IsTransparentRetryAttempt),
)
}
populateSpan(rs, ri.ai)
}
+33 -13
View File
@@ -179,14 +179,13 @@ type callInfo struct {
// nameResolutionEventAdded is set when the resolver delay trace event
// is added. Prevents duplicate events, since it is reported per-attempt.
nameResolutionEventAdded atomic.Bool
// previousRPCAttempts holds the count of RPC attempts that have happened
// before current attempt. Transparent retries are excluded.
previousRPCAttempts atomic.Uint32
}
type callInfoKey struct{}
func setCallInfo(ctx context.Context, ci *callInfo) context.Context {
return context.WithValue(ctx, callInfoKey{}, ci)
}
// getCallInfo returns the callInfo stored in the context, or nil
// if there isn't one.
func getCallInfo(ctx context.Context) *callInfo {
@@ -200,19 +199,41 @@ type rpcInfo struct {
ai *attemptInfo
}
type rpcInfoKey struct{}
type clientRPCInfoKey struct{}
type serverRPCInfoKey struct{}
func setRPCInfo(ctx context.Context, ri *rpcInfo) context.Context {
return context.WithValue(ctx, rpcInfoKey{}, ri)
// clientRPCInfo returns the rpcInfo stored in the context for client, or nil
// if there isn't one.
func clientRPCInfo(ctx context.Context) *rpcInfo {
ri, _ := ctx.Value(clientRPCInfoKey{}).(*rpcInfo)
return ri
}
// getRPCInfo returns the rpcInfo stored in the context, or nil
// serverRPCInfo returns the rpcInfo stored in the context for server, or nil
// if there isn't one.
func getRPCInfo(ctx context.Context) *rpcInfo {
ri, _ := ctx.Value(rpcInfoKey{}).(*rpcInfo)
func serverRPCInfo(ctx context.Context) *rpcInfo {
ri, _ := ctx.Value(serverRPCInfoKey{}).(*rpcInfo)
return ri
}
func getOrCreateClientRPCInfo(ctx context.Context) (context.Context, *rpcInfo) {
ri := clientRPCInfo(ctx)
if ri != nil {
return ctx, ri
}
ri = &rpcInfo{ai: &attemptInfo{}}
return context.WithValue(ctx, clientRPCInfoKey{}, ri), ri
}
func getOrCreateServerRPCInfo(ctx context.Context) (context.Context, *rpcInfo) {
ri := serverRPCInfo(ctx)
if ri != nil {
return ctx, ri
}
ri = &rpcInfo{ai: &attemptInfo{}}
return context.WithValue(ctx, serverRPCInfoKey{}, ri), ri
}
func removeLeadingSlash(mn string) string {
return strings.TrimLeft(mn, "/")
}
@@ -239,9 +260,8 @@ type attemptInfo struct {
// message counters for sent and received messages (used for
// generating message IDs), and the number of previous RPC attempts for the
// associated call.
countSentMsg uint32
countRecvMsg uint32
previousRPCAttempts uint32
countSentMsg uint32
countRecvMsg uint32
}
type clientMetrics struct {
+4 -3
View File
@@ -196,16 +196,17 @@ func (h *serverMetricsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInf
method = "other"
}
}
ctx, ai := getOrCreateRPCAttemptInfo(ctx)
ctx, ri := getOrCreateServerRPCInfo(ctx)
ai := ri.ai
ai.startTime = time.Now()
ai.method = removeLeadingSlash(method)
return setRPCInfo(ctx, &rpcInfo{ai: ai})
return ctx
}
// HandleRPC handles per RPC stats implementation.
func (h *serverMetricsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ri := getRPCInfo(ctx)
ri := serverRPCInfo(ctx)
if ri == nil {
logger.Error("ctx passed into server side stats handler metrics event handling has no server call data present")
return
+4 -4
View File
@@ -40,9 +40,9 @@ func (h *serverTracingHandler) initializeTraces() {
// TagRPC implements per RPC attempt context management for traces.
func (h *serverTracingHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context {
ctx, ai := getOrCreateRPCAttemptInfo(ctx)
ctx, ai = h.traceTagRPC(ctx, ai)
return setRPCInfo(ctx, &rpcInfo{ai: ai})
ctx, ri := getOrCreateServerRPCInfo(ctx)
ctx, _ = h.traceTagRPC(ctx, ri.ai)
return ctx
}
// traceTagRPC populates context with new span data using the TextMapPropagator
@@ -67,7 +67,7 @@ func (h *serverTracingHandler) traceTagRPC(ctx context.Context, ai *attemptInfo)
// HandleRPC handles per RPC tracing implementation.
func (h *serverTracingHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ri := getRPCInfo(ctx)
ri := serverRPCInfo(ctx)
if ri == nil {
logger.Error("ctx passed into server side tracing handler trace event handling has no server call data present")
return
-14
View File
@@ -17,8 +17,6 @@
package opentelemetry
import (
"sync/atomic"
"go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
@@ -40,18 +38,6 @@ func populateSpan(rs stats.RPCStats, ai *attemptInfo) {
span := ai.traceSpan
switch rs := rs.(type) {
case *stats.Begin:
// Note: Go always added Client and FailFast attributes even though they are not
// defined by the OpenCensus gRPC spec. Thus, they are unimportant for
// correctness.
span.SetAttributes(
attribute.Bool("Client", rs.Client),
attribute.Bool("FailFast", rs.FailFast),
attribute.Int64("previous-rpc-attempts", int64(ai.previousRPCAttempts)),
attribute.Bool("transparent-retry", rs.IsTransparentRetryAttempt),
)
// increment previous rpc attempts applicable for next attempt
atomic.AddUint32(&ai.previousRPCAttempts, 1)
case *stats.DelayedPickComplete:
span.AddEvent("Delayed LB pick complete")
case *stats.InPayload:
+1 -1
View File
@@ -19,4 +19,4 @@
package grpc
// Version is the current grpc version.
const Version = "1.81.0"
const Version = "1.82.1"
+9 -6
View File
@@ -73,7 +73,7 @@ cloud.google.com/go/storage/experimental
cloud.google.com/go/storage/internal
cloud.google.com/go/storage/internal/apiv2
cloud.google.com/go/storage/internal/apiv2/storagepb
# github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0
# github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0
## explicit; go 1.24.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp
# github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0
@@ -314,6 +314,7 @@ github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/gcp_authn/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3
github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3
@@ -748,7 +749,7 @@ github.com/zeromq/goczmq
## explicit; go 1.24.0
go.opentelemetry.io/auto/sdk
go.opentelemetry.io/auto/sdk/internal/telemetry
# go.opentelemetry.io/contrib/detectors/gcp v1.42.0
# go.opentelemetry.io/contrib/detectors/gcp v1.43.0
## explicit; go 1.25.0
go.opentelemetry.io/contrib/detectors/gcp
# go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0
@@ -957,7 +958,7 @@ google.golang.org/genproto/googleapis/type/date
google.golang.org/genproto/googleapis/type/expr
google.golang.org/genproto/googleapis/type/interval
google.golang.org/genproto/googleapis/type/timeofday
# google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9
# google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478
## explicit; go 1.25.0
google.golang.org/genproto/googleapis/api
google.golang.org/genproto/googleapis/api/annotations
@@ -968,12 +969,12 @@ google.golang.org/genproto/googleapis/api/label
google.golang.org/genproto/googleapis/api/metric
google.golang.org/genproto/googleapis/api/monitoredres
google.golang.org/genproto/googleapis/api/serviceconfig
# google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d
# google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478
## explicit; go 1.25.0
google.golang.org/genproto/googleapis/rpc/code
google.golang.org/genproto/googleapis/rpc/errdetails
google.golang.org/genproto/googleapis/rpc/status
# google.golang.org/grpc v1.81.0
# google.golang.org/grpc v1.82.1
## explicit; go 1.25.0
google.golang.org/grpc
google.golang.org/grpc/attributes
@@ -1021,6 +1022,8 @@ google.golang.org/grpc/encoding
google.golang.org/grpc/encoding/gzip
google.golang.org/grpc/encoding/internal
google.golang.org/grpc/encoding/proto
google.golang.org/grpc/experimental/balancer/hostname
google.golang.org/grpc/experimental/balancer/weight
google.golang.org/grpc/experimental/opentelemetry
google.golang.org/grpc/experimental/stats
google.golang.org/grpc/grpclog
@@ -1032,7 +1035,6 @@ google.golang.org/grpc/internal/admin
google.golang.org/grpc/internal/backoff
google.golang.org/grpc/internal/balancer/gracefulswitch
google.golang.org/grpc/internal/balancer/nop
google.golang.org/grpc/internal/balancer/weight
google.golang.org/grpc/internal/balancergroup
google.golang.org/grpc/internal/balancerload
google.golang.org/grpc/internal/binarylog
@@ -1066,6 +1068,7 @@ google.golang.org/grpc/internal/stats
google.golang.org/grpc/internal/status
google.golang.org/grpc/internal/syscall
google.golang.org/grpc/internal/transport
google.golang.org/grpc/internal/transport/internal
google.golang.org/grpc/internal/transport/networktype
google.golang.org/grpc/internal/transport/readyreader
google.golang.org/grpc/internal/wrr