]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-queue.c
Merge pull request #31777 from keszybz/unit-retitling-and-comments
[thirdparty/systemd.git] / src / network / networkd-queue.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "netdev.h"
4 #include "netlink-util.h"
5 #include "networkd-link.h"
6 #include "networkd-manager.h"
7 #include "networkd-queue.h"
8 #include "string-table.h"
9
10 #define REPLY_CALLBACK_COUNT_THRESHOLD 128
11
12 static Request* request_detach_impl(Request *req) {
13 assert(req);
14
15 if (!req->manager)
16 return NULL;
17
18 ordered_set_remove(req->manager->request_queue, req);
19 req->manager = NULL;
20 return req;
21 }
22
23 void request_detach(Request *req) {
24 request_unref(request_detach_impl(req));
25 }
26
27 static Request *request_free(Request *req) {
28 if (!req)
29 return NULL;
30
31 /* To prevent from triggering assertions in the hash and compare functions, remove this request
32 * from the set before freeing userdata below. */
33 request_detach_impl(req);
34
35 if (req->free_func)
36 req->free_func(req->userdata);
37
38 if (req->counter)
39 (*req->counter)--;
40
41 link_unref(req->link); /* link may be NULL, but link_unref() can handle it gracefully. */
42
43 return mfree(req);
44 }
45
46 DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free);
47
48 static void request_destroy_callback(Request *req) {
49 assert(req);
50
51 request_detach(req);
52 request_unref(req);
53 }
54
55 static void request_hash_func(const Request *req, struct siphash *state) {
56 assert(req);
57 assert(state);
58
59 siphash24_compress_typesafe(req->type, state);
60
61 if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
62 siphash24_compress_boolean(req->link, state);
63 if (req->link)
64 siphash24_compress_typesafe(req->link->ifindex, state);
65 }
66
67 siphash24_compress_typesafe(req->hash_func, state);
68 siphash24_compress_typesafe(req->compare_func, state);
69
70 if (req->hash_func)
71 req->hash_func(req->userdata, state);
72 }
73
74 static int request_compare_func(const struct Request *a, const struct Request *b) {
75 int r;
76
77 assert(a);
78 assert(b);
79
80 r = CMP(a->type, b->type);
81 if (r != 0)
82 return r;
83
84 if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) {
85 r = CMP(!!a->link, !!b->link);
86 if (r != 0)
87 return r;
88
89 if (a->link) {
90 r = CMP(a->link->ifindex, b->link->ifindex);
91 if (r != 0)
92 return r;
93 }
94 }
95
96 r = CMP(PTR_TO_UINT64(a->hash_func), PTR_TO_UINT64(b->hash_func));
97 if (r != 0)
98 return r;
99
100 r = CMP(PTR_TO_UINT64(a->compare_func), PTR_TO_UINT64(b->compare_func));
101 if (r != 0)
102 return r;
103
104 if (a->compare_func)
105 return a->compare_func(a->userdata, b->userdata);
106
107 return 0;
108 }
109
110 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
111 request_hash_ops,
112 Request,
113 request_hash_func,
114 request_compare_func,
115 request_detach);
116
117 static int request_new(
118 Manager *manager,
119 Link *link,
120 RequestType type,
121 void *userdata,
122 mfree_func_t free_func,
123 hash_func_t hash_func,
124 compare_func_t compare_func,
125 request_process_func_t process,
126 unsigned *counter,
127 request_netlink_handler_t netlink_handler,
128 Request **ret) {
129
130 _cleanup_(request_unrefp) Request *req = NULL;
131 Request *existing;
132 int r;
133
134 assert(manager);
135 assert(process);
136
137 req = new(Request, 1);
138 if (!req)
139 return -ENOMEM;
140
141 *req = (Request) {
142 .n_ref = 1,
143 .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */
144 .type = type,
145 .userdata = userdata,
146 .hash_func = hash_func,
147 .compare_func = compare_func,
148 .process = process,
149 .netlink_handler = netlink_handler,
150 };
151
152 existing = ordered_set_get(manager->request_queue, req);
153 if (existing) {
154 if (ret)
155 *ret = existing;
156 return 0;
157 }
158
159 r = ordered_set_ensure_put(&manager->request_queue, &request_hash_ops, req);
160 if (r < 0)
161 return r;
162
163 req->manager = manager;
164 req->free_func = free_func;
165 req->counter = counter;
166 if (req->counter)
167 (*req->counter)++;
168
169 /* If this is called in the ORDERED_SET_FOREACH() loop of manager_process_requests(), we need to
170 * exit from the loop, due to the limitation of the iteration on OrderedSet. */
171 manager->request_queued = true;
172
173 if (ret)
174 *ret = req;
175
176 TAKE_PTR(req);
177 return 1;
178 }
179
180 int netdev_queue_request(
181 NetDev *netdev,
182 request_process_func_t process,
183 Request **ret) {
184
185 int r;
186
187 assert(netdev);
188
189 r = request_new(netdev->manager, NULL, REQUEST_TYPE_NETDEV_INDEPENDENT,
190 netdev, (mfree_func_t) netdev_unref,
191 trivial_hash_func, trivial_compare_func,
192 process, NULL, NULL, ret);
193 if (r <= 0)
194 return r;
195
196 netdev_ref(netdev);
197 return 1;
198 }
199
200 int link_queue_request_full(
201 Link *link,
202 RequestType type,
203 void *userdata,
204 mfree_func_t free_func,
205 hash_func_t hash_func,
206 compare_func_t compare_func,
207 request_process_func_t process,
208 unsigned *counter,
209 request_netlink_handler_t netlink_handler,
210 Request **ret) {
211
212 assert(link);
213
214 return request_new(link->manager, link, type,
215 userdata, free_func, hash_func, compare_func,
216 process, counter, netlink_handler, ret);
217 }
218
219 int link_requeue_request(Link *link, Request *req, void *userdata, Request **ret) {
220 assert(link);
221 assert(req);
222
223 return link_queue_request_full(
224 link,
225 req->type,
226 userdata,
227 req->free_func,
228 req->hash_func,
229 req->compare_func,
230 req->process,
231 req->counter,
232 req->netlink_handler,
233 ret);
234 }
235
236 int manager_process_requests(Manager *manager) {
237 Request *req;
238 int r;
239
240 assert(manager);
241
242 /* Process only when no remove request is queued. */
243 if (!ordered_set_isempty(manager->remove_request_queue))
244 return 0;
245
246 manager->request_queued = false;
247
248 ORDERED_SET_FOREACH(req, manager->request_queue) {
249 if (req->waiting_reply)
250 continue; /* Already processed, and waiting for netlink reply. */
251
252 /* Typically, requests send netlink message asynchronously. If there are many requests
253 * queued, then this event may make reply callback queue in sd-netlink full. */
254 if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
255 netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD ||
256 fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD)
257 break;
258
259 /* Avoid the request and link freed by req->process() and request_detach(). */
260 _unused_ _cleanup_(request_unrefp) Request *req_unref = request_ref(req);
261 _cleanup_(link_unrefp) Link *link = link_ref(req->link);
262
263 assert(req->process);
264 r = req->process(req, link, req->userdata);
265 if (r < 0) {
266 request_detach(req);
267
268 if (link) {
269 link_enter_failed(link);
270 /* link_enter_failed() may detach multiple requests from the queue.
271 * Hence, we need to exit from the loop. */
272 break;
273 }
274 }
275 if (r > 0 && !req->waiting_reply)
276 /* If the request sends netlink message, e.g. for Address or so, the Request object is
277 * referenced by the netlink slot, and will be detached later by its destroy callback.
278 * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */
279 request_detach(req);
280
281 if (manager->request_queued)
282 break; /* New request is queued. Exit from the loop. */
283 }
284
285 return 0;
286 }
287
288 static int request_netlink_handler(sd_netlink *nl, sd_netlink_message *m, Request *req) {
289 assert(req);
290
291 if (req->counter) {
292 assert(*req->counter > 0);
293 (*req->counter)--;
294 req->counter = NULL; /* To prevent double decrement on free. */
295 }
296
297 if (req->link && IN_SET(req->link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
298 return 0;
299
300 if (req->netlink_handler)
301 return req->netlink_handler(nl, m, req, req->link, req->userdata);
302
303 return 0;
304 }
305
306 int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req) {
307 int r;
308
309 assert(nl);
310 assert(m);
311 assert(req);
312
313 r = netlink_call_async(nl, NULL, m, request_netlink_handler, request_destroy_callback, req);
314 if (r < 0)
315 return r;
316
317 request_ref(req);
318 req->waiting_reply = true;
319 return 0;
320 }
321
322 static const char *const request_type_table[_REQUEST_TYPE_MAX] = {
323 [REQUEST_TYPE_ACTIVATE_LINK] = "activate link",
324 [REQUEST_TYPE_ADDRESS] = "address",
325 [REQUEST_TYPE_ADDRESS_LABEL] = "address label",
326 [REQUEST_TYPE_BRIDGE_FDB] = "bridge FDB",
327 [REQUEST_TYPE_BRIDGE_MDB] = "bridge MDB",
328 [REQUEST_TYPE_DHCP_SERVER] = "DHCP server",
329 [REQUEST_TYPE_DHCP4_CLIENT] = "DHCPv4 client",
330 [REQUEST_TYPE_DHCP6_CLIENT] = "DHCPv6 client",
331 [REQUEST_TYPE_IPV6_PROXY_NDP] = "IPv6 proxy NDP",
332 [REQUEST_TYPE_NDISC] = "NDisc",
333 [REQUEST_TYPE_NEIGHBOR] = "neighbor",
334 [REQUEST_TYPE_NETDEV_INDEPENDENT] = "independent netdev",
335 [REQUEST_TYPE_NETDEV_STACKED] = "stacked netdev",
336 [REQUEST_TYPE_NEXTHOP] = "nexthop",
337 [REQUEST_TYPE_RADV] = "RADV",
338 [REQUEST_TYPE_ROUTE] = "route",
339 [REQUEST_TYPE_ROUTING_POLICY_RULE] = "routing policy rule",
340 [REQUEST_TYPE_SET_LINK_ADDRESS_GENERATION_MODE] = "IPv6LL address generation mode",
341 [REQUEST_TYPE_SET_LINK_BOND] = "bond configurations",
342 [REQUEST_TYPE_SET_LINK_BRIDGE] = "bridge configurations",
343 [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 1)",
344 [REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 2)",
345 [REQUEST_TYPE_SET_LINK_CAN] = "CAN interface configurations",
346 [REQUEST_TYPE_SET_LINK_FLAGS] = "link flags",
347 [REQUEST_TYPE_SET_LINK_GROUP] = "interface group",
348 [REQUEST_TYPE_SET_LINK_IPOIB] = "IPoIB configurations",
349 [REQUEST_TYPE_SET_LINK_MAC] = "MAC address",
350 [REQUEST_TYPE_SET_LINK_MASTER] = "master interface",
351 [REQUEST_TYPE_SET_LINK_MTU] = "MTU",
352 [REQUEST_TYPE_SRIOV] = "SR-IOV",
353 [REQUEST_TYPE_TC_QDISC] = "QDisc",
354 [REQUEST_TYPE_TC_CLASS] = "TClass",
355 [REQUEST_TYPE_UP_DOWN] = "bring link up or down",
356 };
357
358 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType);
359
360 static RemoveRequest* remove_request_free(RemoveRequest *req) {
361 if (!req)
362 return NULL;
363
364 if (req->manager)
365 ordered_set_remove(req->manager->remove_request_queue, req);
366
367 if (req->unref_func)
368 req->unref_func(req->userdata);
369
370 link_unref(req->link);
371 sd_netlink_unref(req->netlink);
372 sd_netlink_message_unref(req->message);
373
374 return mfree(req);
375 }
376
377 DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free);
378 DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free);
379 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
380 remove_request_hash_ops,
381 void,
382 trivial_hash_func,
383 trivial_compare_func,
384 remove_request_free);
385
386 int remove_request_add(
387 Manager *manager,
388 Link *link,
389 void *userdata,
390 mfree_func_t unref_func,
391 sd_netlink *netlink,
392 sd_netlink_message *message,
393 remove_request_netlink_handler_t netlink_handler) {
394
395 _cleanup_(remove_request_freep) RemoveRequest *req = NULL;
396 int r;
397
398 assert(manager);
399 assert(userdata);
400 assert(netlink);
401 assert(message);
402
403 req = new(RemoveRequest, 1);
404 if (!req)
405 return -ENOMEM;
406
407 *req = (RemoveRequest) {
408 .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */
409 .userdata = userdata,
410 .netlink = sd_netlink_ref(netlink),
411 .message = sd_netlink_message_ref(message),
412 .netlink_handler = netlink_handler,
413 };
414
415 r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req);
416 if (r < 0)
417 return r;
418 assert(r > 0);
419
420 req->manager = manager;
421 req->unref_func = unref_func;
422
423 TAKE_PTR(req);
424 return 0;
425 }
426
427 int manager_process_remove_requests(Manager *manager) {
428 RemoveRequest *req;
429 int r;
430
431 assert(manager);
432
433 while ((req = ordered_set_first(manager->remove_request_queue))) {
434
435 /* Do not make the reply callback queue in sd-netlink full. */
436 if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD)
437 return 0;
438
439 r = netlink_call_async(
440 req->netlink, NULL, req->message,
441 req->netlink_handler,
442 remove_request_destroy_callback,
443 req);
444 if (r < 0) {
445 _cleanup_(link_unrefp) Link *link = link_ref(req->link);
446
447 log_link_warning_errno(link, r, "Failed to call netlink message: %m");
448
449 /* First free the request. */
450 remove_request_free(req);
451
452 /* Then, make the link enter the failed state. */
453 if (link)
454 link_enter_failed(link);
455
456 } else {
457 /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove
458 * request may not freed on shutting down. */
459 req->netlink = sd_netlink_unref(req->netlink);
460 ordered_set_remove(manager->remove_request_queue, req);
461 }
462 }
463
464 return 0;
465 }