Merge pull request #14173 from ssahani/tc-sfq
network: tc: introduce sfq and tbf
This commit is contained in:
commit
da4dd97405
|
@ -2368,6 +2368,40 @@
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TokenBufferFilterLatencySec=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the latency parameter, which specifies the maximum amount of time a
|
||||
packet can sit in the Token Buffer Filter (TBF). Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TokenBufferFilterBurst=</varname></term>
|
||||
<listitem>
|
||||
<para>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.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TokenBufferFilterRate=</varname></term>
|
||||
<listitem>
|
||||
<para>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.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>StochasticFairnessQueueingPerturbPeriodSec=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the interval in seconds for queue algorithm perturbation. Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -109,6 +109,10 @@ sources = files('''
|
|||
tc/netem.h
|
||||
tc/qdisc.c
|
||||
tc/qdisc.h
|
||||
tc/sfq.c
|
||||
tc/sfq.h
|
||||
tc/tbf.c
|
||||
tc/tbf.h
|
||||
tc/tc-util.c
|
||||
tc/tc-util.h
|
||||
'''.split())
|
||||
|
|
|
@ -2585,7 +2585,7 @@ static int link_drop_config(Link *link) {
|
|||
}
|
||||
|
||||
static int link_configure_qdiscs(Link *link) {
|
||||
QDiscs *qdisc;
|
||||
QDisc *qdisc;
|
||||
Iterator i;
|
||||
int r;
|
||||
|
||||
|
@ -2601,7 +2601,7 @@ static int link_configure_qdiscs(Link *link) {
|
|||
if (link->qdisc_messages == 0)
|
||||
link->qdiscs_configured = true;
|
||||
else
|
||||
log_link_debug(link, "Configuring QDiscs");
|
||||
log_link_debug(link, "Configuring queuing discipline (qdisc)");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -250,6 +250,10 @@ 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
|
||||
TrafficControlQueueingDiscipline.StochasticFairnessQueueingPerturbPeriodSec, config_parse_tc_stochastic_fairness_queueing_perturb_period, 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)
|
||||
|
|
|
@ -154,6 +154,8 @@ int network_verify(Network *network) {
|
|||
Prefix *prefix, *prefix_next;
|
||||
Route *route, *route_next;
|
||||
FdbEntry *fdb, *fdb_next;
|
||||
QDisc *qdisc;
|
||||
Iterator i;
|
||||
|
||||
assert(network);
|
||||
assert(network->filename);
|
||||
|
@ -313,6 +315,11 @@ int network_verify(Network *network) {
|
|||
if (routing_policy_rule_section_verify(rule) < 0)
|
||||
routing_policy_rule_free(rule);
|
||||
|
||||
bool has_root = false, has_clsact = false;
|
||||
ORDERED_HASHMAP_FOREACH(qdisc, network->qdiscs_by_section, i)
|
||||
if (qdisc_section_verify(qdisc, &has_root, &has_clsact) < 0)
|
||||
qdisc_free(qdisc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,9 @@
|
|||
* Copyright © 2019 VMware, Inc. */
|
||||
|
||||
#include <linux/pkt_sched.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-parser.h"
|
||||
#include "hashmap.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "netem.h"
|
||||
#include "netlink-util.h"
|
||||
#include "networkd-manager.h"
|
||||
|
@ -15,7 +12,6 @@
|
|||
#include "qdisc.h"
|
||||
#include "string-util.h"
|
||||
#include "tc-util.h"
|
||||
#include "util.h"
|
||||
|
||||
int network_emulator_new(NetworkEmulator **ret) {
|
||||
NetworkEmulator *ne = NULL;
|
||||
|
@ -34,33 +30,33 @@ int network_emulator_new(NetworkEmulator **ret) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req) {
|
||||
int network_emulator_fill_message(Link *link, const NetworkEmulator *ne, sd_netlink_message *req) {
|
||||
struct tc_netem_qopt opt = {
|
||||
.limit = 1000,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(qdisc);
|
||||
assert(ne);
|
||||
assert(req);
|
||||
|
||||
if (qdisc->ne.limit > 0)
|
||||
opt.limit = qdisc->ne.limit;
|
||||
if (ne->limit > 0)
|
||||
opt.limit = ne->limit;
|
||||
|
||||
if (qdisc->ne.loss > 0)
|
||||
opt.loss = qdisc->ne.loss;
|
||||
if (ne->loss > 0)
|
||||
opt.loss = ne->loss;
|
||||
|
||||
if (qdisc->ne.duplicate > 0)
|
||||
opt.duplicate = qdisc->ne.duplicate;
|
||||
if (ne->duplicate > 0)
|
||||
opt.duplicate = ne->duplicate;
|
||||
|
||||
if (qdisc->ne.delay != USEC_INFINITY) {
|
||||
r = tc_time_to_tick(qdisc->ne.delay, &opt.latency);
|
||||
if (ne->delay != USEC_INFINITY) {
|
||||
r = tc_time_to_tick(ne->delay, &opt.latency);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Failed to calculate latency in TCA_OPTION: %m");
|
||||
}
|
||||
|
||||
if (qdisc->ne.jitter != USEC_INFINITY) {
|
||||
r = tc_time_to_tick(qdisc->ne.jitter, &opt.jitter);
|
||||
if (ne->jitter != USEC_INFINITY) {
|
||||
r = tc_time_to_tick(ne->jitter, &opt.jitter);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Failed to calculate jitter in TCA_OPTION: %m");
|
||||
}
|
||||
|
@ -84,7 +80,7 @@ int config_parse_tc_network_emulator_delay(
|
|||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
|
||||
Network *network = data;
|
||||
usec_t u;
|
||||
int r;
|
||||
|
@ -139,7 +135,7 @@ int config_parse_tc_network_emulator_rate(
|
|||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
|
||||
Network *network = data;
|
||||
uint32_t rate;
|
||||
int r;
|
||||
|
@ -189,7 +185,7 @@ int config_parse_tc_network_emulator_packet_limit(
|
|||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
|
||||
Network *network = data;
|
||||
int r;
|
||||
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#include "networkd-link.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef struct QDiscs QDiscs;
|
||||
|
||||
typedef struct NetworkEmulator {
|
||||
usec_t delay;
|
||||
usec_t jitter;
|
||||
|
@ -20,7 +18,7 @@ typedef struct NetworkEmulator {
|
|||
} NetworkEmulator;
|
||||
|
||||
int network_emulator_new(NetworkEmulator **ret);
|
||||
int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req);
|
||||
int network_emulator_fill_message(Link *link, const NetworkEmulator *ne, sd_netlink_message *req);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_delay);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_rate);
|
||||
|
|
|
@ -12,16 +12,15 @@
|
|||
#include "qdisc.h"
|
||||
#include "set.h"
|
||||
#include "string-util.h"
|
||||
#include "util.h"
|
||||
|
||||
static int qdisc_new(QDiscs **ret) {
|
||||
QDiscs *qdisc;
|
||||
static int qdisc_new(QDisc **ret) {
|
||||
QDisc *qdisc;
|
||||
|
||||
qdisc = new(QDiscs, 1);
|
||||
qdisc = new(QDisc, 1);
|
||||
if (!qdisc)
|
||||
return -ENOMEM;
|
||||
|
||||
*qdisc = (QDiscs) {
|
||||
*qdisc = (QDisc) {
|
||||
.family = AF_UNSPEC,
|
||||
.parent = TC_H_ROOT,
|
||||
};
|
||||
|
@ -31,9 +30,9 @@ static int qdisc_new(QDiscs **ret) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret) {
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDisc **ret) {
|
||||
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
|
||||
_cleanup_(qdisc_freep) QDiscs *qdisc = NULL;
|
||||
_cleanup_(qdisc_freep) QDisc *qdisc = NULL;
|
||||
int r;
|
||||
|
||||
assert(network);
|
||||
|
@ -76,7 +75,7 @@ int qdisc_new_static(Network *network, const char *filename, unsigned section_li
|
|||
return 0;
|
||||
}
|
||||
|
||||
void qdisc_free(QDiscs *qdisc) {
|
||||
void qdisc_free(QDisc *qdisc) {
|
||||
if (!qdisc)
|
||||
return;
|
||||
|
||||
|
@ -106,7 +105,7 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
|
|||
}
|
||||
|
||||
if (link->route_messages == 0) {
|
||||
log_link_debug(link, "QDiscs configured");
|
||||
log_link_debug(link, "QDisc configured");
|
||||
link->qdiscs_configured = true;
|
||||
link_check_ready(link);
|
||||
}
|
||||
|
@ -114,7 +113,7 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
int qdisc_configure(Link *link, QDiscs *qdisc) {
|
||||
int qdisc_configure(Link *link, QDisc *qdisc) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
|
||||
_cleanup_free_ char *tca_kind = NULL;
|
||||
int r;
|
||||
|
@ -147,7 +146,27 @@ int qdisc_configure(Link *link, QDiscs *qdisc) {
|
|||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = network_emulator_fill_message(link, qdisc, req);
|
||||
r = network_emulator_fill_message(link, &qdisc->ne, req);
|
||||
if (r < 0)
|
||||
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 (qdisc->has_stochastic_fairness_queueing) {
|
||||
r = free_and_strdup(&tca_kind, "sfq");
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = stochastic_fairness_queueing_fill_message(link, &qdisc->sfq, req);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
@ -168,6 +187,42 @@ int qdisc_configure(Link *link, QDiscs *qdisc) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
|
||||
unsigned i;
|
||||
|
||||
assert(qdisc);
|
||||
assert(has_root);
|
||||
assert(has_clsact);
|
||||
|
||||
if (section_is_invalid(qdisc->section))
|
||||
return -EINVAL;
|
||||
|
||||
i = qdisc->has_network_emulator + qdisc->has_token_buffer_filter + qdisc->has_stochastic_fairness_queueing;
|
||||
if (i > 1)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: TrafficControlQueueingDiscipline section has more than one type of discipline. "
|
||||
"Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
|
||||
qdisc->section->filename, qdisc->section->line);
|
||||
|
||||
if (qdisc->parent == TC_H_ROOT) {
|
||||
if (*has_root)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: More than one root TrafficControlQueueingDiscipline sections are defined. "
|
||||
"Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
|
||||
qdisc->section->filename, qdisc->section->line);
|
||||
*has_root = true;
|
||||
} else if (qdisc->parent == TC_H_CLSACT) {
|
||||
if (*has_clsact)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: More than one clsact TrafficControlQueueingDiscipline sections are defined. "
|
||||
"Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
|
||||
qdisc->section->filename, qdisc->section->line);
|
||||
*has_clsact = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_qdiscs_parent(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
|
@ -180,7 +235,7 @@ int config_parse_tc_qdiscs_parent(
|
|||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
|
||||
_cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
|
||||
Network *network = data;
|
||||
int r;
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#include "networkd-link.h"
|
||||
#include "networkd-network.h"
|
||||
#include "networkd-util.h"
|
||||
#include "sfq.h"
|
||||
#include "tbf.h"
|
||||
|
||||
typedef struct QDiscs {
|
||||
typedef struct QDisc {
|
||||
NetworkConfigSection *section;
|
||||
Network *network;
|
||||
|
||||
|
@ -20,15 +22,21 @@ typedef struct QDiscs {
|
|||
uint32_t parent;
|
||||
|
||||
bool has_network_emulator:1;
|
||||
bool has_token_buffer_filter:1;
|
||||
bool has_stochastic_fairness_queueing:1;
|
||||
|
||||
NetworkEmulator ne;
|
||||
} QDiscs;
|
||||
TokenBufferFilter tbf;
|
||||
StochasticFairnessQueueing sfq;
|
||||
} QDisc;
|
||||
|
||||
void qdisc_free(QDiscs *qdisc);
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret);
|
||||
void qdisc_free(QDisc *qdisc);
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDisc **ret);
|
||||
|
||||
int qdisc_configure(Link *link, QDiscs *qdisc);
|
||||
int qdisc_configure(Link *link, QDisc *qdisc);
|
||||
|
||||
DEFINE_NETWORK_SECTION_FUNCTIONS(QDiscs, qdisc_free);
|
||||
int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact);
|
||||
|
||||
DEFINE_NETWORK_SECTION_FUNCTIONS(QDisc, qdisc_free);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_qdiscs_parent);
|
||||
|
|
87
src/network/tc/sfq.c
Normal file
87
src/network/tc/sfq.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
|
||||
#include <linux/pkt_sched.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-parser.h"
|
||||
#include "netlink-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "qdisc.h"
|
||||
#include "sfq.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int stochastic_fairness_queueing_new(StochasticFairnessQueueing **ret) {
|
||||
StochasticFairnessQueueing *sfq = NULL;
|
||||
|
||||
sfq = new0(StochasticFairnessQueueing, 1);
|
||||
if (!sfq)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(sfq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stochastic_fairness_queueing_fill_message(Link *link, const StochasticFairnessQueueing *sfq, sd_netlink_message *req) {
|
||||
struct tc_sfq_qopt_v1 opt = {};
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(sfq);
|
||||
assert(req);
|
||||
|
||||
opt.v0.perturb_period = sfq->perturb_period / USEC_PER_SEC;
|
||||
|
||||
r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_sfq_qopt_v1));
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append TCA_OPTIONS attribute: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_stochastic_fairness_queueing_perturb_period(
|
||||
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) QDisc *qdisc = NULL;
|
||||
Network *network = data;
|
||||
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->sfq.perturb_period = 0;
|
||||
|
||||
qdisc = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = parse_sec(rvalue, &qdisc->sfq.perturb_period);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r,
|
||||
"Failed to parse '%s=', ignoring assignment: %s",
|
||||
lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qdisc->has_stochastic_fairness_queueing = true;
|
||||
qdisc = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
17
src/network/tc/sfq.h
Normal file
17
src/network/tc/sfq.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
/* 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 StochasticFairnessQueueing {
|
||||
usec_t perturb_period;
|
||||
} StochasticFairnessQueueing;
|
||||
|
||||
int stochastic_fairness_queueing_new(StochasticFairnessQueueing **ret);
|
||||
int stochastic_fairness_queueing_fill_message(Link *link, const StochasticFairnessQueueing *sfq, sd_netlink_message *req);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_stochastic_fairness_queueing_perturb_period);
|
167
src/network/tc/tbf.c
Normal file
167
src/network/tc/tbf.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
|
||||
#include <linux/pkt_sched.h>
|
||||
#include <math.h>
|
||||
|
||||
#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) QDisc *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) QDisc *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;
|
||||
}
|
21
src/network/tc/tbf.h
Normal file
21
src/network/tc/tbf.h
Normal file
|
@ -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);
|
|
@ -270,3 +270,7 @@ NetworkEmulatorDelayJitterSec=
|
|||
NetworkEmulatorLossRate=
|
||||
NetworkEmulatorDuplicateRate=
|
||||
NetworkEmulatorPacketLimit=
|
||||
TokenBufferFilterRate=
|
||||
TokenBufferFilterBurst=
|
||||
TokenBufferFilterLatencySec=
|
||||
StochasticFairnessQueueingPerturbPeriodSec=
|
||||
|
|
16
test/test-network/conf/25-qdisc-tbf-and-sfq.network
Normal file
16
test/test-network/conf/25-qdisc-tbf-and-sfq.network
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Match]
|
||||
Name=test1
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=no
|
||||
Address=10.1.2.4/16
|
||||
|
||||
[TrafficControlQueueingDiscipline]
|
||||
Parent=root
|
||||
TokenBufferFilterRate=0.5M
|
||||
TokenBufferFilterBurst=5K
|
||||
TokenBufferFilterLatencySec=70msec
|
||||
|
||||
[TrafficControlQueueingDiscipline]
|
||||
Parent=clsact
|
||||
StochasticFairnessQueueingPerturbPeriodSec=5sec
|
|
@ -1498,7 +1498,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
|||
'25-neighbor-ip-dummy.network',
|
||||
'25-neighbor-ip.network',
|
||||
'25-nexthop.network',
|
||||
'25-qdisc.network',
|
||||
'25-qdisc-netem.network',
|
||||
'25-qdisc-tbf-and-sfq.network',
|
||||
'25-route-ipv6-src.network',
|
||||
'25-route-static.network',
|
||||
'25-gateway-static.network',
|
||||
|
@ -2057,15 +2058,23 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
|||
self.assertRegex(output, '192.168.5.1')
|
||||
|
||||
def test_qdisc(self):
|
||||
copy_unit_to_networkd_unit_path('25-qdisc.network', '12-dummy.netdev')
|
||||
copy_unit_to_networkd_unit_path('25-qdisc-netem.network', '12-dummy.netdev',
|
||||
'25-qdisc-tbf-and-sfq.network', '11-dummy.netdev')
|
||||
start_networkd()
|
||||
|
||||
self.wait_online(['dummy98:routable'])
|
||||
self.wait_online(['dummy98:routable', 'test1:routable'])
|
||||
|
||||
output = check_output('tc qdisc show dev dummy98')
|
||||
print(output)
|
||||
self.assertRegex(output, 'qdisc netem')
|
||||
self.assertRegex(output, 'limit 100 delay 50.0ms 10.0ms loss 20%')
|
||||
self.assertRegex(output, 'limit 200 delay 100.0ms 13.0ms loss 20.5%')
|
||||
output = check_output('tc qdisc show dev test1')
|
||||
print(output)
|
||||
self.assertRegex(output, 'qdisc tbf')
|
||||
self.assertRegex(output, 'rate 500Kbit burst 5000b lat 70.0ms')
|
||||
self.assertRegex(output, 'qdisc sfq')
|
||||
self.assertRegex(output, 'perturb 5sec')
|
||||
|
||||
class NetworkdStateFileTests(unittest.TestCase, Utilities):
|
||||
links = [
|
||||
|
|
Loading…
Reference in a new issue