diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/network/networkd-queue.c | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/network/networkd-queue.c')
-rw-r--r-- | src/network/networkd-queue.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c new file mode 100644 index 0000000..1128987 --- /dev/null +++ b/src/network/networkd-queue.c @@ -0,0 +1,333 @@ +/* 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" + +#define REPLY_CALLBACK_COUNT_THRESHOLD 128 + +static Request *request_free(Request *req) { + if (!req) + return NULL; + + /* To prevent from triggering assertions in the hash and compare functions, remove this request + * from the set 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); + +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_destroy_callback(Request *req) { + assert(req); + + if (req->manager) + request_detach(req->manager, req); + + 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, + .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->free_func = free_func; + 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) { + _cleanup_(link_unrefp) Link *link = link_ref(req->link); + + assert(req->process); + + if (req->waiting_reply) + continue; /* Waiting for netlink reply. */ + + /* Typically, requests send netlink message asynchronously. If there are many requests + * queued, then this event may make reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) + return 0; + + r = req->process(req, link, req->userdata); + if (r == 0) + continue; + + processed = true; + + /* If the request sends netlink message, e.g. for Address or so, the Request object + * is referenced by the netlink slot, and will be detached later by its destroy callback. + * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */ + if (!req->waiting_reply) + 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); + req->waiting_reply = true; + 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); |