summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/networkd-queue.c')
-rw-r--r--src/network/networkd-queue.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c
new file mode 100644
index 0000000..c7a26c7
--- /dev/null
+++ b/src/network/networkd-queue.c
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "netdev.h"
+#include "netlink-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-queue.h"
+#include "string-table.h"
+
+static Request *request_free(Request *req) {
+ if (!req)
+ return NULL;
+
+ /* To prevent from triggering assertions in the hash and compare functions, remove this request
+ * before freeing userdata below. */
+ if (req->manager)
+ ordered_set_remove(req->manager->request_queue, req);
+
+ if (req->free_func)
+ req->free_func(req->userdata);
+
+ if (req->counter)
+ (*req->counter)--;
+
+ link_unref(req->link); /* link may be NULL, but link_unref() can handle it gracefully. */
+
+ return mfree(req);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free);
+DEFINE_TRIVIAL_DESTRUCTOR(request_destroy_callback, Request, request_unref);
+
+void request_detach(Manager *manager, Request *req) {
+ assert(manager);
+
+ if (!req)
+ return;
+
+ req = ordered_set_remove(manager->request_queue, req);
+ if (!req)
+ return;
+
+ req->manager = NULL;
+ request_unref(req);
+}
+
+static void request_hash_func(const Request *req, struct siphash *state) {
+ assert(req);
+ assert(state);
+
+ siphash24_compress_boolean(req->link, state);
+ if (req->link)
+ siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state);
+
+ siphash24_compress(&req->type, sizeof(req->type), state);
+
+ siphash24_compress(&req->hash_func, sizeof(req->hash_func), state);
+ siphash24_compress(&req->compare_func, sizeof(req->compare_func), state);
+
+ if (req->hash_func)
+ req->hash_func(req->userdata, state);
+}
+
+static int request_compare_func(const struct Request *a, const struct Request *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(!!a->link, !!b->link);
+ if (r != 0)
+ return r;
+
+ if (a->link) {
+ r = CMP(a->link->ifindex, b->link->ifindex);
+ if (r != 0)
+ return r;
+ }
+
+ r = CMP(a->type, b->type);
+ if (r != 0)
+ return r;
+
+ r = CMP(PTR_TO_UINT64(a->hash_func), PTR_TO_UINT64(b->hash_func));
+ if (r != 0)
+ return r;
+
+ r = CMP(PTR_TO_UINT64(a->compare_func), PTR_TO_UINT64(b->compare_func));
+ if (r != 0)
+ return r;
+
+ if (a->compare_func)
+ return a->compare_func(a->userdata, b->userdata);
+
+ return 0;
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+ request_hash_ops,
+ Request,
+ request_hash_func,
+ request_compare_func,
+ request_unref);
+
+static int request_new(
+ Manager *manager,
+ Link *link,
+ RequestType type,
+ void *userdata,
+ mfree_func_t free_func,
+ hash_func_t hash_func,
+ compare_func_t compare_func,
+ request_process_func_t process,
+ unsigned *counter,
+ request_netlink_handler_t netlink_handler,
+ Request **ret) {
+
+ _cleanup_(request_unrefp) Request *req = NULL;
+ Request *existing;
+ int r;
+
+ assert(manager);
+ assert(process);
+
+ req = new(Request, 1);
+ if (!req)
+ return -ENOMEM;
+
+ *req = (Request) {
+ .n_ref = 1,
+ .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */
+ .type = type,
+ .userdata = userdata,
+ .free_func = free_func,
+ .hash_func = hash_func,
+ .compare_func = compare_func,
+ .process = process,
+ .netlink_handler = netlink_handler,
+ };
+
+ existing = ordered_set_get(manager->request_queue, req);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
+
+ r = ordered_set_ensure_put(&manager->request_queue, &request_hash_ops, req);
+ if (r < 0)
+ return r;
+
+ req->manager = manager;
+ req->counter = counter;
+ if (req->counter)
+ (*req->counter)++;
+
+ if (ret)
+ *ret = req;
+
+ TAKE_PTR(req);
+ return 1;
+}
+
+int netdev_queue_request(
+ NetDev *netdev,
+ request_process_func_t process,
+ Request **ret) {
+
+ int r;
+
+ assert(netdev);
+
+ r = request_new(netdev->manager, NULL, REQUEST_TYPE_NETDEV_INDEPENDENT,
+ netdev, (mfree_func_t) netdev_unref,
+ trivial_hash_func, trivial_compare_func,
+ process, NULL, NULL, ret);
+ if (r <= 0)
+ return r;
+
+ netdev_ref(netdev);
+ return 1;
+}
+
+int link_queue_request_full(
+ Link *link,
+ RequestType type,
+ void *userdata,
+ mfree_func_t free_func,
+ hash_func_t hash_func,
+ compare_func_t compare_func,
+ request_process_func_t process,
+ unsigned *counter,
+ request_netlink_handler_t netlink_handler,
+ Request **ret) {
+
+ assert(link);
+
+ return request_new(link->manager, link, type,
+ userdata, free_func, hash_func, compare_func,
+ process, counter, netlink_handler, ret);
+}
+
+int manager_process_requests(sd_event_source *s, void *userdata) {
+ Manager *manager = ASSERT_PTR(userdata);
+ int r;
+
+ for (;;) {
+ bool processed = false;
+ Request *req;
+
+ ORDERED_SET_FOREACH(req, manager->request_queue) {
+ _unused_ _cleanup_(request_unrefp) Request *ref = request_ref(req);
+ _cleanup_(link_unrefp) Link *link = link_ref(req->link);
+
+ assert(req->process);
+
+ r = req->process(req, link, req->userdata);
+ if (r == 0)
+ continue;
+
+ processed = true;
+ request_detach(manager, req);
+
+ if (r < 0 && link) {
+ link_enter_failed(link);
+ /* link_enter_failed() may remove multiple requests,
+ * hence we need to exit from the loop. */
+ break;
+ }
+ }
+
+ /* When at least one request is processed, then another request may be ready now. */
+ if (!processed)
+ break;
+ }
+
+ return 0;
+}
+
+static int request_netlink_handler(sd_netlink *nl, sd_netlink_message *m, Request *req) {
+ assert(req);
+
+ if (req->counter) {
+ assert(*req->counter > 0);
+ (*req->counter)--;
+ req->counter = NULL; /* To prevent double decrement on free. */
+ }
+
+ if (req->link && IN_SET(req->link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 0;
+
+ if (req->netlink_handler)
+ return req->netlink_handler(nl, m, req, req->link, req->userdata);
+
+ return 0;
+}
+
+int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req) {
+ int r;
+
+ assert(nl);
+ assert(m);
+ assert(req);
+
+ r = netlink_call_async(nl, NULL, m, request_netlink_handler, request_destroy_callback, req);
+ if (r < 0)
+ return r;
+
+ request_ref(req);
+ return 0;
+}
+
+static const char *const request_type_table[_REQUEST_TYPE_MAX] = {
+ [REQUEST_TYPE_ACTIVATE_LINK] = "activate link",
+ [REQUEST_TYPE_ADDRESS] = "address",
+ [REQUEST_TYPE_ADDRESS_LABEL] = "address label",
+ [REQUEST_TYPE_BRIDGE_FDB] = "bridge FDB",
+ [REQUEST_TYPE_BRIDGE_MDB] = "bridge MDB",
+ [REQUEST_TYPE_DHCP_SERVER] = "DHCP server",
+ [REQUEST_TYPE_DHCP4_CLIENT] = "DHCPv4 client",
+ [REQUEST_TYPE_DHCP6_CLIENT] = "DHCPv6 client",
+ [REQUEST_TYPE_IPV6_PROXY_NDP] = "IPv6 proxy NDP",
+ [REQUEST_TYPE_NDISC] = "NDisc",
+ [REQUEST_TYPE_NEIGHBOR] = "neighbor",
+ [REQUEST_TYPE_NETDEV_INDEPENDENT] = "independent netdev",
+ [REQUEST_TYPE_NETDEV_STACKED] = "stacked netdev",
+ [REQUEST_TYPE_NEXTHOP] = "nexthop",
+ [REQUEST_TYPE_RADV] = "RADV",
+ [REQUEST_TYPE_ROUTE] = "route",
+ [REQUEST_TYPE_ROUTING_POLICY_RULE] = "routing policy rule",
+ [REQUEST_TYPE_SET_LINK_ADDRESS_GENERATION_MODE] = "IPv6LL address generation mode",
+ [REQUEST_TYPE_SET_LINK_BOND] = "bond configurations",
+ [REQUEST_TYPE_SET_LINK_BRIDGE] = "bridge configurations",
+ [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations",
+ [REQUEST_TYPE_SET_LINK_CAN] = "CAN interface configurations",
+ [REQUEST_TYPE_SET_LINK_FLAGS] = "link flags",
+ [REQUEST_TYPE_SET_LINK_GROUP] = "interface group",
+ [REQUEST_TYPE_SET_LINK_IPOIB] = "IPoIB configurations",
+ [REQUEST_TYPE_SET_LINK_MAC] = "MAC address",
+ [REQUEST_TYPE_SET_LINK_MASTER] = "master interface",
+ [REQUEST_TYPE_SET_LINK_MTU] = "MTU",
+ [REQUEST_TYPE_SRIOV] = "SR-IOV",
+ [REQUEST_TYPE_TC_QDISC] = "QDisc",
+ [REQUEST_TYPE_TC_CLASS] = "TClass",
+ [REQUEST_TYPE_UP_DOWN] = "bring link up or down",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType);