| /* 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); |