/* 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) { if (free_func) free_func(userdata); 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) { assert(netdev); return request_new(netdev->manager, NULL, REQUEST_TYPE_NETDEV_INDEPENDENT, netdev_ref(netdev), (mfree_func_t) netdev_unref, trivial_hash_func, trivial_compare_func, process, NULL, NULL, ret); } 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);