Merge pull request #13747 from ssahani/tc-qdisc
network: introduce Traffic Control
This commit is contained in:
commit
a346aa7c38
|
@ -2287,6 +2287,56 @@
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</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>
|
<refsect1>
|
||||||
<title>[BridgeVLAN] Section Options</title>
|
<title>[BridgeVLAN] Section Options</title>
|
||||||
<para>The <literal>[BridgeVLAN]</literal> section manages the VLAN ID configuration of a bridge port and accepts
|
<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_link.h>
|
||||||
#include <linux/if_macsec.h>
|
#include <linux/if_macsec.h>
|
||||||
#include <linux/if_tunnel.h>
|
#include <linux/if_tunnel.h>
|
||||||
#include <linux/nexthop.h>
|
|
||||||
#include <linux/l2tp.h>
|
#include <linux/l2tp.h>
|
||||||
|
#include <linux/nexthop.h>
|
||||||
#include <linux/nl80211.h>
|
#include <linux/nl80211.h>
|
||||||
|
#include <linux/pkt_sched.h>
|
||||||
#include <linux/veth.h>
|
#include <linux/veth.h>
|
||||||
#include <linux/wireguard.h>
|
#include <linux/wireguard.h>
|
||||||
|
|
||||||
|
@ -733,6 +734,18 @@ static const NLTypeSystem rtnl_nexthop_type_system = {
|
||||||
.types = rtnl_nexthop_types,
|
.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[] = {
|
static const NLType rtnl_types[] = {
|
||||||
[NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 },
|
[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) },
|
[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_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_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_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 = {
|
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);
|
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_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);
|
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;
|
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-util.h
|
||||||
networkd-wifi.c
|
networkd-wifi.c
|
||||||
networkd-wifi.h
|
networkd-wifi.h
|
||||||
|
tc/netem.c
|
||||||
|
tc/netem.h
|
||||||
|
tc/qdisc.c
|
||||||
|
tc/qdisc.h
|
||||||
|
tc/tc-util.c
|
||||||
|
tc/tc-util.h
|
||||||
'''.split())
|
'''.split())
|
||||||
|
|
||||||
systemd_networkd_sources = files('networkd.c')
|
systemd_networkd_sources = files('networkd.c')
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "tmpfile-util.h"
|
#include "tmpfile-util.h"
|
||||||
#include "udev-util.h"
|
#include "udev-util.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "tc/qdisc.h"
|
||||||
#include "virt.h"
|
#include "virt.h"
|
||||||
|
|
||||||
uint32_t link_get_vrf_table(Link *link) {
|
uint32_t link_get_vrf_table(Link *link) {
|
||||||
|
@ -1094,6 +1095,9 @@ void link_check_ready(Link *link) {
|
||||||
if (!link->routing_policy_rules_configured)
|
if (!link->routing_policy_rules_configured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!link->qdiscs_configured)
|
||||||
|
return;
|
||||||
|
|
||||||
if (link_has_carrier(link) || !link->network->configure_without_carrier) {
|
if (link_has_carrier(link) || !link->network->configure_without_carrier) {
|
||||||
|
|
||||||
if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4) && !link->ipv4ll_address)
|
if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4) && !link->ipv4ll_address)
|
||||||
|
@ -2580,6 +2584,28 @@ static int link_drop_config(Link *link) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int link_configure_qdiscs(Link *link) {
|
||||||
|
QDiscs *qdisc;
|
||||||
|
Iterator i;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
link->qdiscs_configured = false;
|
||||||
|
link->qdisc_messages = 0;
|
||||||
|
|
||||||
|
ORDERED_HASHMAP_FOREACH(qdisc, link->network->qdiscs_by_section, i) {
|
||||||
|
r = qdisc_configure(link, qdisc);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link->qdisc_messages == 0)
|
||||||
|
link->qdiscs_configured = true;
|
||||||
|
else
|
||||||
|
log_link_debug(link, "Configuring QDiscs");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int link_configure(Link *link) {
|
static int link_configure(Link *link) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
@ -2587,6 +2613,10 @@ static int link_configure(Link *link) {
|
||||||
assert(link->network);
|
assert(link->network);
|
||||||
assert(link->state == LINK_STATE_INITIALIZED);
|
assert(link->state == LINK_STATE_INITIALIZED);
|
||||||
|
|
||||||
|
r = link_configure_qdiscs(link);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
if (link->iftype == ARPHRD_CAN)
|
if (link->iftype == ARPHRD_CAN)
|
||||||
return link_configure_can(link);
|
return link_configure_can(link);
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ typedef struct Link {
|
||||||
unsigned nexthop_messages;
|
unsigned nexthop_messages;
|
||||||
unsigned routing_policy_rule_messages;
|
unsigned routing_policy_rule_messages;
|
||||||
unsigned routing_policy_rule_remove_messages;
|
unsigned routing_policy_rule_remove_messages;
|
||||||
|
unsigned qdisc_messages;
|
||||||
unsigned enslaving;
|
unsigned enslaving;
|
||||||
|
|
||||||
Set *addresses;
|
Set *addresses;
|
||||||
|
@ -113,6 +114,7 @@ typedef struct Link {
|
||||||
bool static_routes_ready:1;
|
bool static_routes_ready:1;
|
||||||
bool static_nexthops_configured:1;
|
bool static_nexthops_configured:1;
|
||||||
bool routing_policy_rules_configured:1;
|
bool routing_policy_rules_configured:1;
|
||||||
|
bool qdiscs_configured:1;
|
||||||
bool setting_mtu:1;
|
bool setting_mtu:1;
|
||||||
|
|
||||||
LIST_HEAD(Address, pool_addresses);
|
LIST_HEAD(Address, pool_addresses);
|
||||||
|
|
|
@ -13,6 +13,8 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
|
||||||
#include "networkd-ndisc.h"
|
#include "networkd-ndisc.h"
|
||||||
#include "networkd-network.h"
|
#include "networkd-network.h"
|
||||||
#include "vlan-util.h"
|
#include "vlan-util.h"
|
||||||
|
#include "tc/qdisc.h"
|
||||||
|
#include "tc/netem.h"
|
||||||
%}
|
%}
|
||||||
struct ConfigPerfItem;
|
struct ConfigPerfItem;
|
||||||
%null_strings
|
%null_strings
|
||||||
|
@ -241,6 +243,11 @@ CAN.BitRate, config_parse_si_size,
|
||||||
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
|
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
|
||||||
CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
|
CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
|
||||||
CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
|
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 */
|
/* backwards compatibility: do not add new entries to this section */
|
||||||
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
|
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
|
||||||
DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
|
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"
|
"IPv6PrefixDelegation\0"
|
||||||
"IPv6Prefix\0"
|
"IPv6Prefix\0"
|
||||||
"IPv6RoutePrefix\0"
|
"IPv6RoutePrefix\0"
|
||||||
|
"TrafficControlQueueingDiscipline\0"
|
||||||
"CAN\0",
|
"CAN\0",
|
||||||
config_item_perf_lookup, network_network_gperf_lookup,
|
config_item_perf_lookup, network_network_gperf_lookup,
|
||||||
CONFIG_PARSE_WARN, network);
|
CONFIG_PARSE_WARN, network);
|
||||||
|
@ -663,6 +664,7 @@ static Network *network_free(Network *network) {
|
||||||
hashmap_free(network->address_labels_by_section);
|
hashmap_free(network->address_labels_by_section);
|
||||||
hashmap_free(network->prefixes_by_section);
|
hashmap_free(network->prefixes_by_section);
|
||||||
hashmap_free(network->rules_by_section);
|
hashmap_free(network->rules_by_section);
|
||||||
|
ordered_hashmap_free_with_destructor(network->qdiscs_by_section, qdisc_free);
|
||||||
|
|
||||||
if (network->manager &&
|
if (network->manager &&
|
||||||
network->manager->duids_requesting_uuid)
|
network->manager->duids_requesting_uuid)
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "networkd-util.h"
|
#include "networkd-util.h"
|
||||||
#include "ordered-set.h"
|
#include "ordered-set.h"
|
||||||
#include "resolve-util.h"
|
#include "resolve-util.h"
|
||||||
|
#include "tc/qdisc.h"
|
||||||
|
|
||||||
typedef enum IPv6PrivacyExtensions {
|
typedef enum IPv6PrivacyExtensions {
|
||||||
/* The values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values */
|
/* 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 *prefixes_by_section;
|
||||||
Hashmap *route_prefixes_by_section;
|
Hashmap *route_prefixes_by_section;
|
||||||
Hashmap *rules_by_section;
|
Hashmap *rules_by_section;
|
||||||
|
OrderedHashmap *qdiscs_by_section;
|
||||||
|
|
||||||
/* All kinds of DNS configuration */
|
/* All kinds of DNS configuration */
|
||||||
struct in_addr_data *dns;
|
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);
|
203
src/network/tc/qdisc.c
Normal file
203
src/network/tc/qdisc.c
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/* 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");
|
||||||
|
link_enter_failed(link);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link->route_messages == 0) {
|
||||||
|
log_link_debug(link, "QDiscs configured");
|
||||||
|
link->qdiscs_configured = true;
|
||||||
|
link_check_ready(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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_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 */
|
/* genl */
|
||||||
int sd_genl_socket_open(sd_netlink **nl);
|
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);
|
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]
|
[NextHop]
|
||||||
Id=
|
Id=
|
||||||
Gateway=
|
Gateway=
|
||||||
|
[TrafficControlQueueingDiscipline]
|
||||||
|
Parent=
|
||||||
|
NetworkEmulatorDelaySec=
|
||||||
|
NetworkEmulatorDelayJitterSec=
|
||||||
|
NetworkEmulatorLossRate=
|
||||||
|
NetworkEmulatorPacketLimit=
|
||||||
|
|
20
test/test-network/conf/25-qdisc.network
Normal file
20
test/test-network/conf/25-qdisc.network
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[Match]
|
||||||
|
Name=dummy98
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
IPv6AcceptRA=no
|
||||||
|
Address=10.1.2.3/16
|
||||||
|
|
||||||
|
[TrafficControlQueueingDiscipline]
|
||||||
|
Parent=root
|
||||||
|
NetworkEmulatorDelaySec=50ms
|
||||||
|
NetworkEmulatorDelayJitterSec=10ms
|
||||||
|
NetworkEmulatorLossRate=20%
|
||||||
|
NetworkEmulatorPacketLimit=100
|
||||||
|
|
||||||
|
[TrafficControlQueueingDiscipline]
|
||||||
|
Parent=clsact
|
||||||
|
NetworkEmulatorDelaySec=100ms
|
||||||
|
NetworkEmulatorDelayJitterSec=13ms
|
||||||
|
NetworkEmulatorLossRate=20.5%
|
||||||
|
NetworkEmulatorPacketLimit=200
|
|
@ -1485,15 +1485,16 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||||
'25-gre-tunnel-remote-any.netdev',
|
'25-gre-tunnel-remote-any.netdev',
|
||||||
'25-ip6gre-tunnel-remote-any.netdev',
|
'25-ip6gre-tunnel-remote-any.netdev',
|
||||||
'25-ipv6-address-label-section.network',
|
'25-ipv6-address-label-section.network',
|
||||||
|
'25-link-local-addressing-no.network',
|
||||||
|
'25-link-local-addressing-yes.network',
|
||||||
|
'25-link-section-unmanaged.network',
|
||||||
'25-neighbor-section.network',
|
'25-neighbor-section.network',
|
||||||
'25-neighbor-next.network',
|
'25-neighbor-next.network',
|
||||||
'25-neighbor-ipv6.network',
|
'25-neighbor-ipv6.network',
|
||||||
'25-neighbor-ip-dummy.network',
|
'25-neighbor-ip-dummy.network',
|
||||||
'25-neighbor-ip.network',
|
'25-neighbor-ip.network',
|
||||||
'25-nexthop.network',
|
'25-nexthop.network',
|
||||||
'25-link-local-addressing-no.network',
|
'25-qdisc.network',
|
||||||
'25-link-local-addressing-yes.network',
|
|
||||||
'25-link-section-unmanaged.network',
|
|
||||||
'25-route-ipv6-src.network',
|
'25-route-ipv6-src.network',
|
||||||
'25-route-static.network',
|
'25-route-static.network',
|
||||||
'25-gateway-static.network',
|
'25-gateway-static.network',
|
||||||
|
@ -2051,6 +2052,17 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||||
print(output)
|
print(output)
|
||||||
self.assertRegex(output, '192.168.5.1')
|
self.assertRegex(output, '192.168.5.1')
|
||||||
|
|
||||||
|
def test_qdisc(self):
|
||||||
|
copy_unit_to_networkd_unit_path('25-qdisc.network', '12-dummy.netdev')
|
||||||
|
start_networkd()
|
||||||
|
|
||||||
|
self.wait_online(['dummy98:routable'])
|
||||||
|
|
||||||
|
output = check_output('tc qdisc show dev dummy98')
|
||||||
|
print(output)
|
||||||
|
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%')
|
||||||
|
|
||||||
class NetworkdStateFileTests(unittest.TestCase, Utilities):
|
class NetworkdStateFileTests(unittest.TestCase, Utilities):
|
||||||
links = [
|
links = [
|
||||||
'dummy98',
|
'dummy98',
|
||||||
|
|
Loading…
Reference in a new issue