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