/src/systemd/src/libsystemd/sd-netlink/sd-netlink.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | | |
3 | | #include <poll.h> |
4 | | #include <sys/socket.h> |
5 | | |
6 | | #include "sd-netlink.h" |
7 | | |
8 | | #include "alloc-util.h" |
9 | | #include "fd-util.h" |
10 | | #include "hashmap.h" |
11 | | #include "macro.h" |
12 | | #include "missing.h" |
13 | | #include "netlink-internal.h" |
14 | | #include "netlink-slot.h" |
15 | | #include "netlink-util.h" |
16 | | #include "process-util.h" |
17 | | #include "socket-util.h" |
18 | | #include "string-util.h" |
19 | | #include "util.h" |
20 | | |
21 | 40.0k | static int sd_netlink_new(sd_netlink **ret) { |
22 | 40.0k | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
23 | 40.0k | |
24 | 40.0k | assert_return(ret, -EINVAL); |
25 | 40.0k | |
26 | 40.0k | rtnl = new(sd_netlink, 1); |
27 | 40.0k | if (!rtnl) |
28 | 0 | return -ENOMEM; |
29 | 40.0k | |
30 | 40.0k | *rtnl = (sd_netlink) { |
31 | 40.0k | .n_ref = 1, |
32 | 40.0k | .fd = -1, |
33 | 40.0k | .sockaddr.nl.nl_family = AF_NETLINK, |
34 | 40.0k | .original_pid = getpid_cached(), |
35 | 40.0k | .protocol = -1, |
36 | 40.0k | |
37 | 40.0k | /* Change notification responses have sequence 0, so we must |
38 | 40.0k | * start our request sequence numbers at 1, or we may confuse our |
39 | 40.0k | * responses with notifications from the kernel */ |
40 | 40.0k | .serial = 1, |
41 | 40.0k | |
42 | 40.0k | }; |
43 | 40.0k | |
44 | 40.0k | /* We guarantee that the read buffer has at least space for |
45 | 40.0k | * a message header */ |
46 | 40.0k | if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, |
47 | 40.0k | sizeof(struct nlmsghdr), sizeof(uint8_t))) |
48 | 0 | return -ENOMEM; |
49 | 40.0k | |
50 | 40.0k | *ret = TAKE_PTR(rtnl); |
51 | 40.0k | |
52 | 40.0k | return 0; |
53 | 40.0k | } |
54 | | |
55 | 0 | int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { |
56 | 0 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
57 | 0 | socklen_t addrlen; |
58 | 0 | int r; |
59 | 0 |
|
60 | 0 | assert_return(ret, -EINVAL); |
61 | 0 |
|
62 | 0 | r = sd_netlink_new(&rtnl); |
63 | 0 | if (r < 0) |
64 | 0 | return r; |
65 | 0 | |
66 | 0 | addrlen = sizeof(rtnl->sockaddr); |
67 | 0 |
|
68 | 0 | r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); |
69 | 0 | if (r < 0) |
70 | 0 | return -errno; |
71 | 0 | |
72 | 0 | if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) |
73 | 0 | return -EINVAL; |
74 | 0 | |
75 | 0 | rtnl->fd = fd; |
76 | 0 |
|
77 | 0 | *ret = TAKE_PTR(rtnl); |
78 | 0 |
|
79 | 0 | return 0; |
80 | 0 | } |
81 | | |
82 | 201k | static bool rtnl_pid_changed(sd_netlink *rtnl) { |
83 | 201k | assert(rtnl); |
84 | 201k | |
85 | 201k | /* We don't support people creating an rtnl connection and |
86 | 201k | * keeping it around over a fork(). Let's complain. */ |
87 | 201k | |
88 | 201k | return rtnl->original_pid != getpid_cached(); |
89 | 201k | } |
90 | | |
91 | 40.0k | int sd_netlink_open_fd(sd_netlink **ret, int fd) { |
92 | 40.0k | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
93 | 40.0k | int r; |
94 | 40.0k | int protocol; |
95 | 40.0k | socklen_t l; |
96 | 40.0k | |
97 | 40.0k | assert_return(ret, -EINVAL); |
98 | 40.0k | assert_return(fd >= 0, -EBADF); |
99 | 40.0k | |
100 | 40.0k | r = sd_netlink_new(&rtnl); |
101 | 40.0k | if (r < 0) |
102 | 0 | return r; |
103 | 40.0k | |
104 | 40.0k | l = sizeof(protocol); |
105 | 40.0k | r = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &l); |
106 | 40.0k | if (r < 0) |
107 | 0 | return r; |
108 | 40.0k | |
109 | 40.0k | rtnl->fd = fd; |
110 | 40.0k | rtnl->protocol = protocol; |
111 | 40.0k | |
112 | 40.0k | r = socket_bind(rtnl); |
113 | 40.0k | if (r < 0) { |
114 | 0 | rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ |
115 | 0 | rtnl->protocol = -1; |
116 | 0 | return r; |
117 | 0 | } |
118 | 40.0k | |
119 | 40.0k | *ret = TAKE_PTR(rtnl); |
120 | 40.0k | |
121 | 40.0k | return 0; |
122 | 40.0k | } |
123 | | |
124 | 40.0k | int netlink_open_family(sd_netlink **ret, int family) { |
125 | 40.0k | _cleanup_close_ int fd = -1; |
126 | 40.0k | int r; |
127 | 40.0k | |
128 | 40.0k | fd = socket_open(family); |
129 | 40.0k | if (fd < 0) |
130 | 0 | return fd; |
131 | 40.0k | |
132 | 40.0k | r = sd_netlink_open_fd(ret, fd); |
133 | 40.0k | if (r < 0) |
134 | 0 | return r; |
135 | 40.0k | |
136 | 40.0k | fd = -1; |
137 | 40.0k | |
138 | 40.0k | return 0; |
139 | 40.0k | } |
140 | | |
141 | 20.0k | int sd_netlink_open(sd_netlink **ret) { |
142 | 20.0k | return netlink_open_family(ret, NETLINK_ROUTE); |
143 | 20.0k | } |
144 | | |
145 | 40.0k | int sd_netlink_inc_rcvbuf(sd_netlink *rtnl, size_t size) { |
146 | 40.0k | assert_return(rtnl, -EINVAL); |
147 | 40.0k | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
148 | 40.0k | |
149 | 40.0k | return fd_inc_rcvbuf(rtnl->fd, size); |
150 | 40.0k | } |
151 | | |
152 | 40.0k | static sd_netlink *netlink_free(sd_netlink *rtnl) { |
153 | 40.0k | sd_netlink_slot *s; |
154 | 40.0k | unsigned i; |
155 | 40.0k | |
156 | 40.0k | assert(rtnl); |
157 | 40.0k | |
158 | 40.0k | for (i = 0; i < rtnl->rqueue_size; i++) |
159 | 0 | sd_netlink_message_unref(rtnl->rqueue[i]); |
160 | 40.0k | free(rtnl->rqueue); |
161 | 40.0k | |
162 | 40.0k | for (i = 0; i < rtnl->rqueue_partial_size; i++) |
163 | 0 | sd_netlink_message_unref(rtnl->rqueue_partial[i]); |
164 | 40.0k | free(rtnl->rqueue_partial); |
165 | 40.0k | |
166 | 40.0k | free(rtnl->rbuffer); |
167 | 40.0k | |
168 | 200k | while ((s = rtnl->slots)) { |
169 | 160k | assert(s->floating); |
170 | 160k | netlink_slot_disconnect(s, true); |
171 | 160k | } |
172 | 40.0k | hashmap_free(rtnl->reply_callbacks); |
173 | 40.0k | prioq_free(rtnl->reply_callbacks_prioq); |
174 | 40.0k | |
175 | 40.0k | sd_event_source_unref(rtnl->io_event_source); |
176 | 40.0k | sd_event_source_unref(rtnl->time_event_source); |
177 | 40.0k | sd_event_unref(rtnl->event); |
178 | 40.0k | |
179 | 40.0k | hashmap_free(rtnl->broadcast_group_refs); |
180 | 40.0k | |
181 | 40.0k | safe_close(rtnl->fd); |
182 | 40.0k | return mfree(rtnl); |
183 | 40.0k | } |
184 | | |
185 | | DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_netlink, sd_netlink, netlink_free); |
186 | | |
187 | 533 | static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { |
188 | 533 | assert(rtnl); |
189 | 533 | assert(!rtnl_pid_changed(rtnl)); |
190 | 533 | assert(m); |
191 | 533 | assert(m->hdr); |
192 | 533 | |
193 | 533 | /* don't use seq == 0, as that is used for broadcasts, so we |
194 | 533 | would get confused by replies to such messages */ |
195 | 533 | m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; |
196 | 533 | |
197 | 533 | rtnl_message_seal(m); |
198 | 533 | |
199 | 533 | return; |
200 | 533 | } |
201 | | |
202 | | int sd_netlink_send(sd_netlink *nl, |
203 | | sd_netlink_message *message, |
204 | 533 | uint32_t *serial) { |
205 | 533 | int r; |
206 | 533 | |
207 | 533 | assert_return(nl, -EINVAL); |
208 | 533 | assert_return(!rtnl_pid_changed(nl), -ECHILD); |
209 | 533 | assert_return(message, -EINVAL); |
210 | 533 | assert_return(!message->sealed, -EPERM); |
211 | 533 | |
212 | 533 | rtnl_seal_message(nl, message); |
213 | 533 | |
214 | 533 | r = socket_write_message(nl, message); |
215 | 533 | if (r < 0) |
216 | 0 | return r; |
217 | 533 | |
218 | 533 | if (serial) |
219 | 533 | *serial = rtnl_message_get_serial(message); |
220 | 533 | |
221 | 533 | return 1; |
222 | 533 | } |
223 | | |
224 | 0 | int rtnl_rqueue_make_room(sd_netlink *rtnl) { |
225 | 0 | assert(rtnl); |
226 | 0 |
|
227 | 0 | if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) |
228 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), |
229 | 0 | "rtnl: exhausted the read queue size (%d)", |
230 | 0 | RTNL_RQUEUE_MAX); |
231 | 0 | |
232 | 0 | if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) |
233 | 0 | return -ENOMEM; |
234 | 0 | |
235 | 0 | return 0; |
236 | 0 | } |
237 | | |
238 | 0 | int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { |
239 | 0 | assert(rtnl); |
240 | 0 |
|
241 | 0 | if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) |
242 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), |
243 | 0 | "rtnl: exhausted the partial read queue size (%d)", |
244 | 0 | RTNL_RQUEUE_MAX); |
245 | 0 | |
246 | 0 | if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, |
247 | 0 | rtnl->rqueue_partial_size + 1)) |
248 | 0 | return -ENOMEM; |
249 | 0 | |
250 | 0 | return 0; |
251 | 0 | } |
252 | | |
253 | 0 | static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { |
254 | 0 | int r; |
255 | 0 |
|
256 | 0 | assert(rtnl); |
257 | 0 | assert(message); |
258 | 0 |
|
259 | 0 | if (rtnl->rqueue_size <= 0) { |
260 | 0 | /* Try to read a new message */ |
261 | 0 | r = socket_read_message(rtnl); |
262 | 0 | if (r == -ENOBUFS) { /* FIXME: ignore buffer overruns for now */ |
263 | 0 | log_debug_errno(r, "Got ENOBUFS from netlink socket, ignoring."); |
264 | 0 | return 1; |
265 | 0 | } |
266 | 0 | if (r <= 0) |
267 | 0 | return r; |
268 | 0 | } |
269 | 0 | |
270 | 0 | /* Dispatch a queued message */ |
271 | 0 | *message = rtnl->rqueue[0]; |
272 | 0 | rtnl->rqueue_size--; |
273 | 0 | memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); |
274 | 0 |
|
275 | 0 | return 1; |
276 | 0 | } |
277 | | |
278 | 0 | static int process_timeout(sd_netlink *rtnl) { |
279 | 0 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
280 | 0 | struct reply_callback *c; |
281 | 0 | sd_netlink_slot *slot; |
282 | 0 | usec_t n; |
283 | 0 | int r; |
284 | 0 |
|
285 | 0 | assert(rtnl); |
286 | 0 |
|
287 | 0 | c = prioq_peek(rtnl->reply_callbacks_prioq); |
288 | 0 | if (!c) |
289 | 0 | return 0; |
290 | 0 | |
291 | 0 | n = now(CLOCK_MONOTONIC); |
292 | 0 | if (c->timeout > n) |
293 | 0 | return 0; |
294 | 0 | |
295 | 0 | r = rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, c->serial, &m); |
296 | 0 | if (r < 0) |
297 | 0 | return r; |
298 | 0 | |
299 | 0 | assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); |
300 | 0 | c->timeout = 0; |
301 | 0 | hashmap_remove(rtnl->reply_callbacks, &c->serial); |
302 | 0 |
|
303 | 0 | slot = container_of(c, sd_netlink_slot, reply_callback); |
304 | 0 |
|
305 | 0 | r = c->callback(rtnl, m, slot->userdata); |
306 | 0 | if (r < 0) |
307 | 0 | log_debug_errno(r, "sd-netlink: timedout callback %s%s%sfailed: %m", |
308 | 0 | slot->description ? "'" : "", |
309 | 0 | strempty(slot->description), |
310 | 0 | slot->description ? "' " : ""); |
311 | 0 |
|
312 | 0 | if (slot->floating) |
313 | 0 | netlink_slot_disconnect(slot, true); |
314 | 0 |
|
315 | 0 | return 1; |
316 | 0 | } |
317 | | |
318 | 0 | static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { |
319 | 0 | struct reply_callback *c; |
320 | 0 | sd_netlink_slot *slot; |
321 | 0 | uint64_t serial; |
322 | 0 | uint16_t type; |
323 | 0 | int r; |
324 | 0 |
|
325 | 0 | assert(rtnl); |
326 | 0 | assert(m); |
327 | 0 |
|
328 | 0 | serial = rtnl_message_get_serial(m); |
329 | 0 | c = hashmap_remove(rtnl->reply_callbacks, &serial); |
330 | 0 | if (!c) |
331 | 0 | return 0; |
332 | 0 | |
333 | 0 | if (c->timeout != 0) { |
334 | 0 | prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); |
335 | 0 | c->timeout = 0; |
336 | 0 | } |
337 | 0 |
|
338 | 0 | r = sd_netlink_message_get_type(m, &type); |
339 | 0 | if (r < 0) |
340 | 0 | return r; |
341 | 0 | |
342 | 0 | if (type == NLMSG_DONE) |
343 | 0 | m = NULL; |
344 | 0 |
|
345 | 0 | slot = container_of(c, sd_netlink_slot, reply_callback); |
346 | 0 |
|
347 | 0 | r = c->callback(rtnl, m, slot->userdata); |
348 | 0 | if (r < 0) |
349 | 0 | log_debug_errno(r, "sd-netlink: reply callback %s%s%sfailed: %m", |
350 | 0 | slot->description ? "'" : "", |
351 | 0 | strempty(slot->description), |
352 | 0 | slot->description ? "' " : ""); |
353 | 0 |
|
354 | 0 | if (slot->floating) |
355 | 0 | netlink_slot_disconnect(slot, true); |
356 | 0 |
|
357 | 0 | return 1; |
358 | 0 | } |
359 | | |
360 | 0 | static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { |
361 | 0 | struct match_callback *c; |
362 | 0 | sd_netlink_slot *slot; |
363 | 0 | uint16_t type; |
364 | 0 | int r; |
365 | 0 |
|
366 | 0 | assert(rtnl); |
367 | 0 | assert(m); |
368 | 0 |
|
369 | 0 | r = sd_netlink_message_get_type(m, &type); |
370 | 0 | if (r < 0) |
371 | 0 | return r; |
372 | 0 | |
373 | 0 | LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { |
374 | 0 | if (type == c->type) { |
375 | 0 | slot = container_of(c, sd_netlink_slot, match_callback); |
376 | 0 |
|
377 | 0 | r = c->callback(rtnl, m, slot->userdata); |
378 | 0 | if (r != 0) { |
379 | 0 | if (r < 0) |
380 | 0 | log_debug_errno(r, "sd-netlink: match callback %s%s%sfailed: %m", |
381 | 0 | slot->description ? "'" : "", |
382 | 0 | strempty(slot->description), |
383 | 0 | slot->description ? "' " : ""); |
384 | 0 |
|
385 | 0 | break; |
386 | 0 | } |
387 | 0 | } |
388 | 0 | } |
389 | 0 |
|
390 | 0 | return 1; |
391 | 0 | } |
392 | | |
393 | 0 | static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { |
394 | 0 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
395 | 0 | int r; |
396 | 0 |
|
397 | 0 | assert(rtnl); |
398 | 0 |
|
399 | 0 | r = process_timeout(rtnl); |
400 | 0 | if (r != 0) |
401 | 0 | goto null_message; |
402 | 0 | |
403 | 0 | r = dispatch_rqueue(rtnl, &m); |
404 | 0 | if (r < 0) |
405 | 0 | return r; |
406 | 0 | if (!m) |
407 | 0 | goto null_message; |
408 | 0 | |
409 | 0 | if (sd_netlink_message_is_broadcast(m)) { |
410 | 0 | r = process_match(rtnl, m); |
411 | 0 | if (r != 0) |
412 | 0 | goto null_message; |
413 | 0 | } else { |
414 | 0 | r = process_reply(rtnl, m); |
415 | 0 | if (r != 0) |
416 | 0 | goto null_message; |
417 | 0 | } |
418 | 0 | |
419 | 0 | if (ret) { |
420 | 0 | *ret = TAKE_PTR(m); |
421 | 0 |
|
422 | 0 | return 1; |
423 | 0 | } |
424 | 0 |
|
425 | 0 | return 1; |
426 | 0 | |
427 | 0 | null_message: |
428 | 0 | if (r >= 0 && ret) |
429 | 0 | *ret = NULL; |
430 | 0 |
|
431 | 0 | return r; |
432 | 0 | } |
433 | | |
434 | 0 | int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { |
435 | 0 | NETLINK_DONT_DESTROY(rtnl); |
436 | 0 | int r; |
437 | 0 |
|
438 | 0 | assert_return(rtnl, -EINVAL); |
439 | 0 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
440 | 0 | assert_return(!rtnl->processing, -EBUSY); |
441 | 0 |
|
442 | 0 | rtnl->processing = true; |
443 | 0 | r = process_running(rtnl, ret); |
444 | 0 | rtnl->processing = false; |
445 | 0 |
|
446 | 0 | return r; |
447 | 0 | } |
448 | | |
449 | 533 | static usec_t calc_elapse(uint64_t usec) { |
450 | 533 | if (usec == (uint64_t) -1) |
451 | 0 | return 0; |
452 | 533 | |
453 | 533 | if (usec == 0) |
454 | 533 | usec = RTNL_DEFAULT_TIMEOUT; |
455 | 533 | |
456 | 533 | return now(CLOCK_MONOTONIC) + usec; |
457 | 533 | } |
458 | | |
459 | 0 | static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { |
460 | 0 | struct pollfd p[1] = {}; |
461 | 0 | struct timespec ts; |
462 | 0 | usec_t m = USEC_INFINITY; |
463 | 0 | int r, e; |
464 | 0 |
|
465 | 0 | assert(rtnl); |
466 | 0 |
|
467 | 0 | e = sd_netlink_get_events(rtnl); |
468 | 0 | if (e < 0) |
469 | 0 | return e; |
470 | 0 | |
471 | 0 | if (need_more) |
472 | 0 | /* Caller wants more data, and doesn't care about |
473 | 0 | * what's been read or any other timeouts. */ |
474 | 0 | e |= POLLIN; |
475 | 0 | else { |
476 | 0 | usec_t until; |
477 | 0 | /* Caller wants to process if there is something to |
478 | 0 | * process, but doesn't care otherwise */ |
479 | 0 |
|
480 | 0 | r = sd_netlink_get_timeout(rtnl, &until); |
481 | 0 | if (r < 0) |
482 | 0 | return r; |
483 | 0 | if (r > 0) { |
484 | 0 | usec_t nw; |
485 | 0 | nw = now(CLOCK_MONOTONIC); |
486 | 0 | m = until > nw ? until - nw : 0; |
487 | 0 | } |
488 | 0 | } |
489 | 0 |
|
490 | 0 | if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) |
491 | 0 | m = timeout_usec; |
492 | 0 |
|
493 | 0 | p[0].fd = rtnl->fd; |
494 | 0 | p[0].events = e; |
495 | 0 |
|
496 | 0 | r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); |
497 | 0 | if (r < 0) |
498 | 0 | return -errno; |
499 | 0 | |
500 | 0 | return r > 0 ? 1 : 0; |
501 | 0 | } |
502 | | |
503 | 0 | int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { |
504 | 0 | assert_return(nl, -EINVAL); |
505 | 0 | assert_return(!rtnl_pid_changed(nl), -ECHILD); |
506 | 0 |
|
507 | 0 | if (nl->rqueue_size > 0) |
508 | 0 | return 0; |
509 | 0 | |
510 | 0 | return rtnl_poll(nl, false, timeout_usec); |
511 | 0 | } |
512 | | |
513 | 0 | static int timeout_compare(const void *a, const void *b) { |
514 | 0 | const struct reply_callback *x = a, *y = b; |
515 | 0 |
|
516 | 0 | if (x->timeout != 0 && y->timeout == 0) |
517 | 0 | return -1; |
518 | 0 | |
519 | 0 | if (x->timeout == 0 && y->timeout != 0) |
520 | 0 | return 1; |
521 | 0 | |
522 | 0 | return CMP(x->timeout, y->timeout); |
523 | 0 | } |
524 | | |
525 | | int sd_netlink_call_async( |
526 | | sd_netlink *nl, |
527 | | sd_netlink_slot **ret_slot, |
528 | | sd_netlink_message *m, |
529 | | sd_netlink_message_handler_t callback, |
530 | | sd_netlink_destroy_t destroy_callback, |
531 | | void *userdata, |
532 | | uint64_t usec, |
533 | 533 | const char *description) { |
534 | 533 | _cleanup_free_ sd_netlink_slot *slot = NULL; |
535 | 533 | uint32_t s; |
536 | 533 | int r, k; |
537 | 533 | |
538 | 533 | assert_return(nl, -EINVAL); |
539 | 533 | assert_return(m, -EINVAL); |
540 | 533 | assert_return(callback, -EINVAL); |
541 | 533 | assert_return(!rtnl_pid_changed(nl), -ECHILD); |
542 | 533 | |
543 | 533 | r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); |
544 | 533 | if (r < 0) |
545 | 0 | return r; |
546 | 533 | |
547 | 533 | if (usec != (uint64_t) -1) { |
548 | 533 | r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); |
549 | 533 | if (r < 0) |
550 | 0 | return r; |
551 | 533 | } |
552 | 533 | |
553 | 533 | r = netlink_slot_allocate(nl, !ret_slot, NETLINK_REPLY_CALLBACK, sizeof(struct reply_callback), userdata, description, &slot); |
554 | 533 | if (r < 0) |
555 | 0 | return r; |
556 | 533 | |
557 | 533 | slot->reply_callback.callback = callback; |
558 | 533 | slot->reply_callback.timeout = calc_elapse(usec); |
559 | 533 | |
560 | 533 | k = sd_netlink_send(nl, m, &s); |
561 | 533 | if (k < 0) |
562 | 0 | return k; |
563 | 533 | |
564 | 533 | slot->reply_callback.serial = s; |
565 | 533 | |
566 | 533 | r = hashmap_put(nl->reply_callbacks, &slot->reply_callback.serial, &slot->reply_callback); |
567 | 533 | if (r < 0) |
568 | 0 | return r; |
569 | 533 | |
570 | 533 | if (slot->reply_callback.timeout != 0) { |
571 | 533 | r = prioq_put(nl->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); |
572 | 533 | if (r < 0) { |
573 | 0 | (void) hashmap_remove(nl->reply_callbacks, &slot->reply_callback.serial); |
574 | 0 | return r; |
575 | 0 | } |
576 | 533 | } |
577 | 533 | |
578 | 533 | /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ |
579 | 533 | slot->destroy_callback = destroy_callback; |
580 | 533 | |
581 | 533 | if (ret_slot) |
582 | 0 | *ret_slot = slot; |
583 | 533 | |
584 | 533 | TAKE_PTR(slot); |
585 | 533 | |
586 | 533 | return k; |
587 | 533 | } |
588 | | |
589 | | int sd_netlink_call(sd_netlink *rtnl, |
590 | | sd_netlink_message *message, |
591 | | uint64_t usec, |
592 | 0 | sd_netlink_message **ret) { |
593 | 0 | usec_t timeout; |
594 | 0 | uint32_t serial; |
595 | 0 | int r; |
596 | 0 |
|
597 | 0 | assert_return(rtnl, -EINVAL); |
598 | 0 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
599 | 0 | assert_return(message, -EINVAL); |
600 | 0 |
|
601 | 0 | r = sd_netlink_send(rtnl, message, &serial); |
602 | 0 | if (r < 0) |
603 | 0 | return r; |
604 | 0 | |
605 | 0 | timeout = calc_elapse(usec); |
606 | 0 |
|
607 | 0 | for (;;) { |
608 | 0 | usec_t left; |
609 | 0 | unsigned i; |
610 | 0 |
|
611 | 0 | for (i = 0; i < rtnl->rqueue_size; i++) { |
612 | 0 | uint32_t received_serial; |
613 | 0 |
|
614 | 0 | received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); |
615 | 0 |
|
616 | 0 | if (received_serial == serial) { |
617 | 0 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; |
618 | 0 | uint16_t type; |
619 | 0 |
|
620 | 0 | incoming = rtnl->rqueue[i]; |
621 | 0 |
|
622 | 0 | /* found a match, remove from rqueue and return it */ |
623 | 0 | memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, |
624 | 0 | sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); |
625 | 0 | rtnl->rqueue_size--; |
626 | 0 |
|
627 | 0 | r = sd_netlink_message_get_errno(incoming); |
628 | 0 | if (r < 0) |
629 | 0 | return r; |
630 | 0 | |
631 | 0 | r = sd_netlink_message_get_type(incoming, &type); |
632 | 0 | if (r < 0) |
633 | 0 | return r; |
634 | 0 | |
635 | 0 | if (type == NLMSG_DONE) { |
636 | 0 | *ret = NULL; |
637 | 0 | return 0; |
638 | 0 | } |
639 | 0 | |
640 | 0 | if (ret) |
641 | 0 | *ret = TAKE_PTR(incoming); |
642 | 0 |
|
643 | 0 | return 1; |
644 | 0 | } |
645 | 0 | } |
646 | 0 |
|
647 | 0 | r = socket_read_message(rtnl); |
648 | 0 | if (r < 0) |
649 | 0 | return r; |
650 | 0 | if (r > 0) |
651 | 0 | /* received message, so try to process straight away */ |
652 | 0 | continue; |
653 | 0 | |
654 | 0 | if (timeout > 0) { |
655 | 0 | usec_t n; |
656 | 0 |
|
657 | 0 | n = now(CLOCK_MONOTONIC); |
658 | 0 | if (n >= timeout) |
659 | 0 | return -ETIMEDOUT; |
660 | 0 | |
661 | 0 | left = timeout - n; |
662 | 0 | } else |
663 | 0 | left = (uint64_t) -1; |
664 | 0 |
|
665 | 0 | r = rtnl_poll(rtnl, true, left); |
666 | 0 | if (r < 0) |
667 | 0 | return r; |
668 | 0 | else if (r == 0) |
669 | 0 | return -ETIMEDOUT; |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | 0 | int sd_netlink_get_events(sd_netlink *rtnl) { |
674 | 0 | assert_return(rtnl, -EINVAL); |
675 | 0 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
676 | 0 |
|
677 | 0 | if (rtnl->rqueue_size == 0) |
678 | 0 | return POLLIN; |
679 | 0 | else |
680 | 0 | return 0; |
681 | 0 | } |
682 | | |
683 | 0 | int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { |
684 | 0 | struct reply_callback *c; |
685 | 0 |
|
686 | 0 | assert_return(rtnl, -EINVAL); |
687 | 0 | assert_return(timeout_usec, -EINVAL); |
688 | 0 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
689 | 0 |
|
690 | 0 | if (rtnl->rqueue_size > 0) { |
691 | 0 | *timeout_usec = 0; |
692 | 0 | return 1; |
693 | 0 | } |
694 | 0 | |
695 | 0 | c = prioq_peek(rtnl->reply_callbacks_prioq); |
696 | 0 | if (!c) { |
697 | 0 | *timeout_usec = (uint64_t) -1; |
698 | 0 | return 0; |
699 | 0 | } |
700 | 0 | |
701 | 0 | *timeout_usec = c->timeout; |
702 | 0 |
|
703 | 0 | return 1; |
704 | 0 | } |
705 | | |
706 | 0 | static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
707 | 0 | sd_netlink *rtnl = userdata; |
708 | 0 | int r; |
709 | 0 |
|
710 | 0 | assert(rtnl); |
711 | 0 |
|
712 | 0 | r = sd_netlink_process(rtnl, NULL); |
713 | 0 | if (r < 0) |
714 | 0 | return r; |
715 | 0 | |
716 | 0 | return 1; |
717 | 0 | } |
718 | | |
719 | 0 | static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { |
720 | 0 | sd_netlink *rtnl = userdata; |
721 | 0 | int r; |
722 | 0 |
|
723 | 0 | assert(rtnl); |
724 | 0 |
|
725 | 0 | r = sd_netlink_process(rtnl, NULL); |
726 | 0 | if (r < 0) |
727 | 0 | return r; |
728 | 0 | |
729 | 0 | return 1; |
730 | 0 | } |
731 | | |
732 | 0 | static int prepare_callback(sd_event_source *s, void *userdata) { |
733 | 0 | sd_netlink *rtnl = userdata; |
734 | 0 | int r, e; |
735 | 0 | usec_t until; |
736 | 0 |
|
737 | 0 | assert(s); |
738 | 0 | assert(rtnl); |
739 | 0 |
|
740 | 0 | e = sd_netlink_get_events(rtnl); |
741 | 0 | if (e < 0) |
742 | 0 | return e; |
743 | 0 | |
744 | 0 | r = sd_event_source_set_io_events(rtnl->io_event_source, e); |
745 | 0 | if (r < 0) |
746 | 0 | return r; |
747 | 0 | |
748 | 0 | r = sd_netlink_get_timeout(rtnl, &until); |
749 | 0 | if (r < 0) |
750 | 0 | return r; |
751 | 0 | if (r > 0) { |
752 | 0 | int j; |
753 | 0 |
|
754 | 0 | j = sd_event_source_set_time(rtnl->time_event_source, until); |
755 | 0 | if (j < 0) |
756 | 0 | return j; |
757 | 0 | } |
758 | 0 | |
759 | 0 | r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); |
760 | 0 | if (r < 0) |
761 | 0 | return r; |
762 | 0 | |
763 | 0 | return 1; |
764 | 0 | } |
765 | | |
766 | 40.0k | int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int64_t priority) { |
767 | 40.0k | int r; |
768 | 40.0k | |
769 | 40.0k | assert_return(rtnl, -EINVAL); |
770 | 40.0k | assert_return(!rtnl->event, -EBUSY); |
771 | 40.0k | |
772 | 40.0k | assert(!rtnl->io_event_source); |
773 | 40.0k | assert(!rtnl->time_event_source); |
774 | 40.0k | |
775 | 40.0k | if (event) |
776 | 40.0k | rtnl->event = sd_event_ref(event); |
777 | 0 | else { |
778 | 0 | r = sd_event_default(&rtnl->event); |
779 | 0 | if (r < 0) |
780 | 0 | return r; |
781 | 40.0k | } |
782 | 40.0k | |
783 | 40.0k | r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); |
784 | 40.0k | if (r < 0) |
785 | 0 | goto fail; |
786 | 40.0k | |
787 | 40.0k | r = sd_event_source_set_priority(rtnl->io_event_source, priority); |
788 | 40.0k | if (r < 0) |
789 | 0 | goto fail; |
790 | 40.0k | |
791 | 40.0k | r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); |
792 | 40.0k | if (r < 0) |
793 | 0 | goto fail; |
794 | 40.0k | |
795 | 40.0k | r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); |
796 | 40.0k | if (r < 0) |
797 | 0 | goto fail; |
798 | 40.0k | |
799 | 40.0k | r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); |
800 | 40.0k | if (r < 0) |
801 | 0 | goto fail; |
802 | 40.0k | |
803 | 40.0k | r = sd_event_source_set_priority(rtnl->time_event_source, priority); |
804 | 40.0k | if (r < 0) |
805 | 0 | goto fail; |
806 | 40.0k | |
807 | 40.0k | r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); |
808 | 40.0k | if (r < 0) |
809 | 0 | goto fail; |
810 | 40.0k | |
811 | 40.0k | return 0; |
812 | 0 | |
813 | 0 | fail: |
814 | 0 | sd_netlink_detach_event(rtnl); |
815 | 0 | return r; |
816 | 40.0k | } |
817 | | |
818 | 0 | int sd_netlink_detach_event(sd_netlink *rtnl) { |
819 | 0 | assert_return(rtnl, -EINVAL); |
820 | 0 | assert_return(rtnl->event, -ENXIO); |
821 | 0 |
|
822 | 0 | rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); |
823 | 0 |
|
824 | 0 | rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); |
825 | 0 |
|
826 | 0 | rtnl->event = sd_event_unref(rtnl->event); |
827 | 0 |
|
828 | 0 | return 0; |
829 | 0 | } |
830 | | |
831 | | int sd_netlink_add_match( |
832 | | sd_netlink *rtnl, |
833 | | sd_netlink_slot **ret_slot, |
834 | | uint16_t type, |
835 | | sd_netlink_message_handler_t callback, |
836 | | sd_netlink_destroy_t destroy_callback, |
837 | | void *userdata, |
838 | 160k | const char *description) { |
839 | 160k | _cleanup_free_ sd_netlink_slot *slot = NULL; |
840 | 160k | int r; |
841 | 160k | |
842 | 160k | assert_return(rtnl, -EINVAL); |
843 | 160k | assert_return(callback, -EINVAL); |
844 | 160k | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
845 | 160k | |
846 | 160k | r = netlink_slot_allocate(rtnl, !ret_slot, NETLINK_MATCH_CALLBACK, sizeof(struct match_callback), userdata, description, &slot); |
847 | 160k | if (r < 0) |
848 | 0 | return r; |
849 | 160k | |
850 | 160k | slot->match_callback.callback = callback; |
851 | 160k | slot->match_callback.type = type; |
852 | 160k | |
853 | 160k | switch (type) { |
854 | 160k | case RTM_NEWLINK: |
855 | 40.0k | case RTM_DELLINK: |
856 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); |
857 | 40.0k | if (r < 0) |
858 | 0 | return r; |
859 | 40.0k | |
860 | 40.0k | break; |
861 | 40.0k | case RTM_NEWADDR: |
862 | 40.0k | case RTM_DELADDR: |
863 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); |
864 | 40.0k | if (r < 0) |
865 | 0 | return r; |
866 | 40.0k | |
867 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); |
868 | 40.0k | if (r < 0) |
869 | 0 | return r; |
870 | 40.0k | |
871 | 40.0k | break; |
872 | 40.0k | case RTM_NEWROUTE: |
873 | 40.0k | case RTM_DELROUTE: |
874 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); |
875 | 40.0k | if (r < 0) |
876 | 0 | return r; |
877 | 40.0k | |
878 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); |
879 | 40.0k | if (r < 0) |
880 | 0 | return r; |
881 | 40.0k | break; |
882 | 40.0k | case RTM_NEWRULE: |
883 | 40.0k | case RTM_DELRULE: |
884 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_RULE); |
885 | 40.0k | if (r < 0) |
886 | 0 | return r; |
887 | 40.0k | |
888 | 40.0k | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_RULE); |
889 | 40.0k | if (r < 0) |
890 | 0 | return r; |
891 | 40.0k | break; |
892 | 40.0k | default: |
893 | 0 | return -EOPNOTSUPP; |
894 | 160k | } |
895 | 160k | |
896 | 160k | LIST_PREPEND(match_callbacks, rtnl->match_callbacks, &slot->match_callback); |
897 | 160k | |
898 | 160k | /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ |
899 | 160k | slot->destroy_callback = destroy_callback; |
900 | 160k | |
901 | 160k | if (ret_slot) |
902 | 0 | *ret_slot = slot; |
903 | 160k | |
904 | 160k | TAKE_PTR(slot); |
905 | 160k | |
906 | 160k | return 0; |
907 | 160k | } |