diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 8933fc4977..6f3d90d64c 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -424,6 +424,7 @@ static int manager_connect_rtnl(Manager *m, int fd) { static int manager_post_handler(sd_event_source *s, void *userdata) { Manager *manager = ASSERT_PTR(userdata); + (void) manager_process_remove_requests(manager); (void) manager_process_requests(manager); (void) manager_clean_all(manager); return 0; @@ -594,6 +595,7 @@ Manager* manager_free(Manager *m) { (void) link_stop_engines(link, true); m->request_queue = ordered_set_free(m->request_queue); + m->remove_request_queue = ordered_set_free(m->remove_request_queue); m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref); m->new_wlan_ifindices = set_free(m->new_wlan_ifindices); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index a2edfd0e79..a97ae8ea21 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -102,6 +102,7 @@ struct Manager { bool request_queued; OrderedSet *request_queue; + OrderedSet *remove_request_queue; Hashmap *tuntap_fds_by_name; }; diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 1678510d52..0b89ec9736 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -224,6 +224,10 @@ int manager_process_requests(Manager *manager) { assert(manager); + /* Process only when no remove request is queued. */ + if (!ordered_set_isempty(manager->remove_request_queue)) + return 0; + manager->request_queued = false; ORDERED_SET_FOREACH(req, manager->request_queue) { @@ -339,3 +343,110 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType); + +static RemoveRequest* remove_request_free(RemoveRequest *req) { + if (!req) + return NULL; + + if (req->manager) + ordered_set_remove(req->manager->remove_request_queue, req); + + if (req->unref_func) + req->unref_func(req->userdata); + + link_unref(req->link); + sd_netlink_unref(req->netlink); + sd_netlink_message_unref(req->message); + + return mfree(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free); +DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free); +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + remove_request_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + remove_request_free); + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler) { + + _cleanup_(remove_request_freep) RemoveRequest *req = NULL; + int r; + + assert(manager); + assert(userdata); + assert(netlink); + assert(message); + + req = new(RemoveRequest, 1); + if (!req) + return -ENOMEM; + + *req = (RemoveRequest) { + .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */ + .userdata = userdata, + .netlink = sd_netlink_ref(netlink), + .message = sd_netlink_message_ref(message), + .netlink_handler = netlink_handler, + }; + + r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req); + if (r < 0) + return r; + assert(r > 0); + + req->manager = manager; + req->unref_func = unref_func; + + TAKE_PTR(req); + return 0; +} + +int manager_process_remove_requests(Manager *manager) { + RemoveRequest *req; + int r; + + assert(manager); + + while ((req = ordered_set_first(manager->remove_request_queue))) { + + /* Do not make the reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD) + return 0; + + r = netlink_call_async( + req->netlink, NULL, req->message, + req->netlink_handler, + remove_request_destroy_callback, + req); + if (r < 0) { + _cleanup_(link_unrefp) Link *link = link_ref(req->link); + + log_link_warning_errno(link, r, "Failed to call netlink message: %m"); + + /* First free the request. */ + remove_request_free(req); + + /* Then, make the link enter the failed state. */ + if (link) + link_enter_failed(link); + + } else { + /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove + * request may not freed on shutting down. */ + req->netlink = sd_netlink_unref(req->netlink); + ordered_set_remove(manager->remove_request_queue, req); + } + } + + return 0; +} diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index 21fa7d9453..e911fdd33a 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -139,3 +139,52 @@ int manager_process_requests(Manager *manager); int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req); const char* request_type_to_string(RequestType t) _const_; + +typedef struct RemoveRequest RemoveRequest; +typedef int (*remove_request_netlink_handler_t)(sd_netlink *nl, sd_netlink_message *m, RemoveRequest *req); + +struct RemoveRequest { + Manager *manager; + Link *link; + void *userdata; /* e.g. Address */ + mfree_func_t unref_func; /* e.g. address_unref() */ + sd_netlink *netlink; + sd_netlink_message *message; + remove_request_netlink_handler_t netlink_handler; +}; + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, /* This is unref()ed when the call failed. */ + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler); + +#define _remove_request_add(manager, link, data, name, nl, m, handler) \ + ({ \ + typeof(*data) *_data = (data); \ + int _r; \ + \ + _r = remove_request_add(manager, link, _data, \ + (mfree_func_t) name##_unref, \ + nl, m, handler); \ + if (_r >= 0) \ + name##_ref(_data); \ + _r; \ + }) + + +#define link_remove_request_add(link, data, name, nl, m, handler) \ + ({ \ + Link *_link = (link); \ + \ + _remove_request_add(_link->manager, _link, data, name, \ + nl, m, handler); \ + }) + +#define manager_remove_request_add(manager, data, name, nl, m, handler) \ + _remove_request_add(manager, NULL, data, name, nl, m, handler) + +int manager_process_remove_requests(Manager *manager);