mirror of
https://github.com/systemd/systemd.git
synced 2025-02-24 17:57:34 +03:00
network: introduce TrafficControl
Add network delay to a interface
This commit is contained in:
parent
644ee25461
commit
0f5bd7fe24
@ -2287,6 +2287,56 @@
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[TrafficControlQueueingDiscipline] Section Options</title>
|
||||
<para>The <literal>[TrafficControlQueueingDiscipline]</literal> section manages the Traffic control. It can be used
|
||||
to configure the kernel packet scheduler and simulate packet delay and loss for UDP or TCP applications,
|
||||
or limit the bandwidth usage of a particular service to simulate internet connections.</para>
|
||||
|
||||
<variablelist class='network-directives'>
|
||||
<varlistentry>
|
||||
<term><varname>Parent=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the parent Queueing Discipline (qdisc). Takes one of <literal>root</literal>
|
||||
or <literal>clsact</literal>. Defaults to <literal>root</literal>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>NetworkEmulatorDelaySec=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the fixed amount of delay to be added to all packets going out of the
|
||||
interface. Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>NetworkEmulatorDelayJitterSec=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the chosen delay to be added to the packets outgoing to the network
|
||||
interface. Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>NetworkEmulatorPacketLimit=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the maximum number of packets the qdisc may hold queued at a time.
|
||||
An unsigned integer ranges 0 to 4294967294. Defaults to 1000.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>NetworkEmulatorLossRate=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies an independent loss probability to be added to the packets outgoing from the
|
||||
network interface. Takes a percentage value, suffixed with "%". Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[BridgeVLAN] Section Options</title>
|
||||
<para>The <literal>[BridgeVLAN]</literal> section manages the VLAN ID configuration of a bridge port and accepts
|
||||
|
1184
src/basic/linux/pkt_sched.h
Normal file
1184
src/basic/linux/pkt_sched.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,9 +18,10 @@
|
||||
#include <linux/if_link.h>
|
||||
#include <linux/if_macsec.h>
|
||||
#include <linux/if_tunnel.h>
|
||||
#include <linux/nexthop.h>
|
||||
#include <linux/l2tp.h>
|
||||
#include <linux/nexthop.h>
|
||||
#include <linux/nl80211.h>
|
||||
#include <linux/pkt_sched.h>
|
||||
#include <linux/veth.h>
|
||||
#include <linux/wireguard.h>
|
||||
|
||||
@ -733,6 +734,18 @@ static const NLTypeSystem rtnl_nexthop_type_system = {
|
||||
.types = rtnl_nexthop_types,
|
||||
};
|
||||
|
||||
static const NLType rtnl_qdisc_types[] = {
|
||||
[TCA_KIND] = { .type = NETLINK_TYPE_STRING },
|
||||
[TCA_OPTIONS] = { .size = sizeof(struct tc_netem_qopt) },
|
||||
[TCA_INGRESS_BLOCK] = { .type = NETLINK_TYPE_U32 },
|
||||
[TCA_EGRESS_BLOCK] = { .type = NETLINK_TYPE_U32 },
|
||||
};
|
||||
|
||||
static const NLTypeSystem rtnl_qdisc_type_system = {
|
||||
.count = ELEMENTSOF(rtnl_qdisc_types),
|
||||
.types = rtnl_qdisc_types,
|
||||
};
|
||||
|
||||
static const NLType rtnl_types[] = {
|
||||
[NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 },
|
||||
[NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) },
|
||||
@ -758,6 +771,9 @@ static const NLType rtnl_types[] = {
|
||||
[RTM_NEWNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) },
|
||||
[RTM_DELNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) },
|
||||
[RTM_GETNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) },
|
||||
[RTM_NEWQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) },
|
||||
[RTM_DELQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) },
|
||||
[RTM_GETQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) },
|
||||
};
|
||||
|
||||
const NLTypeSystem rtnl_type_system_root = {
|
||||
|
@ -41,6 +41,10 @@ static inline bool rtnl_message_type_is_routing_policy_rule(uint16_t type) {
|
||||
return IN_SET(type, RTM_NEWRULE, RTM_DELRULE, RTM_GETRULE);
|
||||
}
|
||||
|
||||
static inline bool rtnl_message_type_is_qdisc(uint16_t type) {
|
||||
return IN_SET(type, RTM_NEWQDISC, RTM_DELQDISC, RTM_GETQDISC);
|
||||
}
|
||||
|
||||
int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name);
|
||||
int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, uint32_t mtu);
|
||||
|
||||
|
@ -1033,3 +1033,46 @@ int sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(const sd_netlink_m
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_rtnl_message_new_qdisc(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int tcm_family, int tcm_ifindex) {
|
||||
struct tcmsg *tcm;
|
||||
int r;
|
||||
|
||||
assert_return(rtnl_message_type_is_qdisc(nlmsg_type), -EINVAL);
|
||||
assert_return(ret, -EINVAL);
|
||||
|
||||
r = message_new(rtnl, ret, nlmsg_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (nlmsg_type == RTM_NEWQDISC)
|
||||
(*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
|
||||
|
||||
tcm = NLMSG_DATA((*ret)->hdr);
|
||||
tcm->tcm_family = tcm_family;
|
||||
tcm->tcm_ifindex = tcm_ifindex;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_rtnl_message_set_qdisc_parent(sd_netlink_message *m, uint32_t parent) {
|
||||
struct tcmsg *tcm;
|
||||
|
||||
assert_return(rtnl_message_type_is_qdisc(m->hdr->nlmsg_type), -EINVAL);
|
||||
|
||||
tcm = NLMSG_DATA(m->hdr);
|
||||
tcm->tcm_parent = parent;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_rtnl_message_set_qdisc_handle(sd_netlink_message *m, uint32_t handle) {
|
||||
struct tcmsg *tcm;
|
||||
|
||||
assert_return(rtnl_message_type_is_qdisc(m->hdr->nlmsg_type), -EINVAL);
|
||||
|
||||
tcm = NLMSG_DATA(m->hdr);
|
||||
tcm->tcm_handle = handle;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -105,6 +105,12 @@ sources = files('''
|
||||
networkd-util.h
|
||||
networkd-wifi.c
|
||||
networkd-wifi.h
|
||||
tc/netem.c
|
||||
tc/netem.h
|
||||
tc/qdisc.c
|
||||
tc/qdisc.h
|
||||
tc/tc-util.c
|
||||
tc/tc-util.h
|
||||
'''.split())
|
||||
|
||||
systemd_networkd_sources = files('networkd.c')
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "tmpfile-util.h"
|
||||
#include "udev-util.h"
|
||||
#include "util.h"
|
||||
#include "tc/qdisc.h"
|
||||
#include "virt.h"
|
||||
|
||||
uint32_t link_get_vrf_table(Link *link) {
|
||||
@ -2581,6 +2582,8 @@ static int link_drop_config(Link *link) {
|
||||
}
|
||||
|
||||
static int link_configure(Link *link) {
|
||||
QDiscs *qdisc;
|
||||
Iterator i;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
@ -2590,6 +2593,9 @@ static int link_configure(Link *link) {
|
||||
if (link->iftype == ARPHRD_CAN)
|
||||
return link_configure_can(link);
|
||||
|
||||
ORDERED_HASHMAP_FOREACH(qdisc, link->network->qdiscs_by_section, i)
|
||||
(void) qdisc_configure(link, qdisc);
|
||||
|
||||
/* Drop foreign config, but ignore loopback or critical devices.
|
||||
* We do not want to remove loopback address or addresses used for root NFS. */
|
||||
if (!(link->flags & IFF_LOOPBACK) &&
|
||||
|
@ -78,6 +78,7 @@ typedef struct Link {
|
||||
unsigned nexthop_messages;
|
||||
unsigned routing_policy_rule_messages;
|
||||
unsigned routing_policy_rule_remove_messages;
|
||||
unsigned qdisc_messages;
|
||||
unsigned enslaving;
|
||||
|
||||
Set *addresses;
|
||||
|
@ -13,6 +13,8 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
|
||||
#include "networkd-ndisc.h"
|
||||
#include "networkd-network.h"
|
||||
#include "vlan-util.h"
|
||||
#include "tc/qdisc.h"
|
||||
#include "tc/netem.h"
|
||||
%}
|
||||
struct ConfigPerfItem;
|
||||
%null_strings
|
||||
@ -241,6 +243,11 @@ CAN.BitRate, config_parse_si_size,
|
||||
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
|
||||
CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
|
||||
CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
|
||||
TrafficControlQueueingDiscipline.Parent, config_parse_tc_qdiscs_parent, 0, 0
|
||||
TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_tc_network_emulator_delay, 0, 0
|
||||
TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_tc_network_emulator_delay, 0, 0
|
||||
TrafficControlQueueingDiscipline.NetworkEmulatorLossRate, config_parse_tc_network_emulator_loss_rate, 0, 0
|
||||
TrafficControlQueueingDiscipline.NetworkEmulatorPacketLimit, config_parse_tc_network_emulator_packet_limit, 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)
|
||||
|
@ -471,6 +471,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
|
||||
"IPv6PrefixDelegation\0"
|
||||
"IPv6Prefix\0"
|
||||
"IPv6RoutePrefix\0"
|
||||
"TrafficControlQueueingDiscipline\0"
|
||||
"CAN\0",
|
||||
config_item_perf_lookup, network_network_gperf_lookup,
|
||||
CONFIG_PARSE_WARN, network);
|
||||
@ -663,6 +664,7 @@ static Network *network_free(Network *network) {
|
||||
hashmap_free(network->address_labels_by_section);
|
||||
hashmap_free(network->prefixes_by_section);
|
||||
hashmap_free(network->rules_by_section);
|
||||
ordered_hashmap_free_with_destructor(network->qdiscs_by_section, qdisc_free);
|
||||
|
||||
if (network->manager &&
|
||||
network->manager->duids_requesting_uuid)
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "networkd-util.h"
|
||||
#include "ordered-set.h"
|
||||
#include "resolve-util.h"
|
||||
#include "tc/qdisc.h"
|
||||
|
||||
typedef enum IPv6PrivacyExtensions {
|
||||
/* The values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values */
|
||||
@ -265,6 +266,7 @@ struct Network {
|
||||
Hashmap *prefixes_by_section;
|
||||
Hashmap *route_prefixes_by_section;
|
||||
Hashmap *rules_by_section;
|
||||
OrderedHashmap *qdiscs_by_section;
|
||||
|
||||
/* All kinds of DNS configuration */
|
||||
struct in_addr_data *dns;
|
||||
|
213
src/network/tc/netem.c
Normal file
213
src/network/tc/netem.c
Normal file
@ -0,0 +1,213 @@
|
||||
/* 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 "hashmap.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "netem.h"
|
||||
#include "netlink-util.h"
|
||||
#include "networkd-manager.h"
|
||||
#include "parse-util.h"
|
||||
#include "qdisc.h"
|
||||
#include "string-util.h"
|
||||
#include "tc-util.h"
|
||||
#include "util.h"
|
||||
|
||||
int network_emulator_new(NetworkEmulator **ret) {
|
||||
NetworkEmulator *ne = NULL;
|
||||
|
||||
ne = new(NetworkEmulator, 1);
|
||||
if (!ne)
|
||||
return -ENOMEM;
|
||||
|
||||
*ne = (NetworkEmulator) {
|
||||
.delay = USEC_INFINITY,
|
||||
.jitter = USEC_INFINITY,
|
||||
};
|
||||
|
||||
*ret = TAKE_PTR(ne);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req) {
|
||||
struct tc_netem_qopt opt = {
|
||||
.limit = 1000,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(qdisc);
|
||||
assert(req);
|
||||
|
||||
if (qdisc->ne.limit > 0)
|
||||
opt.limit = qdisc->ne.limit;
|
||||
|
||||
if (qdisc->ne.loss > 0)
|
||||
opt.loss = qdisc->ne.loss;
|
||||
|
||||
if (qdisc->ne.delay != USEC_INFINITY) {
|
||||
r = tc_time_to_tick(qdisc->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 (r < 0)
|
||||
return log_link_error_errno(link, r, "Failed to calculate jitter in TCA_OPTION: %m");
|
||||
}
|
||||
|
||||
r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_netem_qopt));
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append TCA_OPTION attribute: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_network_emulator_delay(
|
||||
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)) {
|
||||
if (streq(lvalue, "NetworkEmulatorDelaySec"))
|
||||
qdisc->ne.delay = USEC_INFINITY;
|
||||
else if (streq(lvalue, "NetworkEmulatorDelayJitterSec"))
|
||||
qdisc->ne.jitter = USEC_INFINITY;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (streq(lvalue, "NetworkEmulatorDelaySec"))
|
||||
qdisc->ne.delay = u;
|
||||
else if (streq(lvalue, "NetworkEmulatorDelayJitterSec"))
|
||||
qdisc->ne.jitter = u;
|
||||
|
||||
qdisc->has_network_emulator = true;
|
||||
qdisc = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_network_emulator_loss_rate(
|
||||
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;
|
||||
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->ne.loss = 0;
|
||||
|
||||
qdisc = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = parse_tc_percent(rvalue, &qdisc->ne.loss);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r,
|
||||
"Failed to parse 'NetworkEmularorLossRate=', ignoring assignment: %s",
|
||||
rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qdisc = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_network_emulator_packet_limit(
|
||||
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;
|
||||
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->ne.limit = 0;
|
||||
qdisc = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = safe_atou(rvalue, &qdisc->ne.limit);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r,
|
||||
"Failed to parse 'NetworkEmulatorPacketLimit=', ignoring assignment: %s",
|
||||
rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qdisc = NULL;
|
||||
return 0;
|
||||
}
|
28
src/network/tc/netem.h
Normal file
28
src/network/tc/netem.h
Normal file
@ -0,0 +1,28 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
#pragma once
|
||||
|
||||
#include "sd-netlink.h"
|
||||
|
||||
#include "conf-parser.h"
|
||||
#include "macro.h"
|
||||
#include "../networkd-link.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef struct NetworkEmulator NetworkEmulator;
|
||||
typedef struct QDiscs QDiscs;
|
||||
|
||||
struct NetworkEmulator {
|
||||
usec_t delay;
|
||||
usec_t jitter;
|
||||
|
||||
uint32_t limit;
|
||||
uint32_t loss;
|
||||
};
|
||||
|
||||
int network_emulator_new(NetworkEmulator **ret);
|
||||
int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_delay);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_loss_rate);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_packet_limit);
|
196
src/network/tc/qdisc.c
Normal file
196
src/network/tc/qdisc.c
Normal file
@ -0,0 +1,196 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
|
||||
#include <linux/pkt_sched.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-parser.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "netlink-util.h"
|
||||
#include "networkd-manager.h"
|
||||
#include "parse-util.h"
|
||||
#include "qdisc.h"
|
||||
#include "set.h"
|
||||
#include "string-util.h"
|
||||
#include "util.h"
|
||||
|
||||
static int qdisc_new(QDiscs **ret) {
|
||||
QDiscs *qdisc;
|
||||
|
||||
qdisc = new(QDiscs, 1);
|
||||
if (!qdisc)
|
||||
return -ENOMEM;
|
||||
|
||||
*qdisc = (QDiscs) {
|
||||
.family = AF_UNSPEC,
|
||||
.parent = TC_H_ROOT,
|
||||
};
|
||||
|
||||
*ret = TAKE_PTR(qdisc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret) {
|
||||
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
|
||||
_cleanup_(qdisc_freep) QDiscs *qdisc = NULL;
|
||||
int r;
|
||||
|
||||
assert(network);
|
||||
assert(ret);
|
||||
assert(!!filename == (section_line > 0));
|
||||
|
||||
if (filename) {
|
||||
r = network_config_section_new(filename, section_line, &n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
qdisc = ordered_hashmap_get(network->qdiscs_by_section, n);
|
||||
if (qdisc) {
|
||||
*ret = TAKE_PTR(qdisc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
r = qdisc_new(&qdisc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
qdisc->network = network;
|
||||
|
||||
if (filename) {
|
||||
qdisc->section = TAKE_PTR(n);
|
||||
|
||||
r = ordered_hashmap_ensure_allocated(&network->qdiscs_by_section, &network_config_hash_ops);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = ordered_hashmap_put(network->qdiscs_by_section, qdisc->section, qdisc);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(qdisc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void qdisc_free(QDiscs *qdisc) {
|
||||
if (!qdisc)
|
||||
return;
|
||||
|
||||
if (qdisc->network && qdisc->section)
|
||||
ordered_hashmap_remove(qdisc->network->qdiscs_by_section, qdisc->section);
|
||||
|
||||
network_config_section_free(qdisc->section);
|
||||
|
||||
free(qdisc);
|
||||
}
|
||||
|
||||
static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->qdisc_messages > 0);
|
||||
link->qdisc_messages--;
|
||||
|
||||
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
|
||||
return 1;
|
||||
|
||||
r = sd_netlink_message_get_errno(m);
|
||||
if (r < 0 && r != -EEXIST) {
|
||||
log_link_error_errno(link, r, "Could not set QDisc: %m");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int qdisc_configure(Link *link, QDiscs *qdisc) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
assert(link->manager->rtnl);
|
||||
assert(link->ifindex > 0);
|
||||
|
||||
r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not create RTM_NEWQDISC message: %m");
|
||||
|
||||
r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not create tcm_parent message: %m");
|
||||
|
||||
if (qdisc->parent == TC_H_CLSACT) {
|
||||
r = sd_rtnl_message_set_qdisc_handle(req, TC_H_MAKE(TC_H_CLSACT, 0));
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not set tcm_handle message: %m");
|
||||
|
||||
r = sd_netlink_message_append_string(req, TCA_KIND, "clsact");
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
|
||||
}
|
||||
|
||||
if (qdisc->has_network_emulator) {
|
||||
r = sd_netlink_message_append_string(req, TCA_KIND, "netem");
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
|
||||
|
||||
r = network_emulator_fill_message(link, qdisc, req);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
|
||||
|
||||
link_ref(link);
|
||||
link->qdisc_messages++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_tc_qdiscs_parent(
|
||||
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;
|
||||
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 (streq(rvalue, "root"))
|
||||
qdisc->parent = TC_H_ROOT;
|
||||
else if (streq(rvalue, "clsact"))
|
||||
qdisc->parent = TC_H_CLSACT;
|
||||
else {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r,
|
||||
"Failed to parse [QueueDiscs] 'Parent=', ignoring assignment: %s",
|
||||
rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qdisc = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
35
src/network/tc/qdisc.h
Normal file
35
src/network/tc/qdisc.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
#pragma once
|
||||
|
||||
#include "conf-parser.h"
|
||||
#include "macro.h"
|
||||
#include "netem.h"
|
||||
#include "../networkd-util.h"
|
||||
|
||||
typedef struct QDiscs QDiscs;
|
||||
|
||||
struct QDiscs {
|
||||
NetworkConfigSection *section;
|
||||
Network *network;
|
||||
|
||||
Link *link;
|
||||
|
||||
int family;
|
||||
|
||||
uint32_t handle;
|
||||
uint32_t parent;
|
||||
|
||||
bool has_network_emulator:1;
|
||||
|
||||
NetworkEmulator ne;
|
||||
};
|
||||
|
||||
void qdisc_free(QDiscs *qdisc);
|
||||
int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret);
|
||||
|
||||
int qdisc_configure(Link *link, QDiscs *qdisc);
|
||||
|
||||
DEFINE_NETWORK_SECTION_FUNCTIONS(QDiscs, qdisc_free);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tc_qdiscs_parent);
|
63
src/network/tc/tc-util.c
Normal file
63
src/network/tc/tc-util.c
Normal file
@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "fileio.h"
|
||||
#include "parse-util.h"
|
||||
#include "tc-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
static int tc_init(double *ticks_in_usec) {
|
||||
uint32_t clock_resolution, ticks_to_usec, usec_to_ticks;
|
||||
_cleanup_free_ char *line = NULL;
|
||||
double clock_factor;
|
||||
int r;
|
||||
|
||||
r = read_one_line_file("/proc/net/psched", &line);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sscanf(line, "%08x%08x%08x", &ticks_to_usec, &usec_to_ticks, &clock_resolution);
|
||||
if (r < 3)
|
||||
return -EIO;
|
||||
|
||||
clock_factor = (double) clock_resolution / USEC_PER_SEC;
|
||||
*ticks_in_usec = (double) ticks_to_usec / usec_to_ticks * clock_factor;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tc_time_to_tick(usec_t t, uint32_t *ret) {
|
||||
static double ticks_in_usec = -1;
|
||||
usec_t a;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (ticks_in_usec < 0) {
|
||||
r = tc_init(&ticks_in_usec);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
a = t * ticks_in_usec;
|
||||
if (a > UINT32_MAX)
|
||||
return -ERANGE;
|
||||
|
||||
*ret = a;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_tc_percent(const char *s, uint32_t *percent) {
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(percent);
|
||||
|
||||
r = parse_permille(s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*percent = (double) r / 1000 * UINT32_MAX;
|
||||
return 0;
|
||||
}
|
8
src/network/tc/tc-util.h
Normal file
8
src/network/tc/tc-util.h
Normal file
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+
|
||||
* Copyright © 2019 VMware, Inc. */
|
||||
#pragma once
|
||||
|
||||
#include "time-util.h"
|
||||
|
||||
int tc_time_to_tick(usec_t t, uint32_t *ret);
|
||||
int parse_tc_percent(const char *s, uint32_t *percent);
|
@ -202,6 +202,10 @@ int sd_rtnl_message_routing_policy_rule_get_rtm_type(const sd_netlink_message *m
|
||||
int sd_rtnl_message_routing_policy_rule_set_flags(sd_netlink_message *m, unsigned flags);
|
||||
int sd_rtnl_message_routing_policy_rule_get_flags(const sd_netlink_message *m, unsigned *flags);
|
||||
|
||||
int sd_rtnl_message_new_qdisc(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int tcm_family, int tcm_ifindex);
|
||||
int sd_rtnl_message_set_qdisc_parent(sd_netlink_message *m, uint32_t parent);
|
||||
int sd_rtnl_message_set_qdisc_handle(sd_netlink_message *m, uint32_t handle);
|
||||
|
||||
/* genl */
|
||||
int sd_genl_socket_open(sd_netlink **nl);
|
||||
int sd_genl_message_new(sd_netlink *nl, sd_genl_family family, uint8_t cmd, sd_netlink_message **m);
|
||||
|
@ -262,3 +262,9 @@ DNS=
|
||||
[NextHop]
|
||||
Id=
|
||||
Gateway=
|
||||
[TrafficControlQueueingDiscipline]
|
||||
Parent=
|
||||
NetworkEmulatorDelaySec=
|
||||
NetworkEmulatorDelayJitterSec=
|
||||
NetworkEmulatorLossRate=
|
||||
NetworkEmulatorPacketLimit=
|
||||
|
Loading…
x
Reference in New Issue
Block a user