diff --git a/api/BUILD b/api/BUILD index 2b36b337b1c3..4cd9d47ab049 100644 --- a/api/BUILD +++ b/api/BUILD @@ -104,6 +104,8 @@ proto_library( "//contrib/envoy/extensions/private_key_providers/kae/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD new file mode 100644 index 000000000000..6409469bbd62 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], +) diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto new file mode 100644 index 000000000000..eaa122194bc2 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client; + +import "google/protobuf/duration.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client"; +option java_outer_classname = "GrpcClientProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// Configuration for gRPC push-based connection event client. +// Actively pushes connection events to a cluster using grpc using some internal timing. +// [#next-free-field: 7] +message GrpcClientConfig { + // Stat prefix for this client's metrics. + string stat_prefix = 1; + + // Name of the cluster to send gRPC requests to. + // It must be present in the config otherwise the setup will throw error in the onServerInitialized. + string cluster = 2 [(validate.rules).string = {min_len: 1}]; + + // Default interval between sending batched connection events. + // Default is 5s. + google.protobuf.Duration default_send_interval = 3 [(validate.rules).duration = { + lte {seconds: 3600} + gte {nanos: 25000000} + }]; + + // Interval between connection retry attempts to the gRPC service. + // Connect timeouts are provided at the cluster level and will be handled by the http/2 client. + // How much time to wait after a failed connect before retrying. Default is 5s. + google.protobuf.Duration connect_retry_interval = 4 [(validate.rules).duration = { + lte {seconds: 3600} + gte {nanos: 25000000} + }]; + + // Maximum number of retry attempts for failed gRPC sends. + // Basically the cluster will have default_send_interval * max_retries time to respond. + // Default is 5. After this we will disconnect and try to connect again. + uint32 max_retries = 5; + + // Maximum events to buffer at any given time + // Default is 1,000,000. + uint32 max_buffer = 6; +} diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto new file mode 100644 index 000000000000..ebc381c0c609 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto @@ -0,0 +1,117 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client; + +import "envoy/config/core/v3/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/rpc/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client"; +option java_outer_classname = "StreamReverseTunnelsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Reverse Tunnel Reporting Service] + +// ReverseTunnelReportingService allows Envoy instances to report reverse tunnel +// connection state changes to a management server for monitoring and coordination. +service ReverseTunnelReportingService { + // Bidirectional stream for reporting reverse tunnel connection state changes. + // The management server can control reporting intervals and acknowledge received reports. + rpc StreamReverseTunnels(stream StreamReverseTunnelsRequest) + returns (stream StreamReverseTunnelsResponse) { + } +} + +// Request message sent by Envoy to report reverse tunnel state changes. +// [#next-free-field: 7] +message StreamReverseTunnelsRequest { + // Node identifier for the reporting Envoy instance. + // This identifies which Envoy instance is sending the report. + config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; + + // List of reverse tunnels that were established since the last report. + // Each tunnel represents a new connection from a downstream Envoy. + repeated ReverseTunnel added_tunnels = 2; + + // List of tunnel names that were disconnected since the last report. + // Only the tunnel name is needed for removal notifications. + repeated string removed_tunnel_names = 3 + [(validate.rules).repeated = {items {string {min_len: 1}}}]; + + // Optional metadata for additional context or debugging information. + // Can include deployment information, version details, etc. + google.protobuf.Struct metadata = 4; + + // Indicates whether this report contains all active tunnels (true) or + // only changes since the last report (false). Usually invoked only on server disconnects. + bool full_push = 5; + + // Unique nonce for this request to enable proper ACK/NACK handling. + // Must be non-negative and should increment for each request. + // This can also be modified to be used for checksum and tracking in the future.a + int64 nonce = 6 [(validate.rules).int64 = {gte: 0}]; +} + +// Response message sent by the management server to control reporting behavior. +message StreamReverseTunnelsResponse { + // Node identifier acknowledging which Envoy instance this response is for. + // Should match the node from the corresponding request. + config.core.v3.Node node = 1; + + // Interval at which Envoy should send tunnel state reports. + // This is used to change the reporting_interval -> no need to repeat the same value. + google.protobuf.Duration report_interval = 2 [(validate.rules).duration = {lte {seconds: 3600}}]; + + // Nonce from the request being acknowledged or rejected. + // Must match the nonce from the corresponding request. + int64 request_nonce = 3 [(validate.rules).int64 = {gte: 0}]; + + // Error details if the previous request failed processing. + // If populated, indicates the request was rejected (NACK). + // If empty, indicates successful processing (ACK). + // NACK will terminate the connection -> useful for logging rather than just some disconnect. + // So basically -> NACK then terminate. + google.rpc.Status error_detail = 4; +} + +// Represents a single reverse tunnel connection with its metadata. +message ReverseTunnel { + // Unique name to identify this tunnel connection. + // Typically formatted as "{node_id}|{cluster_id}" or similar. + // Must be unique within the reporting Envoy instance. + // This is also used for the reporting the disconnection with the associated tunnel initiator. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Detailed information about the tunnel connection. + ReverseTunnelInfo tunnel_info = 2 [(validate.rules).message = {required: true}]; +} + +// Detailed information about a reverse tunnel connection. +message ReverseTunnelInfo { + // Identity information of the tunnel initiator (downstream Envoy). + // Contains node_id, cluster_id, and tenant_id for proper identification. + TunnelInitiatorIdentity identity = 1 [(validate.rules).message = {required: true}]; + + // Timestamp when this tunnel connection was created. + // Used for ordering events and debugging connection timing issues. + google.protobuf.Timestamp created_at = 2 [(validate.rules).timestamp = {required: true}]; +} + +message TunnelInitiatorIdentity { + // Required: Tenant identifier of the initiating Envoy instance. + string tenant_id = 1 [(validate.rules).string = {min_len: 1 max_len: 128}]; + + // Required: Cluster identifier of the initiating Envoy instance. + string cluster_id = 2 [(validate.rules).string = {min_len: 1 max_len: 128}]; + + // Required: Node identifier of the initiating Envoy instance. + string node_id = 3 [(validate.rules).string = {min_len: 1 max_len: 128}]; +} diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD new file mode 100644 index 000000000000..5f552f08145c --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@xds//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto new file mode 100644 index 000000000000..2b1315d80154 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.reporters; + +import "google/protobuf/any.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.reporters"; +option java_outer_classname = "EventReporterProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +message ReverseConnectionReporterClient { + // Name to use to pick out the client should match the one reported by the factory. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Typed config for the client + google.protobuf.Any typed_config = 2 [(validate.rules).any = {required: true}]; +} + +// Configuration for the connection event reporter. +message EventReporterConfig { + // Stat prefix for this reporter's metrics. + // Metrics will be emitted as "{stat_prefix}.events_pushed", etc. + string stat_prefix = 1; + + // List of clients to report to. + repeated ReverseConnectionReporterClient clients = 2 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index d9358c863733..3cd46fa04a6c 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -43,6 +43,8 @@ proto_library( "//contrib/envoy/extensions/private_key_providers/kae/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", diff --git a/docs/root/api-v3/config/contrib/contrib.rst b/docs/root/api-v3/config/contrib/contrib.rst index ac17d0607627..5ce1c24cdb09 100644 --- a/docs/root/api-v3/config/contrib/contrib.rst +++ b/docs/root/api-v3/config/contrib/contrib.rst @@ -21,3 +21,4 @@ Contrib extensions tap_sinks/tap_sinks load_balancing_policies/peak_ewma/peak_ewma istio/istio + reverse_tunnel_reporter/reverse_tunnel_reporter diff --git a/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst b/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst new file mode 100644 index 000000000000..cc06a2bf352f --- /dev/null +++ b/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst @@ -0,0 +1,9 @@ +Reverse tunnel reporter +======================= + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../../extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/* + ../../../extensions/reverse_tunnel_reporters/v3alpha/reporters/*