mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
Then, we can find and use the Request object after sending netlink message. Preparation for later commits.
321 lines
11 KiB
C
321 lines
11 KiB
C
/* 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
|
|
* 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) {
|
|
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,
|
|
.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) {
|
|
|
|
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) {
|
|
_cleanup_(link_unrefp) Link *link = link_ref(req->link);
|
|
|
|
assert(req->process);
|
|
|
|
if (req->waiting_reply)
|
|
continue; /* Waiting for netlink reply. */
|
|
|
|
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);
|