diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index a26e08c99c..20723bfbfa 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2368,6 +2368,33 @@
+
+ TokenBufferFilterLatencySec=
+
+ Specifies the latency parameter, which specifies the maximum amount of time a
+ packet can sit in the Token Buffer Filter (TBF). Defaults to unset.
+
+
+
+
+ TokenBufferFilterBurst=
+
+ Specifies the size of the bucket. This is the maximum amount of bytes that tokens
+ can be available for instantaneous transfer. When the size is suffixed with K, M, or G, it is
+ parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1000. Defaults to
+ unset.
+
+
+
+
+ TokenBufferFilterRate=
+
+ Specifies the device specific bandwidth. When suffixed with K, M, or G, the specified
+ bandwidth is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1000.
+ Defaults to unset.
+
+
+
diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c
index 34b66e6fa6..1569f34cc4 100644
--- a/src/libsystemd/sd-netlink/netlink-message.c
+++ b/src/libsystemd/sd-netlink/netlink-message.c
@@ -532,7 +532,6 @@ int sd_netlink_message_open_array(sd_netlink_message *m, uint16_t type) {
assert_return(m, -EINVAL);
assert_return(!m->sealed, -EPERM);
- assert_return(m->n_containers > 0, -EINVAL);
r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0);
if (r < 0)
diff --git a/src/network/meson.build b/src/network/meson.build
index d502279151..3633694577 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -109,6 +109,8 @@ sources = files('''
tc/netem.h
tc/qdisc.c
tc/qdisc.h
+ tc/tbf.c
+ tc/tbf.h
tc/tc-util.c
tc/tc-util.h
'''.split())
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index f314b1ec16..32c6afc49e 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -250,6 +250,9 @@ TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_pars
TrafficControlQueueingDiscipline.NetworkEmulatorLossRate, config_parse_tc_network_emulator_rate, 0, 0
TrafficControlQueueingDiscipline.NetworkEmulatorDuplicateRate, config_parse_tc_network_emulator_rate, 0, 0
TrafficControlQueueingDiscipline.NetworkEmulatorPacketLimit, config_parse_tc_network_emulator_packet_limit, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterRate, config_parse_tc_token_buffer_filter_size, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterBurst, config_parse_tc_token_buffer_filter_size, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterLatencySec, config_parse_tc_token_buffer_filter_latency, 0, 0
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c
index ed9bd9167a..9baf723461 100644
--- a/src/network/tc/qdisc.c
+++ b/src/network/tc/qdisc.c
@@ -152,6 +152,16 @@ int qdisc_configure(Link *link, QDiscs *qdisc) {
return r;
}
+ if (qdisc->has_token_buffer_filter) {
+ r = free_and_strdup(&tca_kind, "tbf");
+ if (r < 0)
+ return log_oom();
+
+ r = token_buffer_filter_fill_message(link, &qdisc->tbf, req);
+ if (r < 0)
+ return r;
+ }
+
if (tca_kind) {
r = sd_netlink_message_append_string(req, TCA_KIND, tca_kind);
if (r < 0)
diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h
index 95ff829b9e..0453c85c9d 100644
--- a/src/network/tc/qdisc.h
+++ b/src/network/tc/qdisc.h
@@ -7,6 +7,7 @@
#include "networkd-link.h"
#include "networkd-network.h"
#include "networkd-util.h"
+#include "tbf.h"
typedef struct QDiscs {
NetworkConfigSection *section;
@@ -20,8 +21,10 @@ typedef struct QDiscs {
uint32_t parent;
bool has_network_emulator:1;
+ bool has_token_buffer_filter:1;
NetworkEmulator ne;
+ TokenBufferFilter tbf;
} QDiscs;
void qdisc_free(QDiscs *qdisc);
diff --git a/src/network/tc/tbf.c b/src/network/tc/tbf.c
new file mode 100644
index 0000000000..47c999e7c7
--- /dev/null
+++ b/src/network/tc/tbf.c
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+
+#include
+#include
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "netem.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "qdisc.h"
+#include "string-util.h"
+#include "util.h"
+
+int token_buffer_filter_new(TokenBufferFilter **ret) {
+ TokenBufferFilter *ne = NULL;
+
+ ne = new0(TokenBufferFilter, 1);
+ if (!ne)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(ne);
+
+ return 0;
+}
+
+int token_buffer_filter_fill_message(Link *link, const TokenBufferFilter *tbf, sd_netlink_message *req) {
+ struct tc_tbf_qopt opt = {};
+ int r;
+
+ assert(link);
+ assert(tbf);
+ assert(req);
+
+ opt.rate.rate = tbf->rate >= (1ULL << 32) ? ~0U : tbf->rate;
+ opt.limit = tbf->rate * (double) tbf->latency / USEC_PER_SEC + tbf->burst;
+
+ r = sd_netlink_message_open_array(req, TCA_OPTIONS);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m");
+
+ r = sd_netlink_message_append_data(req, TCA_TBF_PARMS, &opt, sizeof(struct tc_tbf_qopt));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_PARMS attribute: %m");
+
+ r = sd_netlink_message_append_data(req, TCA_TBF_BURST, &tbf->burst, sizeof(tbf->burst));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_BURST attribute: %m");
+
+ if (tbf->rate >= (1ULL << 32)) {
+ r = sd_netlink_message_append_data(req, TCA_TBF_RATE64, &tbf->rate, sizeof(tbf->rate));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_RATE64 attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m");
+
+ return 0;
+}
+
+int config_parse_tc_token_buffer_filter_size(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ Network *network = data;
+ uint64_t k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = qdisc_new_static(network, filename, section_line, &qdisc);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue)) {
+ if (streq(lvalue, "TokenBufferFilterRate"))
+ qdisc->tbf.rate = 0;
+ else if (streq(lvalue, "TokenBufferFilterBurst"))
+ qdisc->tbf.burst = 0;
+
+ qdisc = NULL;
+ return 0;
+ }
+
+ r = parse_size(rvalue, 1000, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "TokenBufferFilterRate"))
+ qdisc->tbf.rate = k / 8;
+ else if (streq(lvalue, "TokenBufferFilterBurst"))
+ qdisc->tbf.burst = k;
+
+ qdisc->has_token_buffer_filter = true;
+ qdisc = NULL;
+
+ return 0;
+}
+
+int config_parse_tc_token_buffer_filter_latency(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ Network *network = data;
+ usec_t u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = qdisc_new_static(network, filename, section_line, &qdisc);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue)) {
+ qdisc->tbf.latency = 0;
+
+ qdisc = NULL;
+ return 0;
+ }
+
+ r = parse_sec(rvalue, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ qdisc->tbf.latency = u;
+
+ qdisc->has_token_buffer_filter = true;
+ qdisc = NULL;
+
+ return 0;
+}
diff --git a/src/network/tc/tbf.h b/src/network/tc/tbf.h
new file mode 100644
index 0000000000..c8ae6d057d
--- /dev/null
+++ b/src/network/tc/tbf.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "networkd-link.h"
+
+typedef struct TokenBufferFilter {
+ uint64_t rate;
+
+ uint32_t burst;
+ uint32_t latency;
+} TokenBufferFilter;
+
+int token_buffer_filter_new(TokenBufferFilter **ret);
+int token_buffer_filter_fill_message(Link *link, const TokenBufferFilter *tbf, sd_netlink_message *req);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_tc_token_buffer_filter_latency);
+CONFIG_PARSER_PROTOTYPE(config_parse_tc_token_buffer_filter_size);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index cb10ca306a..1cdbc07a24 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -270,3 +270,6 @@ NetworkEmulatorDelayJitterSec=
NetworkEmulatorLossRate=
NetworkEmulatorDuplicateRate=
NetworkEmulatorPacketLimit=
+TokenBufferFilterRate=
+TokenBufferFilterBurst=
+TokenBufferFilterLatencySec=