Skip to content

Commit 185fc45

Browse files
committed
network filters: add tcp bandwidth limit
Signed-off-by: Anton Kanugalawattage <[email protected]>
1 parent a0b3df3 commit 185fc45

File tree

14 files changed

+848
-0
lines changed

14 files changed

+848
-0
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ extensions/filters/common/original_src @klarose @mattklein123
171171
/*/extensions/retry/priority/previous_priorities @ravenblackx @mattklein123
172172
/*/extensions/retry/host @ravenblackx @mattklein123
173173
/*/extensions/filters/network/http_connection_manager @yanavlasov @mattklein123
174+
/*/extensions/filters/network/tcp_bandwidth_limit @UNOWNED @UNOWNED
174175
/*/extensions/filters/network/tcp_proxy @zuercher @ggreenway
175176
/*/extensions/filters/network/echo @yanavlasov @adisuissa
176177
/*/extensions/filters/udp/dns_filter @mattklein123 @yanjunxiang-google
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 = [
9+
"//envoy/config/core/v3:pkg",
10+
"@com_github_cncf_xds//udpa/annotations:pkg",
11+
],
12+
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
syntax = "proto3";
2+
3+
package envoy.extensions.filters.network.tcp_bandwidth_limit.v3;
4+
5+
import "envoy/config/core/v3/base.proto";
6+
import "google/protobuf/duration.proto";
7+
import "google/protobuf/wrappers.proto";
8+
9+
import "udpa/annotations/status.proto";
10+
import "validate/validate.proto";
11+
12+
option java_package = "io.envoyproxy.envoy.extensions.filters.network.tcp_bandwidth_limit.v3";
13+
option java_outer_classname = "TcpBandwidthLimitProto";
14+
option java_multiple_files = true;
15+
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_bandwidth_limit/v3;tcp_bandwidth_limitv3";
16+
option (udpa.annotations.file_status).package_version_status = ACTIVE;
17+
18+
// [#protodoc-title: TCP Bandwidth Limit]
19+
// TCP Bandwidth Limit :ref:`configuration overview <config_network_filters_tcp_bandwidth_limit>`.
20+
// [#extension: envoy.filters.network.tcp_bandwidth_limit]
21+
22+
message TcpBandwidthLimit {
23+
// The human readable prefix to use when emitting stats.
24+
string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];
25+
26+
// The limit for download bandwidth in KiB/s.
27+
// If not set, no limit is applied (unlimited bandwidth).
28+
google.protobuf.UInt64Value download_limit_kbps = 2;
29+
30+
// The limit for upload bandwidth in KiB/s.
31+
// If not set, no limit is applied (unlimited bandwidth).
32+
google.protobuf.UInt64Value upload_limit_kbps = 3;
33+
34+
// Optional Fill interval in milliseconds for the token refills. Defaults to 50ms.
35+
// It must be at least 20ms to avoid too aggressive refills.
36+
google.protobuf.Duration fill_interval = 4 [(validate.rules).duration = {
37+
lte {seconds: 1}
38+
gte {nanos: 20000000}
39+
}];
40+
41+
// Runtime flag that controls whether the filter is enabled or not. If not specified, defaults
42+
// to enabled.
43+
config.core.v3.RuntimeFeatureFlag runtime_enabled = 5;
44+
}

api/versioning/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ proto_library(
211211
"//envoy/extensions/filters/network/set_filter_state/v3:pkg",
212212
"//envoy/extensions/filters/network/sni_cluster/v3:pkg",
213213
"//envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3:pkg",
214+
"//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg",
214215
"//envoy/extensions/filters/network/tcp_proxy/v3:pkg",
215216
"//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg",
216217
"//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg",

source/extensions/extensions_build_config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ EXTENSIONS = {
249249
"envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config",
250250
"envoy.filters.network.local_ratelimit": "//source/extensions/filters/network/local_ratelimit:config",
251251
"envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config",
252+
"envoy.filters.network.tcp_bandwidth_limit": "//source/extensions/filters/network/tcp_bandwidth_limit:config",
252253
"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config",
253254
"envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config",
254255
"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config",

source/extensions/extensions_metadata.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,13 @@ envoy.filters.network.sni_dynamic_forward_proxy:
896896
status: alpha
897897
type_urls:
898898
- envoy.extensions.filters.network.sni_dynamic_forward_proxy.v3.FilterConfig
899+
envoy.filters.network.tcp_bandwidth_limit:
900+
categories:
901+
- envoy.filters.network
902+
security_posture: robust_to_untrusted_downstream
903+
status: alpha
904+
type_urls:
905+
- envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit
899906
envoy.filters.network.tcp_proxy:
900907
categories:
901908
- envoy.filters.network
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 = "tcp_bandwidth_limit",
14+
srcs = ["tcp_bandwidth_limit.cc"],
15+
hdrs = ["tcp_bandwidth_limit.h"],
16+
deps = [
17+
"//envoy/buffer:buffer_interface",
18+
"//envoy/common:time_interface",
19+
"//envoy/event:dispatcher_interface",
20+
"//envoy/event:timer_interface",
21+
"//envoy/network:connection_interface",
22+
"//envoy/network:filter_interface",
23+
"//envoy/runtime:runtime_interface",
24+
"//envoy/stats:stats_interface",
25+
"//envoy/stats:stats_macros",
26+
"//source/common/buffer:buffer_lib",
27+
"//source/common/common:assert_lib",
28+
"//source/common/common:logger_lib",
29+
"//source/common/common:shared_token_bucket_impl_lib",
30+
"//source/common/runtime:runtime_lib",
31+
"@envoy_api//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg_cc_proto",
32+
],
33+
)
34+
35+
envoy_cc_extension(
36+
name = "config",
37+
srcs = ["config.cc"],
38+
hdrs = ["config.h"],
39+
deps = [
40+
":tcp_bandwidth_limit",
41+
"//envoy/registry",
42+
"//envoy/server:filter_config_interface",
43+
"//source/extensions/filters/network:well_known_names",
44+
"//source/extensions/filters/network/common:factory_base_lib",
45+
"@envoy_api//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg_cc_proto",
46+
],
47+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include "source/extensions/filters/network/tcp_bandwidth_limit/config.h"
2+
3+
#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.h"
4+
#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.validate.h"
5+
6+
#include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace NetworkFilters {
11+
namespace TcpBandwidthLimit {
12+
13+
Network::FilterFactoryCb TcpBandwidthLimitConfigFactory::createFilterFactoryFromProtoTyped(
14+
const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit&
15+
proto_config,
16+
Server::Configuration::FactoryContext& context) {
17+
18+
auto config = std::make_shared<FilterConfig>(proto_config, context.scope(),
19+
context.serverFactoryContext().runtime(),
20+
context.serverFactoryContext().timeSource());
21+
22+
return [config, &context](Network::FilterManager& filter_manager) -> void {
23+
filter_manager.addFilter(std::make_shared<TcpBandwidthLimitFilter>(
24+
config, context.serverFactoryContext().mainThreadDispatcher()));
25+
};
26+
}
27+
28+
REGISTER_FACTORY(TcpBandwidthLimitConfigFactory,
29+
Server::Configuration::NamedNetworkFilterConfigFactory);
30+
31+
} // namespace TcpBandwidthLimit
32+
} // namespace NetworkFilters
33+
} // namespace Extensions
34+
} // namespace Envoy
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.h"
4+
#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.validate.h"
5+
6+
#include "source/extensions/filters/network/common/factory_base.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace NetworkFilters {
11+
namespace TcpBandwidthLimit {
12+
13+
class TcpBandwidthLimitConfigFactory
14+
: public Common::FactoryBase<
15+
envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit> {
16+
public:
17+
TcpBandwidthLimitConfigFactory() : FactoryBase("envoy.filters.network.tcp_bandwidth_limit") {}
18+
19+
private:
20+
Network::FilterFactoryCb createFilterFactoryFromProtoTyped(
21+
const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit&
22+
proto_config,
23+
Server::Configuration::FactoryContext& context) override;
24+
};
25+
26+
} // namespace TcpBandwidthLimit
27+
} // namespace NetworkFilters
28+
} // namespace Extensions
29+
} // namespace Envoy
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h"
2+
3+
#include "envoy/buffer/buffer.h"
4+
#include "envoy/event/dispatcher.h"
5+
#include "envoy/network/connection.h"
6+
7+
#include "source/common/common/assert.h"
8+
9+
namespace Envoy {
10+
namespace Extensions {
11+
namespace NetworkFilters {
12+
namespace TcpBandwidthLimit {
13+
14+
FilterConfig::FilterConfig(
15+
const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit& config,
16+
Stats::Scope& scope, Runtime::Loader& runtime, TimeSource& time_source)
17+
: runtime_(runtime), time_source_(time_source),
18+
has_download_limit_(config.has_download_limit_kbps()),
19+
has_upload_limit_(config.has_upload_limit_kbps()),
20+
download_limit_kbps_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, download_limit_kbps, 0)),
21+
upload_limit_kbps_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, upload_limit_kbps, 0)),
22+
fill_interval_(std::chrono::milliseconds(
23+
PROTOBUF_GET_MS_OR_DEFAULT(config, fill_interval, 50))), // Default 50ms
24+
enabled_(config.runtime_enabled(), runtime),
25+
stats_(generateStats(config.stat_prefix(), scope)) {
26+
27+
if (has_download_limit_) {
28+
// The token bucket is configured with a max token count of the number of
29+
// bytes per second, and refills at the same rate, so that we have a per
30+
// second limit which refills gradually over the fill interval.
31+
uint64_t limit_bytes_per_sec = kiloBytesToBytes(download_limit_kbps_);
32+
download_token_bucket_ = std::make_shared<SharedTokenBucketImpl>(
33+
limit_bytes_per_sec, time_source_, static_cast<double>(limit_bytes_per_sec));
34+
}
35+
36+
if (has_upload_limit_) {
37+
uint64_t limit_bytes_per_sec = kiloBytesToBytes(upload_limit_kbps_);
38+
upload_token_bucket_ = std::make_shared<SharedTokenBucketImpl>(
39+
limit_bytes_per_sec, time_source_, static_cast<double>(limit_bytes_per_sec));
40+
}
41+
}
42+
43+
TcpBandwidthLimitStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope& scope) {
44+
const std::string final_prefix = prefix + ".tcp_bandwidth_limit";
45+
return {ALL_TCP_BANDWIDTH_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
46+
}
47+
48+
TcpBandwidthLimitFilter::TcpBandwidthLimitFilter(FilterConfigSharedPtr config,
49+
Event::Dispatcher& dispatcher)
50+
: config_(config), dispatcher_(dispatcher) {}
51+
52+
Network::FilterStatus TcpBandwidthLimitFilter::onData(Buffer::Instance& data, bool) {
53+
if (!config_->enabled() || !config_->hasDownloadLimit()) {
54+
return Network::FilterStatus::Continue;
55+
}
56+
57+
config_->stats().download_enabled_.inc();
58+
59+
uint64_t data_size = data.length();
60+
uint64_t consumed = config_->downloadTokenBucket()->consume(data_size, true);
61+
62+
if (consumed < data_size) {
63+
config_->stats().download_throttled_.inc();
64+
65+
if (consumed > 0) {
66+
Buffer::OwnedImpl passthrough;
67+
passthrough.move(data, consumed);
68+
read_callbacks_->injectReadDataToFilterChain(passthrough, false);
69+
}
70+
71+
download_buffer_.move(data);
72+
73+
if (!read_disabled_) {
74+
read_callbacks_->connection().readDisable(true);
75+
read_disabled_ = true;
76+
}
77+
78+
if (!download_timer_) {
79+
download_timer_ = dispatcher_.createTimer([this]() { onDownloadTokenTimer(); });
80+
download_timer_->enableTimer(config_->fillInterval());
81+
}
82+
83+
return Network::FilterStatus::StopIteration;
84+
}
85+
86+
return Network::FilterStatus::Continue;
87+
}
88+
89+
Network::FilterStatus TcpBandwidthLimitFilter::onWrite(Buffer::Instance& data, bool) {
90+
if (!config_->enabled() || !config_->hasUploadLimit()) {
91+
return Network::FilterStatus::Continue;
92+
}
93+
94+
config_->stats().upload_enabled_.inc();
95+
96+
uint64_t data_size = data.length();
97+
uint64_t consumed = config_->uploadTokenBucket()->consume(data_size, true);
98+
99+
if (consumed < data_size) {
100+
config_->stats().upload_throttled_.inc();
101+
102+
if (consumed > 0) {
103+
Buffer::OwnedImpl to_send;
104+
to_send.move(data, consumed);
105+
write_callbacks_->connection().write(to_send, false);
106+
}
107+
108+
upload_buffer_.move(data);
109+
110+
if (!upload_timer_) {
111+
upload_timer_ = dispatcher_.createTimer([this]() { onUploadTokenTimer(); });
112+
upload_timer_->enableTimer(config_->fillInterval());
113+
}
114+
115+
return Network::FilterStatus::StopIteration;
116+
}
117+
118+
return Network::FilterStatus::Continue;
119+
}
120+
121+
void TcpBandwidthLimitFilter::onDownloadTokenTimer() {
122+
processBufferedDownloadData();
123+
124+
if (download_buffer_.length() > 0) {
125+
download_timer_->enableTimer(config_->fillInterval());
126+
} else {
127+
download_timer_.reset();
128+
129+
if (read_disabled_) {
130+
read_callbacks_->connection().readDisable(false);
131+
read_disabled_ = false;
132+
}
133+
}
134+
}
135+
136+
void TcpBandwidthLimitFilter::onUploadTokenTimer() {
137+
processBufferedUploadData();
138+
139+
if (upload_buffer_.length() > 0) {
140+
upload_timer_->enableTimer(config_->fillInterval());
141+
} else {
142+
upload_timer_.reset();
143+
}
144+
}
145+
146+
void TcpBandwidthLimitFilter::processBufferedDownloadData() {
147+
if (download_buffer_.length() == 0 || !config_->downloadTokenBucket()) {
148+
return;
149+
}
150+
151+
uint64_t buffer_size = download_buffer_.length();
152+
uint64_t consumed = config_->downloadTokenBucket()->consume(buffer_size, true);
153+
154+
if (consumed > 0) {
155+
Buffer::OwnedImpl data_to_send;
156+
data_to_send.move(download_buffer_, consumed);
157+
read_callbacks_->injectReadDataToFilterChain(data_to_send, false);
158+
}
159+
}
160+
161+
void TcpBandwidthLimitFilter::processBufferedUploadData() {
162+
if (upload_buffer_.length() == 0 || !config_->uploadTokenBucket()) {
163+
return;
164+
}
165+
166+
uint64_t buffer_size = upload_buffer_.length();
167+
uint64_t consumed = config_->uploadTokenBucket()->consume(buffer_size, true);
168+
169+
if (consumed > 0) {
170+
Buffer::OwnedImpl data_to_send;
171+
data_to_send.move(upload_buffer_, consumed);
172+
write_callbacks_->connection().write(data_to_send, false);
173+
}
174+
}
175+
176+
} // namespace TcpBandwidthLimit
177+
} // namespace NetworkFilters
178+
} // namespace Extensions
179+
} // namespace Envoy

0 commit comments

Comments
 (0)