link_dns_settings_clear(link);
link->routes = set_free(link->routes);
-
link->nexthops = set_free(link->nexthops);
-
link->neighbors = set_free(link->neighbors);
-
link->addresses = set_free(link->addresses);
+ link->traffic_control = set_free(link->traffic_control);
link->dhcp_pd_prefixes = set_free(link->dhcp_pd_prefixes);
Set *neighbors;
Set *routes;
Set *nexthops;
+ Set *traffic_control;
sd_dhcp_client *dhcp_client;
sd_dhcp_lease *dhcp_lease;
#include "ordered-set.h"
#include "path-lookup.h"
#include "path-util.h"
+#include "qdisc.h"
#include "selinux-util.h"
#include "set.h"
#include "signal-util.h"
#include "stat-util.h"
#include "strv.h"
#include "sysctl-util.h"
+#include "tclass.h"
#include "tmpfile-util.h"
/* use 128 MB for receive socket kernel queue. */
if (r < 0)
return r;
+ r = netlink_add_match(m->rtnl, NULL, RTM_NEWQDISC, &manager_rtnl_process_qdisc, NULL, m, "network-rtnl_process_qdisc");
+ if (r < 0)
+ return r;
+
+ r = netlink_add_match(m->rtnl, NULL, RTM_DELQDISC, &manager_rtnl_process_qdisc, NULL, m, "network-rtnl_process_qdisc");
+ if (r < 0)
+ return r;
+
+ r = netlink_add_match(m->rtnl, NULL, RTM_NEWTCLASS, &manager_rtnl_process_tclass, NULL, m, "network-rtnl_process_tclass");
+ if (r < 0)
+ return r;
+
+ r = netlink_add_match(m->rtnl, NULL, RTM_DELTCLASS, &manager_rtnl_process_tclass, NULL, m, "network-rtnl_process_tclass");
+ if (r < 0)
+ return r;
+
r = netlink_add_match(m->rtnl, NULL, RTM_NEWADDR, &manager_rtnl_process_address, NULL, m, "network-rtnl_process_address");
if (r < 0)
return r;
return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link);
}
+static int manager_enumerate_qdisc(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_traffic_control(m->rtnl, &req, RTM_GETQDISC, 0, 0, 0);
+ if (r < 0)
+ return r;
+
+ return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_qdisc);
+}
+
+static int manager_enumerate_tclass(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_traffic_control(m->rtnl, &req, RTM_GETTCLASS, 0, 0, 0);
+ if (r < 0)
+ return r;
+
+ return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_tclass);
+}
+
static int manager_enumerate_addresses(Manager *m) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
if (r < 0)
return log_error_errno(r, "Could not enumerate links: %m");
+ r = manager_enumerate_qdisc(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate QDisc: %m");
+
+ r = manager_enumerate_tclass(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate TClass: %m");
+
r = manager_enumerate_addresses(m);
if (r < 0)
return log_error_errno(r, "Could not enumerate addresses: %m");
.kind = kind,
};
} else {
+ assert(kind >= 0 && kind < _QDISC_KIND_MAX);
qdisc = malloc0(qdisc_vtable[kind]->object_size);
if (!qdisc)
return -ENOMEM;
config_section_free(qdisc->section);
+ if (qdisc->link)
+ set_remove(qdisc->link->traffic_control, TC(qdisc));
+
free(qdisc->tca_kind);
return mfree(qdisc);
}
+static const char *qdisc_get_tca_kind(const QDisc *qdisc) {
+ assert(qdisc);
+
+ return (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->tca_kind) ?
+ QDISC_VTABLE(qdisc)->tca_kind : qdisc->tca_kind;
+}
+
+void qdisc_hash_func(const QDisc *qdisc, struct siphash *state) {
+ assert(qdisc);
+ assert(state);
+
+ siphash24_compress(&qdisc->handle, sizeof(qdisc->handle), state);
+ siphash24_compress(&qdisc->parent, sizeof(qdisc->parent), state);
+ siphash24_compress_string(qdisc_get_tca_kind(qdisc), state);
+}
+
+int qdisc_compare_func(const QDisc *a, const QDisc *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(a->handle, b->handle);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->parent, b->parent);
+ if (r != 0)
+ return r;
+
+ return strcmp_ptr(qdisc_get_tca_kind(a), qdisc_get_tca_kind(b));
+}
+
+static int qdisc_get(Link *link, const QDisc *in, QDisc **ret) {
+ TrafficControl *existing;
+ int r;
+
+ assert(link);
+ assert(in);
+
+ r = traffic_control_get(link, TC(in), &existing);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = TC_TO_QDISC(existing);
+ return 0;
+}
+
+static int qdisc_add(Link *link, QDisc *qdisc) {
+ int r;
+
+ assert(link);
+ assert(qdisc);
+
+ r = traffic_control_add(link, TC(qdisc));
+ if (r < 0)
+ return r;
+
+ qdisc->link = link;
+ return 0;
+}
+
+static void log_qdisc_debug(QDisc *qdisc, Link *link, const char *str) {
+ _cleanup_free_ char *state = NULL;
+
+ assert(qdisc);
+ assert(str);
+
+ if (!DEBUG_LOGGING)
+ return;
+
+ (void) network_config_state_to_string_alloc(qdisc->state, &state);
+
+ log_link_debug(link, "%s %s QDisc (%s): handle=%"PRIx32":%"PRIx32", parent=%"PRIx32":%"PRIx32", kind=%s",
+ str, strna(network_config_source_to_string(qdisc->source)), strna(state),
+ TC_H_MAJ(qdisc->handle) >> 16, TC_H_MIN(qdisc->handle),
+ TC_H_MAJ(qdisc->parent) >> 16, TC_H_MIN(qdisc->parent),
+ strna(qdisc_get_tca_kind(qdisc)));
+}
+
static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
return 0;
}
+int manager_rtnl_process_qdisc(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
+ _cleanup_(qdisc_freep) QDisc *tmp = NULL;
+ QDisc *qdisc = NULL;
+ Link *link;
+ uint16_t type;
+ int ifindex, r;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_message_warning_errno(message, r, "rtnl: failed to receive QDisc message, ignoring");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWQDISC, RTM_DELQDISC)) {
+ log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type);
+ return 0;
+ }
+
+ r = sd_rtnl_message_traffic_control_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex);
+ return 0;
+ }
+
+ if (link_get_by_index(m, ifindex, &link) < 0) {
+ if (!m->enumerating)
+ log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex);
+ return 0;
+ }
+
+ r = qdisc_new(_QDISC_KIND_INVALID, &tmp);
+ if (r < 0)
+ return log_oom();
+
+ r = sd_rtnl_message_traffic_control_get_handle(message, &tmp->handle);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received QDisc message without handle, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_traffic_control_get_parent(message, &tmp->parent);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received QDisc message without parent, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string_strdup(message, TCA_KIND, &tmp->tca_kind);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received QDisc message without kind, ignoring: %m");
+ return 0;
+ }
+
+ (void) qdisc_get(link, tmp, &qdisc);
+
+ switch (type) {
+ case RTM_NEWQDISC:
+ if (qdisc) {
+ qdisc_enter_configured(qdisc);
+ log_qdisc_debug(qdisc, link, "Received remembered");
+ } else {
+ qdisc_enter_configured(tmp);
+ log_qdisc_debug(tmp, link, "Received new");
+
+ r = qdisc_add(link, tmp);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to remember QDisc, ignoring: %m");
+ return 0;
+ }
+
+ qdisc = TAKE_PTR(tmp);
+ }
+
+ break;
+
+ case RTM_DELQDISC:
+ if (qdisc) {
+ qdisc_enter_removed(qdisc);
+ if (qdisc->state == 0) {
+ log_qdisc_debug(qdisc, link, "Forgetting");
+ qdisc_free(qdisc);
+ } else
+ log_qdisc_debug(qdisc, link, "Removed");
+ } else
+ log_qdisc_debug(tmp, link, "Kernel removed unknown");
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
int r;
typedef struct QDisc {
TrafficControl meta;
- ConfigSection *section;
+ Link *link;
Network *network;
+ ConfigSection *section;
+ NetworkConfigSource source;
+ NetworkConfigState state;
uint32_t handle;
uint32_t parent;
/* For casting the various qdisc kinds into a qdisc */
#define QDISC(q) (&(q)->meta)
+DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(QDisc, qdisc);
+
QDisc* qdisc_free(QDisc *qdisc);
int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret);
+void qdisc_hash_func(const QDisc *qdic, struct siphash *state);
+int qdisc_compare_func(const QDisc *a, const QDisc *b);
+
int qdisc_configure(Link *link, QDisc *qdisc);
int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact);
+int manager_rtnl_process_qdisc(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
+
DEFINE_SECTION_CLEANUP_FUNCTIONS(QDisc, qdisc_free);
DEFINE_TC_CAST(QDISC, QDisc);
}
}
+void traffic_control_hash_func(const TrafficControl *tc, struct siphash *state) {
+ assert(tc);
+ assert(state);
+
+ siphash24_compress(&tc->kind, sizeof(tc->kind), state);
+
+ switch (tc->kind) {
+ case TC_KIND_QDISC:
+ qdisc_hash_func(TC_TO_QDISC_CONST(tc), state);
+ break;
+ case TC_KIND_TCLASS:
+ tclass_hash_func(TC_TO_TCLASS_CONST(tc), state);
+ break;
+ default:
+ assert_not_reached();
+ }
+}
+
+int traffic_control_compare_func(const TrafficControl *a, const TrafficControl *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(a->kind, b->kind);
+ if (r != 0)
+ return r;
+
+ switch (a->kind) {
+ case TC_KIND_QDISC:
+ return qdisc_compare_func(TC_TO_QDISC_CONST(a), TC_TO_QDISC_CONST(b));
+ case TC_KIND_TCLASS:
+ return tclass_compare_func(TC_TO_TCLASS_CONST(a), TC_TO_TCLASS_CONST(b));
+ default:
+ assert_not_reached();
+ }
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ traffic_control_hash_ops,
+ TrafficControl,
+ traffic_control_hash_func,
+ traffic_control_compare_func,
+ traffic_control_free);
+
+int traffic_control_get(Link *link, const TrafficControl *in, TrafficControl **ret) {
+ TrafficControl *existing;
+
+ assert(link);
+ assert(in);
+
+ existing = set_get(link->traffic_control, in);
+ if (!existing)
+ return -ENOENT;
+
+ if (ret)
+ *ret = existing;
+ return 0;
+}
+
+int traffic_control_add(Link *link, TrafficControl *tc) {
+ int r;
+
+ assert(link);
+ assert(tc);
+
+ /* This must be called only from qdisc_add() or tclass_add(). */
+
+ r = set_ensure_put(&link->traffic_control, &traffic_control_hash_ops, tc);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EEXIST;
+
+ return 0;
+}
+
static int traffic_control_configure(Link *link, TrafficControl *tc) {
assert(link);
assert(tc);
} TrafficControl;
/* For casting a tc into the various tc kinds */
-#define DEFINE_TC_CAST(UPPERCASE, MixedCase) \
- static inline MixedCase* TC_TO_##UPPERCASE(TrafficControl *tc) { \
- if (_unlikely_(!tc || tc->kind != TC_KIND_##UPPERCASE)) \
- return NULL; \
- \
- return (MixedCase*) tc; \
+#define DEFINE_TC_CAST(UPPERCASE, MixedCase) \
+ static inline MixedCase* TC_TO_##UPPERCASE(TrafficControl *tc) { \
+ if (_unlikely_(!tc || tc->kind != TC_KIND_##UPPERCASE)) \
+ return NULL; \
+ \
+ return (MixedCase*) tc; \
+ } \
+ static inline const MixedCase* TC_TO_##UPPERCASE##_CONST(const TrafficControl *tc) { \
+ if (_unlikely_(!tc || tc->kind != TC_KIND_##UPPERCASE)) \
+ return NULL; \
+ \
+ return (const MixedCase*) tc; \
}
/* For casting the various tc kinds into a tc */
void traffic_control_free(TrafficControl *tc);
int link_configure_traffic_control(Link *link);
void network_drop_invalid_traffic_control(Network *network);
+
+void traffic_control_hash_func(const TrafficControl *tc, struct siphash *state);
+int traffic_control_compare_func(const TrafficControl *a, const TrafficControl *b);
+
+int traffic_control_get(Link *link, const TrafficControl *in, TrafficControl **ret);
+int traffic_control_add(Link *link, TrafficControl *tc);
_cleanup_(tclass_freep) TClass *tclass = NULL;
int r;
- tclass = malloc0(tclass_vtable[kind]->object_size);
- if (!tclass)
- return -ENOMEM;
-
- tclass->meta.kind = TC_KIND_TCLASS,
- tclass->parent = TC_H_ROOT;
- tclass->kind = kind;
+ if (kind == _TCLASS_KIND_INVALID) {
+ tclass = new(TClass, 1);
+ if (!tclass)
+ return -ENOMEM;
+
+ *tclass = (TClass) {
+ .meta.kind = TC_KIND_TCLASS,
+ .parent = TC_H_ROOT,
+ .kind = kind,
+ };
+ } else {
+ assert(kind >= 0 && kind < _TCLASS_KIND_MAX);
+ tclass = malloc0(tclass_vtable[kind]->object_size);
+ if (!tclass)
+ return -ENOMEM;
+
+ tclass->meta.kind = TC_KIND_TCLASS;
+ tclass->parent = TC_H_ROOT;
+ tclass->kind = kind;
- if (TCLASS_VTABLE(tclass)->init) {
- r = TCLASS_VTABLE(tclass)->init(tclass);
- if (r < 0)
- return r;
+ if (TCLASS_VTABLE(tclass)->init) {
+ r = TCLASS_VTABLE(tclass)->init(tclass);
+ if (r < 0)
+ return r;
+ }
}
*ret = TAKE_PTR(tclass);
config_section_free(tclass->section);
+ if (tclass->link)
+ set_remove(tclass->link->traffic_control, TC(tclass));
+
+ free(tclass->tca_kind);
return mfree(tclass);
}
+static const char *tclass_get_tca_kind(const TClass *tclass) {
+ assert(tclass);
+
+ return (TCLASS_VTABLE(tclass) && TCLASS_VTABLE(tclass)->tca_kind) ?
+ TCLASS_VTABLE(tclass)->tca_kind : tclass->tca_kind;
+}
+
+void tclass_hash_func(const TClass *tclass, struct siphash *state) {
+ assert(tclass);
+ assert(state);
+
+ siphash24_compress(&tclass->classid, sizeof(tclass->classid), state);
+ siphash24_compress(&tclass->parent, sizeof(tclass->parent), state);
+ siphash24_compress_string(tclass_get_tca_kind(tclass), state);
+}
+
+int tclass_compare_func(const TClass *a, const TClass *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(a->classid, b->classid);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->parent, b->parent);
+ if (r != 0)
+ return r;
+
+ return strcmp_ptr(tclass_get_tca_kind(a), tclass_get_tca_kind(b));
+}
+
+static int tclass_get(Link *link, const TClass *in, TClass **ret) {
+ TrafficControl *existing;
+ int r;
+
+ assert(link);
+ assert(in);
+
+ r = traffic_control_get(link, TC(in), &existing);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = TC_TO_TCLASS(existing);
+ return 0;
+}
+
+static int tclass_add(Link *link, TClass *tclass) {
+ int r;
+
+ assert(link);
+ assert(tclass);
+
+ r = traffic_control_add(link, TC(tclass));
+ if (r < 0)
+ return r;
+
+ tclass->link = link;
+ return 0;
+}
+
+static void log_tclass_debug(TClass *tclass, Link *link, const char *str) {
+ _cleanup_free_ char *state = NULL;
+
+ assert(tclass);
+ assert(str);
+
+ if (!DEBUG_LOGGING)
+ return;
+
+ (void) network_config_state_to_string_alloc(tclass->state, &state);
+
+ log_link_debug(link, "%s %s TClass (%s): classid=%"PRIx32":%"PRIx32", parent=%"PRIx32":%"PRIx32", kind=%s",
+ str, strna(network_config_source_to_string(tclass->source)), strna(state),
+ TC_H_MAJ(tclass->classid) >> 16, TC_H_MIN(tclass->classid),
+ TC_H_MAJ(tclass->parent) >> 16, TC_H_MIN(tclass->parent),
+ strna(tclass_get_tca_kind(tclass)));
+}
+
static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
return 0;
}
+int manager_rtnl_process_tclass(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
+ _cleanup_(tclass_freep) TClass *tmp = NULL;
+ TClass *tclass = NULL;
+ Link *link;
+ uint16_t type;
+ int ifindex, r;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_message_warning_errno(message, r, "rtnl: failed to receive TClass message, ignoring");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWTCLASS, RTM_DELTCLASS)) {
+ log_warning("rtnl: received unexpected message type %u when processing TClass, ignoring.", type);
+ return 0;
+ }
+
+ r = sd_rtnl_message_traffic_control_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received TClass message with invalid ifindex %d, ignoring.", ifindex);
+ return 0;
+ }
+
+ if (link_get_by_index(m, ifindex, &link) < 0) {
+ if (!m->enumerating)
+ log_warning("rtnl: received TClass for link '%d' we don't know about, ignoring.", ifindex);
+ return 0;
+ }
+
+ r = tclass_new(_TCLASS_KIND_INVALID, &tmp);
+ if (r < 0)
+ return log_oom();
+
+ r = sd_rtnl_message_traffic_control_get_handle(message, &tmp->classid);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received TClass message without handle, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_traffic_control_get_parent(message, &tmp->parent);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received TClass message without parent, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string_strdup(message, TCA_KIND, &tmp->tca_kind);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received TClass message without kind, ignoring: %m");
+ return 0;
+ }
+
+ (void) tclass_get(link, tmp, &tclass);
+
+ switch (type) {
+ case RTM_NEWTCLASS:
+ if (tclass) {
+ tclass_enter_configured(tclass);
+ log_tclass_debug(tclass, link, "Received remembered");
+ } else {
+ tclass_enter_configured(tmp);
+ log_tclass_debug(tmp, link, "Received new");
+
+ r = tclass_add(link, tmp);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to remember TClass, ignoring: %m");
+ return 0;
+ }
+
+ tclass = TAKE_PTR(tmp);
+ }
+
+ break;
+
+ case RTM_DELTCLASS:
+ if (tclass) {
+ tclass_enter_removed(tclass);
+ if (tclass->state == 0) {
+ log_tclass_debug(tclass, link, "Forgetting");
+ tclass_free(tclass);
+ } else
+ log_tclass_debug(tclass, link, "Removed");
+ } else
+ log_tclass_debug(tmp, link, "Kernel removed unknown");
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
int tclass_section_verify(TClass *tclass) {
int r;
typedef struct TClass {
TrafficControl meta;
- ConfigSection *section;
+ Link *link;
Network *network;
+ ConfigSection *section;
+ NetworkConfigSource source;
+ NetworkConfigState state;
uint32_t classid;
uint32_t parent;
TClassKind kind;
+ char *tca_kind;
} TClass;
typedef struct TClassVTable {
/* For casting the various tclass kinds into a tclass */
#define TCLASS(t) (&(t)->meta)
+DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(TClass, tclass);
+
TClass* tclass_free(TClass *tclass);
int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret);
+void tclass_hash_func(const TClass *tclass, struct siphash *state);
+int tclass_compare_func(const TClass *a, const TClass *b);
+
int tclass_configure(Link *link, TClass *tclass);
int tclass_section_verify(TClass *tclass);
+int manager_rtnl_process_tclass(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
+
DEFINE_SECTION_CLEANUP_FUNCTIONS(TClass, tclass_free);
DEFINE_TC_CAST(TCLASS, TClass);