]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/tc/qdisc.c
tree-wide: use ASSERT_PTR more
[thirdparty/systemd.git] / src / network / tc / qdisc.c
index 1a84e8b0f5c57e23f18ec170d2423875eca6d187..ca89169a866f59c40bbe7bc387be5cde24f8a9b7 100644 (file)
@@ -7,7 +7,10 @@
 #include "conf-parser.h"
 #include "in-addr-util.h"
 #include "netlink-util.h"
+#include "networkd-link.h"
 #include "networkd-manager.h"
+#include "networkd-network.h"
+#include "networkd-queue.h"
 #include "parse-util.h"
 #include "qdisc.h"
 #include "set.h"
@@ -49,18 +52,15 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) {
                         return -ENOMEM;
 
                 *qdisc = (QDisc) {
-                        .meta.kind = TC_KIND_QDISC,
-                        .family = AF_UNSPEC,
                         .parent = TC_H_ROOT,
                         .kind = kind,
                 };
         } else {
+                assert(kind >= 0 && kind < _QDISC_KIND_MAX);
                 qdisc = malloc0(qdisc_vtable[kind]->object_size);
                 if (!qdisc)
                         return -ENOMEM;
 
-                qdisc->meta.kind = TC_KIND_QDISC,
-                qdisc->family = AF_UNSPEC;
                 qdisc->parent = TC_H_ROOT;
                 qdisc->kind = kind;
 
@@ -77,10 +77,9 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) {
 }
 
 int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret) {
-        _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+        _cleanup_(config_section_freep) ConfigSection *n = NULL;
         _cleanup_(qdisc_freep) QDisc *qdisc = NULL;
-        TrafficControl *existing;
-        QDisc *q = NULL;
+        QDisc *existing;
         int r;
 
         assert(network);
@@ -88,24 +87,19 @@ int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, uns
         assert(filename);
         assert(section_line > 0);
 
-        r = network_config_section_new(filename, section_line, &n);
+        r = config_section_new(filename, section_line, &n);
         if (r < 0)
                 return r;
 
-        existing = ordered_hashmap_get(network->tc_by_section, n);
+        existing = hashmap_get(network->qdiscs_by_section, n);
         if (existing) {
-                if (existing->kind != TC_KIND_QDISC)
-                        return -EINVAL;
-
-                q = TC_TO_QDISC(existing);
-
-                if (q->kind != _QDISC_KIND_INVALID &&
+                if (existing->kind != _QDISC_KIND_INVALID &&
                     kind != _QDISC_KIND_INVALID &&
-                    q->kind != kind)
+                    existing->kind != kind)
                         return -EINVAL;
 
-                if (q->kind == kind || kind == _QDISC_KIND_INVALID) {
-                        *ret = q;
+                if (existing->kind == kind || kind == _QDISC_KIND_INVALID) {
+                        *ret = existing;
                         return 0;
                 }
         }
@@ -114,23 +108,19 @@ int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, uns
         if (r < 0)
                 return r;
 
-        if (q) {
-                qdisc->family = q->family;
-                qdisc->handle = q->handle;
-                qdisc->parent = q->parent;
-                qdisc->tca_kind = TAKE_PTR(q->tca_kind);
+        if (existing) {
+                qdisc->handle = existing->handle;
+                qdisc->parent = existing->parent;
+                qdisc->tca_kind = TAKE_PTR(existing->tca_kind);
 
-                qdisc_free(q);
+                qdisc_free(existing);
         }
 
         qdisc->network = network;
         qdisc->section = TAKE_PTR(n);
+        qdisc->source = NETWORK_CONFIG_SOURCE_STATIC;
 
-        r = ordered_hashmap_ensure_allocated(&network->tc_by_section, &network_config_hash_ops);
-        if (r < 0)
-                return r;
-
-        r = ordered_hashmap_put(network->tc_by_section, qdisc->section, TC(qdisc));
+        r = hashmap_ensure_put(&network->qdiscs_by_section, &config_section_hash_ops, qdisc->section, qdisc);
         if (r < 0)
                 return r;
 
@@ -138,28 +128,176 @@ int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, uns
         return 0;
 }
 
-void qdisc_free(QDisc *qdisc) {
+QDisc* qdisc_free(QDisc *qdisc) {
         if (!qdisc)
-                return;
+                return NULL;
 
         if (qdisc->network && qdisc->section)
-                ordered_hashmap_remove(qdisc->network->tc_by_section, qdisc->section);
+                hashmap_remove(qdisc->network->qdiscs_by_section, qdisc->section);
+
+        config_section_free(qdisc->section);
 
-        network_config_section_free(qdisc->section);
+        if (qdisc->link)
+                set_remove(qdisc->link->qdiscs, qdisc);
 
         free(qdisc->tca_kind);
-        free(qdisc);
+        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;
+}
+
+static 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);
+}
+
+static 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));
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        qdisc_hash_ops,
+        QDisc,
+        qdisc_hash_func,
+        qdisc_compare_func,
+        qdisc_free);
+
+static int qdisc_get(Link *link, const QDisc *in, QDisc **ret) {
+        QDisc *existing;
+
+        assert(link);
+        assert(in);
+
+        existing = set_get(link->qdiscs, in);
+        if (!existing)
+                return -ENOENT;
+
+        if (ret)
+                *ret = existing;
+        return 0;
 }
 
-static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int qdisc_add(Link *link, QDisc *qdisc) {
         int r;
 
         assert(link);
-        assert(link->tc_messages > 0);
-        link->tc_messages--;
+        assert(qdisc);
 
-        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return 1;
+        r = set_ensure_put(&link->qdiscs, &qdisc_hash_ops, qdisc);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EEXIST;
+
+        qdisc->link = link;
+        return 0;
+}
+
+static int qdisc_dup(const QDisc *src, QDisc **ret) {
+        _cleanup_(qdisc_freep) QDisc *dst = NULL;
+
+        assert(src);
+        assert(ret);
+
+        if (QDISC_VTABLE(src))
+                dst = memdup(src, QDISC_VTABLE(src)->object_size);
+        else
+                dst = newdup(QDisc, src, 1);
+        if (!dst)
+                return -ENOMEM;
+
+        /* clear all pointers */
+        dst->network = NULL;
+        dst->section = NULL;
+        dst->link = NULL;
+        dst->tca_kind = NULL;
+
+        if (src->tca_kind) {
+                dst->tca_kind = strdup(src->tca_kind);
+                if (!dst->tca_kind)
+                        return -ENOMEM;
+        }
+
+        *ret = TAKE_PTR(dst);
+        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)));
+}
+
+int link_find_qdisc(Link *link, uint32_t handle, uint32_t parent, const char *kind, QDisc **ret) {
+        QDisc *qdisc;
+
+        assert(link);
+
+        handle = TC_H_MAJ(handle);
+
+        SET_FOREACH(qdisc, link->qdiscs) {
+                if (qdisc->handle != handle)
+                        continue;
+
+                if (qdisc->parent != parent)
+                        continue;
+
+                if (qdisc->source == NETWORK_CONFIG_SOURCE_FOREIGN)
+                        continue;
+
+                if (!qdisc_exists(qdisc))
+                        continue;
+
+                if (kind && !streq_ptr(kind, qdisc_get_tca_kind(qdisc)))
+                        continue;
+
+                if (ret)
+                        *ret = qdisc;
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, QDisc *qdisc) {
+        int r;
+
+        assert(m);
+        assert(link);
 
         r = sd_netlink_message_get_errno(m);
         if (r < 0 && r != -EEXIST) {
@@ -177,62 +315,223 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
         return 1;
 }
 
-int qdisc_configure(Link *link, QDisc *qdisc) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+static int qdisc_configure(QDisc *qdisc, Link *link, Request *req) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
         int r;
 
+        assert(qdisc);
         assert(link);
         assert(link->manager);
         assert(link->manager->rtnl);
         assert(link->ifindex > 0);
+        assert(req);
 
-        r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex);
+        log_qdisc_debug(qdisc, link, "Configuring");
+
+        r = sd_rtnl_message_new_traffic_control(link->manager->rtnl, &m, RTM_NEWQDISC,
+                                                link->ifindex, qdisc->handle, qdisc->parent);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not create RTM_NEWQDISC message: %m");
+                return r;
 
-        r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent);
+        r = sd_netlink_message_append_string(m, TCA_KIND, qdisc_get_tca_kind(qdisc));
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not create tcm_parent message: %m");
+                return r;
 
-        if (qdisc->handle != TC_H_UNSPEC) {
-                r = sd_rtnl_message_set_qdisc_handle(req, qdisc->handle);
+        if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->fill_message) {
+                r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, m);
                 if (r < 0)
-                        return log_link_error_errno(link, r, "Could not set tcm_handle message: %m");
+                        return r;
         }
 
-        if (QDISC_VTABLE(qdisc)) {
-                if (QDISC_VTABLE(qdisc)->fill_tca_kind) {
-                        r = QDISC_VTABLE(qdisc)->fill_tca_kind(link, qdisc, req);
-                        if (r < 0)
-                                return r;
-                } else {
-                        r = sd_netlink_message_append_string(req, TCA_KIND, QDISC_VTABLE(qdisc)->tca_kind);
-                        if (r < 0)
-                                return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
-                }
+        return request_call_netlink_async(link->manager->rtnl, m, req);
+}
 
-                if (QDISC_VTABLE(qdisc)->fill_message) {
-                        r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req);
-                        if (r < 0)
-                                return r;
-                }
-        } else {
-                r = sd_netlink_message_append_string(req, TCA_KIND, qdisc->tca_kind);
+static bool qdisc_is_ready_to_configure(QDisc *qdisc, Link *link) {
+        assert(qdisc);
+        assert(link);
+
+        if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
+                return false;
+
+        /* TC_H_CLSACT == TC_H_INGRESS */
+        if (!IN_SET(qdisc->parent, TC_H_ROOT, TC_H_CLSACT) &&
+            link_find_tclass(link, qdisc->parent, NULL) < 0)
+                return false;
+
+        if (QDISC_VTABLE(qdisc) &&
+            QDISC_VTABLE(qdisc)->is_ready &&
+            QDISC_VTABLE(qdisc)->is_ready(qdisc, link) <= 0)
+                return false;
+
+        return true;
+}
+
+static int qdisc_process_request(Request *req, Link *link, QDisc *qdisc) {
+        int r;
+
+        assert(req);
+        assert(link);
+        assert(qdisc);
+
+        if (!qdisc_is_ready_to_configure(qdisc, link))
+                return 0;
+
+        r = qdisc_configure(qdisc, link, req);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to configure QDisc: %m");
+
+        qdisc_enter_configuring(qdisc);
+        return 1;
+}
+
+int link_request_qdisc(Link *link, QDisc *qdisc) {
+        QDisc *existing;
+        int r;
+
+        assert(link);
+        assert(qdisc);
+
+        if (qdisc_get(link, qdisc, &existing) < 0) {
+                _cleanup_(qdisc_freep) QDisc *tmp = NULL;
+
+                r = qdisc_dup(qdisc, &tmp);
+                if (r < 0)
+                        return log_oom();
+
+                r = qdisc_add(link, tmp);
+                if (r < 0)
+                        return log_link_warning_errno(link, r, "Failed to store QDisc: %m");
+
+                existing = TAKE_PTR(tmp);
+        } else
+                existing->source = qdisc->source;
+
+        log_qdisc_debug(existing, link, "Requesting");
+        r = link_queue_request_safe(link, REQUEST_TYPE_TC_QDISC,
+                                    existing, NULL,
+                                    qdisc_hash_func,
+                                    qdisc_compare_func,
+                                    qdisc_process_request,
+                                    &link->tc_messages,
+                                    qdisc_handler,
+                                    NULL);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to request QDisc: %m");
+        if (r == 0)
+                return 0;
+
+        qdisc_enter_requesting(existing);
+        return 1;
+}
+
+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)
-                        return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
+                        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;
         }
 
-        r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link);
+        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_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+                return log_oom();
 
-        link_ref(link);
-        link->tc_messages++;
+        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;
+        }
 
-        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) {
+static int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
         int r;
 
         assert(qdisc);
@@ -267,6 +566,17 @@ int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
         return 0;
 }
 
+void network_drop_invalid_qdisc(Network *network) {
+        bool has_root = false, has_clsact = false;
+        QDisc *qdisc;
+
+        assert(network);
+
+        HASHMAP_FOREACH(qdisc, network->qdiscs_by_section)
+                if (qdisc_section_verify(qdisc, &has_root, &has_clsact) < 0)
+                        qdisc_free(qdisc);
+}
+
 int config_parse_qdisc_parent(
                 const char *unit,
                 const char *filename,
@@ -280,13 +590,12 @@ int config_parse_qdisc_parent(
                 void *userdata) {
 
         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
-        Network *network = data;
+        Network *network = ASSERT_PTR(data);
         int r;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
-        assert(data);
 
         r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
         if (r == -ENOMEM)
@@ -297,11 +606,9 @@ int config_parse_qdisc_parent(
                 return 0;
         }
 
-        if (streq(rvalue, "root")) {
+        if (streq(rvalue, "root"))
                 qdisc->parent = TC_H_ROOT;
-                if (qdisc->handle == 0)
-                        qdisc->handle = TC_H_UNSPEC;
-        } else if (streq(rvalue, "clsact")) {
+        else if (streq(rvalue, "clsact")) {
                 qdisc->parent = TC_H_CLSACT;
                 qdisc->handle = TC_H_MAKE(TC_H_CLSACT, 0);
         } else if (streq(rvalue, "ingress")) {
@@ -342,14 +649,13 @@ int config_parse_qdisc_handle(
                 void *userdata) {
 
         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
-        Network *network = data;
+        Network *network = ASSERT_PTR(data);
         uint16_t n;
         int r;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
-        assert(data);
 
         r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
         if (r == -ENOMEM)