1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc. */
4 #include <linux/pkt_sched.h>
6 #include "alloc-util.h"
7 #include "conf-parser.h"
8 #include "in-addr-util.h"
9 #include "netlink-util.h"
10 #include "networkd-link.h"
11 #include "networkd-manager.h"
12 #include "networkd-network.h"
13 #include "networkd-queue.h"
14 #include "parse-util.h"
16 #include "string-util.h"
21 const TClassVTable
* const tclass_vtable
[_TCLASS_KIND_MAX
] = {
22 [TCLASS_KIND_DRR
] = &drr_tclass_vtable
,
23 [TCLASS_KIND_HTB
] = &htb_tclass_vtable
,
24 [TCLASS_KIND_QFQ
] = &qfq_tclass_vtable
,
27 static int tclass_new(TClassKind kind
, TClass
**ret
) {
28 _cleanup_(tclass_freep
) TClass
*tclass
= NULL
;
31 if (kind
== _TCLASS_KIND_INVALID
) {
32 tclass
= new(TClass
, 1);
41 assert(kind
>= 0 && kind
< _TCLASS_KIND_MAX
);
42 tclass
= malloc0(tclass_vtable
[kind
]->object_size
);
46 tclass
->parent
= TC_H_ROOT
;
49 if (TCLASS_VTABLE(tclass
)->init
) {
50 r
= TCLASS_VTABLE(tclass
)->init(tclass
);
56 *ret
= TAKE_PTR(tclass
);
61 int tclass_new_static(TClassKind kind
, Network
*network
, const char *filename
, unsigned section_line
, TClass
**ret
) {
62 _cleanup_(config_section_freep
) ConfigSection
*n
= NULL
;
63 _cleanup_(tclass_freep
) TClass
*tclass
= NULL
;
70 assert(section_line
> 0);
72 r
= config_section_new(filename
, section_line
, &n
);
76 existing
= hashmap_get(network
->tclasses_by_section
, n
);
78 if (existing
->kind
!= kind
)
85 r
= tclass_new(kind
, &tclass
);
89 tclass
->network
= network
;
90 tclass
->section
= TAKE_PTR(n
);
91 tclass
->source
= NETWORK_CONFIG_SOURCE_STATIC
;
93 r
= hashmap_ensure_put(&network
->tclasses_by_section
, &config_section_hash_ops
, tclass
->section
, tclass
);
97 *ret
= TAKE_PTR(tclass
);
101 TClass
* tclass_free(TClass
*tclass
) {
105 if (tclass
->network
&& tclass
->section
)
106 hashmap_remove(tclass
->network
->tclasses_by_section
, tclass
->section
);
108 config_section_free(tclass
->section
);
111 set_remove(tclass
->link
->tclasses
, tclass
);
113 free(tclass
->tca_kind
);
114 return mfree(tclass
);
117 static const char *tclass_get_tca_kind(const TClass
*tclass
) {
120 return (TCLASS_VTABLE(tclass
) && TCLASS_VTABLE(tclass
)->tca_kind
) ?
121 TCLASS_VTABLE(tclass
)->tca_kind
: tclass
->tca_kind
;
124 static void tclass_hash_func(const TClass
*tclass
, struct siphash
*state
) {
128 siphash24_compress(&tclass
->classid
, sizeof(tclass
->classid
), state
);
129 siphash24_compress(&tclass
->parent
, sizeof(tclass
->parent
), state
);
130 siphash24_compress_string(tclass_get_tca_kind(tclass
), state
);
133 static int tclass_compare_func(const TClass
*a
, const TClass
*b
) {
139 r
= CMP(a
->classid
, b
->classid
);
143 r
= CMP(a
->parent
, b
->parent
);
147 return strcmp_ptr(tclass_get_tca_kind(a
), tclass_get_tca_kind(b
));
150 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
157 static int tclass_get(Link
*link
, const TClass
*in
, TClass
**ret
) {
163 existing
= set_get(link
->tclasses
, in
);
172 static int tclass_add(Link
*link
, TClass
*tclass
) {
178 r
= set_ensure_put(&link
->tclasses
, &tclass_hash_ops
, tclass
);
188 static int tclass_dup(const TClass
*src
, TClass
**ret
) {
189 _cleanup_(tclass_freep
) TClass
*dst
= NULL
;
194 if (TCLASS_VTABLE(src
))
195 dst
= memdup(src
, TCLASS_VTABLE(src
)->object_size
);
197 dst
= newdup(TClass
, src
, 1);
201 /* clear all pointers */
205 dst
->tca_kind
= NULL
;
208 dst
->tca_kind
= strdup(src
->tca_kind
);
213 *ret
= TAKE_PTR(dst
);
217 int link_find_tclass(Link
*link
, uint32_t classid
, TClass
**ret
) {
222 SET_FOREACH(tclass
, link
->tclasses
) {
223 if (tclass
->classid
!= classid
)
226 if (!tclass_exists(tclass
))
237 static void log_tclass_debug(TClass
*tclass
, Link
*link
, const char *str
) {
238 _cleanup_free_
char *state
= NULL
;
246 (void) network_config_state_to_string_alloc(tclass
->state
, &state
);
248 log_link_debug(link
, "%s %s TClass (%s): classid=%"PRIx32
":%"PRIx32
", parent=%"PRIx32
":%"PRIx32
", kind=%s",
249 str
, strna(network_config_source_to_string(tclass
->source
)), strna(state
),
250 TC_H_MAJ(tclass
->classid
) >> 16, TC_H_MIN(tclass
->classid
),
251 TC_H_MAJ(tclass
->parent
) >> 16, TC_H_MIN(tclass
->parent
),
252 strna(tclass_get_tca_kind(tclass
)));
255 TClass
* tclass_drop(TClass
*tclass
) {
261 link
= ASSERT_PTR(tclass
->link
);
263 /* Also drop all child qdiscs assigned to the class. */
264 SET_FOREACH(qdisc
, link
->qdiscs
) {
265 if (qdisc
->parent
!= tclass
->classid
)
271 tclass_enter_removed(tclass
);
273 if (tclass
->state
== 0) {
274 log_tclass_debug(tclass
, link
, "Forgetting");
275 tclass
= tclass_free(tclass
);
277 log_tclass_debug(tclass
, link
, "Removed");
282 static int tclass_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Request
*req
, Link
*link
, TClass
*tclass
) {
288 r
= sd_netlink_message_get_errno(m
);
289 if (r
< 0 && r
!= -EEXIST
) {
290 log_link_message_error_errno(link
, m
, r
, "Could not set TClass");
291 link_enter_failed(link
);
295 if (link
->tc_messages
== 0) {
296 log_link_debug(link
, "Traffic control configured");
297 link
->tc_configured
= true;
298 link_check_ready(link
);
304 static int tclass_configure(TClass
*tclass
, Link
*link
, Request
*req
) {
305 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*m
= NULL
;
310 assert(link
->manager
);
311 assert(link
->manager
->rtnl
);
312 assert(link
->ifindex
> 0);
315 log_tclass_debug(tclass
, link
, "Configuring");
317 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &m
, RTM_NEWTCLASS
,
318 link
->ifindex
, tclass
->classid
, tclass
->parent
);
322 r
= sd_netlink_message_append_string(m
, TCA_KIND
, TCLASS_VTABLE(tclass
)->tca_kind
);
326 if (TCLASS_VTABLE(tclass
)->fill_message
) {
327 r
= TCLASS_VTABLE(tclass
)->fill_message(link
, tclass
, m
);
332 return request_call_netlink_async(link
->manager
->rtnl
, m
, req
);
335 static bool tclass_is_ready_to_configure(TClass
*tclass
, Link
*link
) {
339 if (!IN_SET(link
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
342 return link_find_qdisc(link
, tclass
->classid
, tclass
->parent
, tclass_get_tca_kind(tclass
), NULL
) >= 0;
345 static int tclass_process_request(Request
*req
, Link
*link
, TClass
*tclass
) {
352 if (!tclass_is_ready_to_configure(tclass
, link
))
355 r
= tclass_configure(tclass
, link
, req
);
357 return log_link_warning_errno(link
, r
, "Failed to configure TClass: %m");
359 tclass_enter_configuring(tclass
);
363 int link_request_tclass(Link
*link
, TClass
*tclass
) {
370 if (tclass_get(link
, tclass
, &existing
) < 0) {
371 _cleanup_(tclass_freep
) TClass
*tmp
= NULL
;
373 r
= tclass_dup(tclass
, &tmp
);
377 r
= tclass_add(link
, tmp
);
379 return log_link_warning_errno(link
, r
, "Failed to store TClass: %m");
381 existing
= TAKE_PTR(tmp
);
383 existing
->source
= tclass
->source
;
385 log_tclass_debug(existing
, link
, "Requesting");
386 r
= link_queue_request_safe(link
, REQUEST_TYPE_TC_CLASS
,
390 tclass_process_request
,
395 return log_link_warning_errno(link
, r
, "Failed to request TClass: %m");
399 tclass_enter_requesting(existing
);
403 int manager_rtnl_process_tclass(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
404 _cleanup_(tclass_freep
) TClass
*tmp
= NULL
;
405 TClass
*tclass
= NULL
;
414 if (sd_netlink_message_is_error(message
)) {
415 r
= sd_netlink_message_get_errno(message
);
417 log_message_warning_errno(message
, r
, "rtnl: failed to receive TClass message, ignoring");
422 r
= sd_netlink_message_get_type(message
, &type
);
424 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
426 } else if (!IN_SET(type
, RTM_NEWTCLASS
, RTM_DELTCLASS
)) {
427 log_warning("rtnl: received unexpected message type %u when processing TClass, ignoring.", type
);
431 r
= sd_rtnl_message_traffic_control_get_ifindex(message
, &ifindex
);
433 log_warning_errno(r
, "rtnl: could not get ifindex from message, ignoring: %m");
435 } else if (ifindex
<= 0) {
436 log_warning("rtnl: received TClass message with invalid ifindex %d, ignoring.", ifindex
);
440 if (link_get_by_index(m
, ifindex
, &link
) < 0) {
442 log_warning("rtnl: received TClass for link '%d' we don't know about, ignoring.", ifindex
);
446 r
= tclass_new(_TCLASS_KIND_INVALID
, &tmp
);
450 r
= sd_rtnl_message_traffic_control_get_handle(message
, &tmp
->classid
);
452 log_link_warning_errno(link
, r
, "rtnl: received TClass message without handle, ignoring: %m");
456 r
= sd_rtnl_message_traffic_control_get_parent(message
, &tmp
->parent
);
458 log_link_warning_errno(link
, r
, "rtnl: received TClass message without parent, ignoring: %m");
462 r
= sd_netlink_message_read_string_strdup(message
, TCA_KIND
, &tmp
->tca_kind
);
464 log_link_warning_errno(link
, r
, "rtnl: received TClass message without kind, ignoring: %m");
468 (void) tclass_get(link
, tmp
, &tclass
);
473 tclass_enter_configured(tclass
);
474 log_tclass_debug(tclass
, link
, "Received remembered");
476 tclass_enter_configured(tmp
);
477 log_tclass_debug(tmp
, link
, "Received new");
479 r
= tclass_add(link
, tmp
);
481 log_link_warning_errno(link
, r
, "Failed to remember TClass, ignoring: %m");
485 tclass
= TAKE_PTR(tmp
);
492 (void) tclass_drop(tclass
);
494 log_tclass_debug(tmp
, link
, "Kernel removed unknown");
499 assert_not_reached();
505 int link_enumerate_tclass(Link
*link
, uint32_t parent
) {
506 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*req
= NULL
;
510 assert(link
->manager
);
511 assert(link
->manager
->rtnl
);
513 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &req
, RTM_GETTCLASS
, link
->ifindex
, 0, parent
);
517 return manager_enumerate_internal(link
->manager
, link
->manager
->rtnl
, req
, manager_rtnl_process_tclass
);
520 static int tclass_section_verify(TClass
*tclass
) {
525 if (section_is_invalid(tclass
->section
))
528 if (TCLASS_VTABLE(tclass
)->verify
) {
529 r
= TCLASS_VTABLE(tclass
)->verify(tclass
);
537 void network_drop_invalid_tclass(Network
*network
) {
542 HASHMAP_FOREACH(tclass
, network
->tclasses_by_section
)
543 if (tclass_section_verify(tclass
) < 0)
547 int config_parse_tclass_parent(
549 const char *filename
,
552 unsigned section_line
,
559 _cleanup_(tclass_free_or_set_invalidp
) TClass
*tclass
= NULL
;
560 Network
*network
= ASSERT_PTR(data
);
567 r
= tclass_new_static(ltype
, network
, filename
, section_line
, &tclass
);
571 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
572 "Failed to create traffic control class, ignoring assignment: %m");
576 if (streq(rvalue
, "root"))
577 tclass
->parent
= TC_H_ROOT
;
579 r
= parse_handle(rvalue
, &tclass
->parent
);
581 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
582 "Failed to parse 'Parent=', ignoring assignment: %s",
593 int config_parse_tclass_classid(
595 const char *filename
,
598 unsigned section_line
,
605 _cleanup_(tclass_free_or_set_invalidp
) TClass
*tclass
= NULL
;
606 Network
*network
= ASSERT_PTR(data
);
613 r
= tclass_new_static(ltype
, network
, filename
, section_line
, &tclass
);
617 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
618 "Failed to create traffic control class, ignoring assignment: %m");
622 if (isempty(rvalue
)) {
623 tclass
->classid
= TC_H_UNSPEC
;
628 r
= parse_handle(rvalue
, &tclass
->classid
);
630 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
631 "Failed to parse 'ClassId=', ignoring assignment: %s",