Skip to content

Commit 5cf7598

Browse files
authored
udp_session_filters: add HTTP capsule filter (resubmit) (envoyproxy#29716)
Signed-off-by: ohadvano <ohadvano@gmail.com>
1 parent 2c29797 commit 5cf7598

File tree

22 files changed

+778
-1
lines changed

22 files changed

+778
-1
lines changed

api/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ proto_library(
236236
"//envoy/extensions/filters/network/wasm/v3:pkg",
237237
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
238238
"//envoy/extensions/filters/udp/dns_filter/v3:pkg",
239+
"//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg",
239240
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
240241
"//envoy/extensions/formatter/cel/v3:pkg",
241242
"//envoy/extensions/formatter/metadata/v3:pkg",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.
2+
3+
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
4+
5+
licenses(["notice"]) # Apache 2
6+
7+
api_proto_package(
8+
deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
9+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
syntax = "proto3";
2+
3+
package envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3;
4+
5+
import "udpa/annotations/status.proto";
6+
7+
option java_package = "io.envoyproxy.envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3";
8+
option java_outer_classname = "HttpCapsuleProto";
9+
option java_multiple_files = true;
10+
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3;http_capsulev3";
11+
option (udpa.annotations.file_status).package_version_status = ACTIVE;
12+
13+
// [#protodoc-title: UDP HTTP Capsule filter]
14+
// UDP to HTTP capsules :ref:`overview <config_udp_session_filters_http_capsule>`.
15+
// [#extension: envoy.filters.udp.session.http_capsule]
16+
17+
message FilterConfig {
18+
}

api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,6 @@ message UdpProxyConfig {
127127

128128
// Optional session filters that will run for each UDP session.
129129
// Only one of use_per_packet_load_balancing or session_filters can be used.
130+
// [#extension-category: envoy.filters.udp.session]
130131
repeated SessionFilter session_filters = 11;
131132
}

api/versioning/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ proto_library(
174174
"//envoy/extensions/filters/network/wasm/v3:pkg",
175175
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
176176
"//envoy/extensions/filters/udp/dns_filter/v3:pkg",
177+
"//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg",
177178
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
178179
"//envoy/extensions/formatter/cel/v3:pkg",
179180
"//envoy/extensions/formatter/metadata/v3:pkg",

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ new_features:
256256
change: |
257257
added :ref:` stats prefix option<envoy_v3_api_field_extensions.stat_sinks.open_telemetry.v3.SinkConfig.stats_prefix>`
258258
to OTLP stats sink that enables adding a static prefix to all stats flushed by this sink.
259+
- area: udp_proxy
260+
change: |
261+
added :ref:`http_capsule <envoy_v3_api_msg_extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig>` UDP session filter
262+
that can be used to encapsule or decapsulate UDP datagrams in HTTP, when used for UDP tunneling.
259263
- area: tap
260264
change: |
261265
added :ref:`record_headers_received_time <envoy_v3_api_field_extensions.filters.http.tap.v3.Tap.record_headers_received_time>`

docs/root/api-v3/config/filter/filter.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Filters
88
listener/listener
99
network/network
1010
udp/udp
11+
udp/session
1112
http/http
1213
dubbo/dubbo
1314
thrift/thrift
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
UDP session filters
2+
====================
3+
4+
.. toctree::
5+
:glob:
6+
:maxdepth: 2
7+
8+
../../../extensions/filters/udp/udp_proxy/session/*/v3/*
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.. _config_udp_session_filters_http_capsule:
2+
3+
HTTP Capsule filter
4+
==================================
5+
6+
Tunneling UDP datagrams over HTTP (see `Proxying UDP in HTTP <https://www.rfc-editor.org/rfc/rfc9298.html>`_) requires an encapsulation mechanism to preserve the boundaries of the original datagram.
7+
This filter applies the `HTTP Datagrams and the Capsule Protocol <https://www.rfc-editor.org/rfc/rfc9297.html>`_ to downstream and upstream datagrams, so they are compliant with the Capsule Protocol.
8+
9+
.. note::
10+
This filter must be used last in the UDP session filter chain, and should only be used when tunneling UDP over HTTP streams.
11+
12+
* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig``.
13+
* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig>`

docs/root/configuration/listeners/udp_filters/udp_proxy.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ upstream, similar to network filters.
8989
Additionally, since :ref:`per packet load balancing <envoy_v3_api_field_extensions.filters.udp.udp_proxy.v3.UdpProxyConfig.use_per_packet_load_balancing>` require
9090
choosing the upstream host for each received datagram, session filters can't be used when this option is enabled.
9191

92+
Envoy has the following builtin UDP session filters.
93+
94+
.. toctree::
95+
:maxdepth: 2
96+
97+
session_filters/http_capsule
98+
9299
Example configuration
93100
---------------------
94101

source/extensions/extensions_build_config.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ EXTENSIONS = {
213213
"envoy.filters.udp.dns_filter": "//source/extensions/filters/udp/dns_filter:config",
214214
"envoy.filters.udp_listener.udp_proxy": "//source/extensions/filters/udp/udp_proxy:config",
215215

216+
#
217+
# UDP Session filters
218+
#
219+
220+
"envoy.filters.udp.session.http_capsule": "//source/extensions/filters/udp/udp_proxy/session_filters/http_capsule:config",
221+
216222
#
217223
# Resource monitors
218224
#

source/extensions/extensions_metadata.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,13 @@ envoy.filters.udp_listener.udp_proxy:
749749
status: stable
750750
type_urls:
751751
- envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig
752+
envoy.filters.udp.session.http_capsule:
753+
categories:
754+
- envoy.filters.udp.session
755+
security_posture: robust_to_untrusted_downstream
756+
status: alpha
757+
type_urls:
758+
- envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig
752759
envoy.formatter.cel:
753760
categories:
754761
- envoy.formatter

source/extensions/filters/udp/udp_proxy/session_filters/filter_config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class NamedUdpSessionFilterConfigFactory : public Envoy::Config::TypedFactory {
3333
createFilterFactoryFromProto(const Protobuf::Message& config,
3434
Server::Configuration::FactoryContext& context) PURE;
3535

36-
std::string category() const override { return "envoy.udp.session_filters"; }
36+
std::string category() const override { return "envoy.filters.udp.session"; }
3737
};
3838

3939
} // namespace SessionFilters
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
load(
2+
"//bazel:envoy_build_system.bzl",
3+
"envoy_cc_extension",
4+
"envoy_cc_library",
5+
"envoy_extension_package",
6+
)
7+
8+
licenses(["notice"]) # Apache 2
9+
10+
envoy_extension_package()
11+
12+
envoy_cc_library(
13+
name = "http_capsule_filter_lib",
14+
srcs = ["http_capsule.cc"],
15+
hdrs = ["http_capsule.h"],
16+
deps = [
17+
"//source/common/buffer:buffer_lib",
18+
"//source/common/common:assert_lib",
19+
"//source/common/common:hex_lib",
20+
"//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface",
21+
"@com_github_google_quiche//:quiche_common_capsule_lib",
22+
"@com_github_google_quiche//:quiche_common_connect_udp_datagram_payload_lib",
23+
"@envoy_api//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg_cc_proto",
24+
],
25+
)
26+
27+
envoy_cc_extension(
28+
name = "config",
29+
srcs = ["config.cc"],
30+
hdrs = ["config.h"],
31+
deps = [
32+
":http_capsule_filter_lib",
33+
"//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib",
34+
"@envoy_api//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg_cc_proto",
35+
],
36+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/config.h"
2+
3+
#include "envoy/registry/registry.h"
4+
#include "envoy/server/filter_config.h"
5+
6+
#include "source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace UdpFilters {
11+
namespace UdpProxy {
12+
namespace SessionFilters {
13+
namespace HttpCapsule {
14+
15+
FilterFactoryCb HttpCapsuleFilterConfigFactory::createFilterFactoryFromProtoTyped(
16+
const FilterConfig&, Server::Configuration::FactoryContext& context) {
17+
return [&context](FilterChainFactoryCallbacks& callbacks) -> void {
18+
callbacks.addFilter(std::make_shared<HttpCapsuleFilter>(context.timeSource()));
19+
};
20+
}
21+
22+
/**
23+
* Static registration for the http_capsule filter. @see RegisterFactory.
24+
*/
25+
REGISTER_FACTORY(HttpCapsuleFilterConfigFactory, NamedUdpSessionFilterConfigFactory);
26+
27+
} // namespace HttpCapsule
28+
} // namespace SessionFilters
29+
} // namespace UdpProxy
30+
} // namespace UdpFilters
31+
} // namespace Extensions
32+
} // namespace Envoy
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include "envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3/http_capsule.pb.h"
4+
#include "envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3/http_capsule.pb.validate.h"
5+
6+
#include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace UdpFilters {
11+
namespace UdpProxy {
12+
namespace SessionFilters {
13+
namespace HttpCapsule {
14+
15+
using FilterConfig =
16+
envoy::extensions::filters::udp::udp_proxy::session::http_capsule::v3::FilterConfig;
17+
18+
/**
19+
* Config registration for the http_capsule filter. @see
20+
* NamedNetworkFilterConfigFactory.
21+
*/
22+
class HttpCapsuleFilterConfigFactory : public FactoryBase<FilterConfig> {
23+
public:
24+
HttpCapsuleFilterConfigFactory() : FactoryBase("envoy.filters.udp.session.http_capsule"){};
25+
26+
private:
27+
FilterFactoryCb
28+
createFilterFactoryFromProtoTyped(const FilterConfig& proto_config,
29+
Server::Configuration::FactoryContext& context) override;
30+
};
31+
32+
} // namespace HttpCapsule
33+
} // namespace SessionFilters
34+
} // namespace UdpProxy
35+
} // namespace UdpFilters
36+
} // namespace Extensions
37+
} // namespace Envoy
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include "source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.h"
2+
3+
#include "source/common/buffer/buffer_impl.h"
4+
#include "source/common/common/hex.h"
5+
6+
#include "absl/strings/escaping.h"
7+
#include "quiche/common/masque/connect_udp_datagram_payload.h"
8+
#include "quiche/common/simple_buffer_allocator.h"
9+
10+
namespace Envoy {
11+
namespace Extensions {
12+
namespace UdpFilters {
13+
namespace UdpProxy {
14+
namespace SessionFilters {
15+
namespace HttpCapsule {
16+
17+
ReadFilterStatus HttpCapsuleFilter::onData(Network::UdpRecvData& data) {
18+
std::string buffer = data.buffer_->toString();
19+
quiche::ConnectUdpDatagramUdpPacketPayload payload(buffer);
20+
quiche::QuicheBuffer serialized_capsule =
21+
SerializeCapsule(quiche::Capsule::Datagram(payload.Serialize()), &capsule_buffer_allocator_);
22+
23+
data.buffer_->drain(data.buffer_->length());
24+
data.buffer_->add(serialized_capsule.AsStringView());
25+
return ReadFilterStatus::Continue;
26+
}
27+
28+
WriteFilterStatus HttpCapsuleFilter::onWrite(Network::UdpRecvData& data) {
29+
// TODO(ohadvano): add filter callbacks to get addresses instead of saving them.
30+
local_address_ = data.addresses_.local_;
31+
peer_address_ = data.addresses_.peer_;
32+
33+
for (const Buffer::RawSlice& slice : data.buffer_->getRawSlices()) {
34+
absl::string_view mem_slice(reinterpret_cast<const char*>(slice.mem_), slice.len_);
35+
if (!capsule_parser_.IngestCapsuleFragment(mem_slice)) {
36+
ENVOY_LOG(error, "Capsule ingestion error occured: slice length = {}", slice.len_);
37+
break;
38+
}
39+
}
40+
41+
// We always stop here as OnCapsule() callback will be responsible to inject
42+
// datagrams to the filter chain once they are ready.
43+
data.buffer_->drain(data.buffer_->length());
44+
return WriteFilterStatus::StopIteration;
45+
}
46+
47+
bool HttpCapsuleFilter::OnCapsule(const quiche::Capsule& capsule) {
48+
quiche::CapsuleType capsule_type = capsule.capsule_type();
49+
if (capsule_type != quiche::CapsuleType::DATAGRAM) {
50+
// Silently drops capsules with an unknown type.
51+
return true;
52+
}
53+
54+
std::unique_ptr<quiche::ConnectUdpDatagramPayload> connect_udp_datagram_payload =
55+
quiche::ConnectUdpDatagramPayload::Parse(capsule.datagram_capsule().http_datagram_payload);
56+
if (!connect_udp_datagram_payload) {
57+
// Indicates parsing failure to reset the data stream.
58+
ENVOY_LOG(debug, "capsule parsing error");
59+
return false;
60+
}
61+
62+
if (connect_udp_datagram_payload->GetType() !=
63+
quiche::ConnectUdpDatagramPayload::Type::kUdpPacket) {
64+
// Silently drops Datagrams with an unknown Context ID.
65+
return true;
66+
}
67+
68+
Network::UdpRecvData datagram;
69+
datagram.buffer_ = std::make_unique<Buffer::OwnedImpl>();
70+
datagram.buffer_->add(connect_udp_datagram_payload->GetUdpProxyingPayload());
71+
datagram.receive_time_ = time_source_.monotonicTime();
72+
datagram.addresses_ = {local_address_, peer_address_};
73+
74+
write_callbacks_->injectDatagramToFilterChain(datagram);
75+
return true;
76+
}
77+
78+
void HttpCapsuleFilter::OnCapsuleParseFailure(absl::string_view reason) {
79+
ENVOY_LOG(debug, "capsule parse failure: {}", reason);
80+
}
81+
82+
} // namespace HttpCapsule
83+
} // namespace SessionFilters
84+
} // namespace UdpProxy
85+
} // namespace UdpFilters
86+
} // namespace Extensions
87+
} // namespace Envoy
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include "envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3/http_capsule.pb.h"
4+
5+
#include "source/common/common/logger.h"
6+
#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h"
7+
8+
#include "quiche/common/capsule.h"
9+
#include "quiche/common/simple_buffer_allocator.h"
10+
11+
namespace Envoy {
12+
namespace Extensions {
13+
namespace UdpFilters {
14+
namespace UdpProxy {
15+
namespace SessionFilters {
16+
namespace HttpCapsule {
17+
18+
class HttpCapsuleFilter : public Filter,
19+
public quiche::CapsuleParser::Visitor,
20+
Logger::Loggable<Logger::Id::http> {
21+
public:
22+
HttpCapsuleFilter(TimeSource& time_source) : time_source_(time_source) {}
23+
24+
// ReadFilter
25+
ReadFilterStatus onNewSession() override { return ReadFilterStatus::Continue; }
26+
ReadFilterStatus onData(Network::UdpRecvData& data) override;
27+
void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override {
28+
read_callbacks_ = &callbacks;
29+
}
30+
31+
// WriteFilter
32+
WriteFilterStatus onWrite(Network::UdpRecvData& data) override;
33+
void initializeWriteFilterCallbacks(WriteFilterCallbacks& callbacks) override {
34+
write_callbacks_ = &callbacks;
35+
}
36+
37+
// quiche::CapsuleParser::Visitor
38+
bool OnCapsule(const quiche::Capsule& capsule) override;
39+
void OnCapsuleParseFailure(absl::string_view error_message) override;
40+
41+
private:
42+
ReadFilterCallbacks* read_callbacks_{};
43+
WriteFilterCallbacks* write_callbacks_{};
44+
quiche::CapsuleParser capsule_parser_{this};
45+
quiche::SimpleBufferAllocator capsule_buffer_allocator_;
46+
Network::Address::InstanceConstSharedPtr local_address_;
47+
Network::Address::InstanceConstSharedPtr peer_address_;
48+
TimeSource& time_source_;
49+
};
50+
51+
} // namespace HttpCapsule
52+
} // namespace SessionFilters
53+
} // namespace UdpProxy
54+
} // namespace UdpFilters
55+
} // namespace Extensions
56+
} // namespace Envoy

0 commit comments

Comments
 (0)