blob: cf2ffa0327eaae13e9aa4f87cb68c6fcee7f83c2 [file] [log] [blame]
/* 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);