]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
65f568bb | 2 | |
65f568bb | 3 | #include <poll.h> |
65f568bb | 4 | |
1c4baffc | 5 | #include "sd-netlink.h" |
07630cea | 6 | |
b5efdb8a | 7 | #include "alloc-util.h" |
3ffd4af2 | 8 | #include "fd-util.h" |
07630cea | 9 | #include "hashmap.h" |
0f2d351f | 10 | #include "io-util.h" |
07630cea | 11 | #include "macro.h" |
1c4baffc | 12 | #include "netlink-internal.h" |
ee38400b | 13 | #include "netlink-slot.h" |
1c4baffc | 14 | #include "netlink-util.h" |
dccca82b | 15 | #include "process-util.h" |
2583fbea | 16 | #include "socket-util.h" |
8190a388 | 17 | #include "string-util.h" |
07630cea | 18 | #include "util.h" |
65f568bb | 19 | |
b522c4b9 LP |
20 | /* Some really high limit, to catch programming errors */ |
21 | #define REPLY_CALLBACKS_MAX UINT16_MAX | |
22 | ||
1c4baffc | 23 | static int sd_netlink_new(sd_netlink **ret) { |
4afd3348 | 24 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
65f568bb TG |
25 | |
26 | assert_return(ret, -EINVAL); | |
27 | ||
2fea6090 | 28 | rtnl = new(sd_netlink, 1); |
65f568bb TG |
29 | if (!rtnl) |
30 | return -ENOMEM; | |
31 | ||
2fea6090 | 32 | *rtnl = (sd_netlink) { |
f23ab4dc | 33 | .n_ref = 1, |
2fea6090 YW |
34 | .fd = -1, |
35 | .sockaddr.nl.nl_family = AF_NETLINK, | |
36 | .original_pid = getpid_cached(), | |
37 | .protocol = -1, | |
adf412b9 | 38 | |
ac3bc1b8 LP |
39 | /* Kernel change notification messages have sequence number 0. We want to avoid that with our |
40 | * own serials, in order not to get confused when matching up kernel replies to our earlier | |
41 | * requests. | |
42 | * | |
43 | * Moreover, when using netlink socket activation (i.e. where PID 1 binds an AF_NETLINK | |
44 | * socket for us and passes it to us across execve()) and we get restarted multiple times | |
45 | * while the socket sticks around we might get confused by replies from earlier runs coming | |
46 | * in late — which is pretty likely if we'd start our sequence numbers always from 1. Hence, | |
47 | * let's start with a value based on the system clock. This should make collisions much less | |
48 | * likely (though still theoretically possible). We use a 32 bit µs counter starting at boot | |
49 | * for this (and explicitly exclude the zero, see above). This counter will wrap around after | |
50 | * a bit more than 1h, but that's hopefully OK as the kernel shouldn't take that long to | |
51 | * reply to our requests. | |
52 | * | |
53 | * We only pick the initial start value this way. For each message we simply increase the | |
54 | * sequence number by 1. This means we could enqueue 1 netlink message per µs without risking | |
55 | * collisions, which should be OK. | |
56 | * | |
57 | * Note this means the serials will be in the range 1…UINT32_MAX here. | |
58 | * | |
59 | * (In an ideal world we'd attach the current serial counter to the netlink socket itself | |
60 | * somehow, to avoid all this, but I couldn't come up with a nice way to do this) */ | |
61 | .serial = (uint32_t) (now(CLOCK_MONOTONIC) % UINT32_MAX) + 1, | |
2fea6090 | 62 | }; |
8cec01b9 | 63 | |
a88f77c4 TG |
64 | /* We guarantee that the read buffer has at least space for |
65 | * a message header */ | |
66 | if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, | |
67 | sizeof(struct nlmsghdr), sizeof(uint8_t))) | |
68 | return -ENOMEM; | |
69 | ||
1cc6c93a | 70 | *ret = TAKE_PTR(rtnl); |
689703f6 | 71 | |
65f568bb TG |
72 | return 0; |
73 | } | |
74 | ||
1c4baffc | 75 | int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { |
4afd3348 | 76 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
a9944163 TG |
77 | socklen_t addrlen; |
78 | int r; | |
79 | ||
80 | assert_return(ret, -EINVAL); | |
81 | ||
1c4baffc | 82 | r = sd_netlink_new(&rtnl); |
a9944163 TG |
83 | if (r < 0) |
84 | return r; | |
85 | ||
86 | addrlen = sizeof(rtnl->sockaddr); | |
87 | ||
88 | r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); | |
89 | if (r < 0) | |
90 | return -errno; | |
91 | ||
d6c16624 LP |
92 | if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) |
93 | return -EINVAL; | |
94 | ||
a9944163 TG |
95 | rtnl->fd = fd; |
96 | ||
1cc6c93a | 97 | *ret = TAKE_PTR(rtnl); |
a9944163 TG |
98 | |
99 | return 0; | |
100 | } | |
101 | ||
de157fe2 | 102 | static bool rtnl_pid_changed(const sd_netlink *rtnl) { |
adf412b9 TG |
103 | assert(rtnl); |
104 | ||
105 | /* We don't support people creating an rtnl connection and | |
106 | * keeping it around over a fork(). Let's complain. */ | |
107 | ||
df0ff127 | 108 | return rtnl->original_pid != getpid_cached(); |
adf412b9 TG |
109 | } |
110 | ||
1c4baffc | 111 | int sd_netlink_open_fd(sd_netlink **ret, int fd) { |
4afd3348 | 112 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; |
13ec9f10 | 113 | int r, protocol; |
65f568bb | 114 | |
9d0db178 | 115 | assert_return(ret, -EINVAL); |
8ac43fee | 116 | assert_return(fd >= 0, -EBADF); |
9d0db178 | 117 | |
1c4baffc | 118 | r = sd_netlink_new(&rtnl); |
65f568bb TG |
119 | if (r < 0) |
120 | return r; | |
121 | ||
13ec9f10 | 122 | r = getsockopt_int(fd, SOL_SOCKET, SO_PROTOCOL, &protocol); |
05d0c2e3 JT |
123 | if (r < 0) |
124 | return r; | |
125 | ||
b95cc756 | 126 | rtnl->fd = fd; |
05d0c2e3 | 127 | rtnl->protocol = protocol; |
65f568bb | 128 | |
e4a1e68d YW |
129 | r = setsockopt_int(fd, SOL_NETLINK, NETLINK_EXT_ACK, 1); |
130 | if (r < 0) | |
131 | log_debug_errno(r, "sd-netlink: Failed to enable NETLINK_EXT_ACK option, ignoring: %m"); | |
132 | ||
b95cc756 | 133 | r = socket_bind(rtnl); |
5c60db87 LP |
134 | if (r < 0) { |
135 | rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ | |
05d0c2e3 | 136 | rtnl->protocol = -1; |
b95cc756 | 137 | return r; |
5c60db87 | 138 | } |
6d0b55c2 | 139 | |
1cc6c93a | 140 | *ret = TAKE_PTR(rtnl); |
65f568bb TG |
141 | |
142 | return 0; | |
143 | } | |
144 | ||
05d0c2e3 | 145 | int netlink_open_family(sd_netlink **ret, int family) { |
31710be5 | 146 | _cleanup_close_ int fd = -1; |
6d0b55c2 LP |
147 | int r; |
148 | ||
05d0c2e3 | 149 | fd = socket_open(family); |
6d0b55c2 | 150 | if (fd < 0) |
b95cc756 | 151 | return fd; |
6d0b55c2 | 152 | |
1c4baffc | 153 | r = sd_netlink_open_fd(ret, fd); |
31710be5 | 154 | if (r < 0) |
6d0b55c2 | 155 | return r; |
d52e1c42 | 156 | TAKE_FD(fd); |
31710be5 TG |
157 | |
158 | return 0; | |
159 | } | |
160 | ||
05d0c2e3 JT |
161 | int sd_netlink_open(sd_netlink **ret) { |
162 | return netlink_open_family(ret, NETLINK_ROUTE); | |
163 | } | |
164 | ||
75f8a779 LP |
165 | int sd_netlink_inc_rcvbuf(sd_netlink *rtnl, size_t size) { |
166 | assert_return(rtnl, -EINVAL); | |
167 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
168 | ||
be660c37 AR |
169 | return fd_inc_rcvbuf(rtnl->fd, size); |
170 | } | |
171 | ||
1c71f7f3 | 172 | static sd_netlink *netlink_free(sd_netlink *rtnl) { |
ee38400b | 173 | sd_netlink_slot *s; |
1c71f7f3 | 174 | unsigned i; |
8c578303 | 175 | |
1c71f7f3 | 176 | assert(rtnl); |
4555ec72 | 177 | |
1c71f7f3 YW |
178 | for (i = 0; i < rtnl->rqueue_size; i++) |
179 | sd_netlink_message_unref(rtnl->rqueue[i]); | |
180 | free(rtnl->rqueue); | |
8cec01b9 | 181 | |
1c71f7f3 YW |
182 | for (i = 0; i < rtnl->rqueue_partial_size; i++) |
183 | sd_netlink_message_unref(rtnl->rqueue_partial[i]); | |
184 | free(rtnl->rqueue_partial); | |
4e996881 | 185 | |
1c71f7f3 | 186 | free(rtnl->rbuffer); |
a88f77c4 | 187 | |
ee38400b YW |
188 | while ((s = rtnl->slots)) { |
189 | assert(s->floating); | |
190 | netlink_slot_disconnect(s, true); | |
545bab1f YW |
191 | } |
192 | hashmap_free(rtnl->reply_callbacks); | |
1c71f7f3 | 193 | prioq_free(rtnl->reply_callbacks_prioq); |
22fdeadc | 194 | |
1c71f7f3 YW |
195 | sd_event_source_unref(rtnl->io_event_source); |
196 | sd_event_source_unref(rtnl->time_event_source); | |
197 | sd_event_unref(rtnl->event); | |
22fdeadc | 198 | |
1c71f7f3 | 199 | hashmap_free(rtnl->broadcast_group_refs); |
9c5a882b | 200 | |
4e8f0ef9 YW |
201 | hashmap_free(rtnl->genl_family_to_nlmsg_type); |
202 | hashmap_free(rtnl->nlmsg_type_to_genl_family); | |
203 | ||
1c71f7f3 YW |
204 | safe_close(rtnl->fd); |
205 | return mfree(rtnl); | |
65f568bb TG |
206 | } |
207 | ||
f23ab4dc | 208 | DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_netlink, sd_netlink, netlink_free); |
1c71f7f3 | 209 | |
1c4baffc | 210 | static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { |
b522c4b9 LP |
211 | uint32_t picked; |
212 | ||
3dd215e0 TG |
213 | assert(rtnl); |
214 | assert(!rtnl_pid_changed(rtnl)); | |
215 | assert(m); | |
216 | assert(m->hdr); | |
217 | ||
b522c4b9 LP |
218 | /* Avoid collisions with outstanding requests */ |
219 | do { | |
220 | picked = rtnl->serial; | |
3dd215e0 | 221 | |
b522c4b9 LP |
222 | /* Don't use seq == 0, as that is used for broadcasts, so we would get confused by replies to |
223 | such messages */ | |
224 | rtnl->serial = rtnl->serial == UINT32_MAX ? 1 : rtnl->serial + 1; | |
225 | ||
226 | } while (hashmap_contains(rtnl->reply_callbacks, UINT32_TO_PTR(picked))); | |
3dd215e0 | 227 | |
b522c4b9 LP |
228 | m->hdr->nlmsg_seq = picked; |
229 | rtnl_message_seal(m); | |
3dd215e0 TG |
230 | } |
231 | ||
1c4baffc | 232 | int sd_netlink_send(sd_netlink *nl, |
6b6e0741 YW |
233 | sd_netlink_message *message, |
234 | uint32_t *serial) { | |
4555ec72 | 235 | int r; |
65f568bb TG |
236 | |
237 | assert_return(nl, -EINVAL); | |
adf412b9 | 238 | assert_return(!rtnl_pid_changed(nl), -ECHILD); |
65f568bb | 239 | assert_return(message, -EINVAL); |
3dd215e0 | 240 | assert_return(!message->sealed, -EPERM); |
65f568bb | 241 | |
3dd215e0 | 242 | rtnl_seal_message(nl, message); |
65f568bb | 243 | |
bbe181b4 TG |
244 | r = socket_write_message(nl, message); |
245 | if (r < 0) | |
246 | return r; | |
65f568bb | 247 | |
4555ec72 | 248 | if (serial) |
3815f36f | 249 | *serial = rtnl_message_get_serial(message); |
65f568bb | 250 | |
4555ec72 TG |
251 | return 1; |
252 | } | |
65f568bb | 253 | |
99c41c0d | 254 | int sd_netlink_sendv(sd_netlink *nl, |
2d1ad724 | 255 | sd_netlink_message **messages, |
99c41c0d FW |
256 | size_t msgcount, |
257 | uint32_t **ret_serial) { | |
258 | _cleanup_free_ uint32_t *serials = NULL; | |
259 | unsigned i; | |
260 | int r; | |
261 | ||
262 | assert_return(nl, -EINVAL); | |
263 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
264 | assert_return(messages, -EINVAL); | |
f6dab748 | 265 | assert_return(msgcount > 0, -EINVAL); |
99c41c0d FW |
266 | |
267 | if (ret_serial) { | |
268 | serials = new0(uint32_t, msgcount); | |
269 | if (!serials) | |
270 | return -ENOMEM; | |
271 | } | |
272 | ||
273 | for (i = 0; i < msgcount; i++) { | |
274 | assert_return(!messages[i]->sealed, -EPERM); | |
275 | rtnl_seal_message(nl, messages[i]); | |
276 | if (serials) | |
277 | serials[i] = rtnl_message_get_serial(messages[i]); | |
278 | } | |
279 | ||
280 | r = socket_writev_message(nl, messages, msgcount); | |
281 | if (r < 0) | |
282 | return r; | |
283 | ||
284 | if (ret_serial) | |
285 | *ret_serial = TAKE_PTR(serials); | |
286 | ||
287 | return r; | |
288 | } | |
289 | ||
1c4baffc | 290 | int rtnl_rqueue_make_room(sd_netlink *rtnl) { |
1b89cf56 TG |
291 | assert(rtnl); |
292 | ||
baaa35ad ZJS |
293 | if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) |
294 | return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), | |
295 | "rtnl: exhausted the read queue size (%d)", | |
296 | RTNL_RQUEUE_MAX); | |
1b89cf56 TG |
297 | |
298 | if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) | |
299 | return -ENOMEM; | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
1c4baffc | 304 | int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { |
4e996881 TG |
305 | assert(rtnl); |
306 | ||
baaa35ad ZJS |
307 | if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) |
308 | return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), | |
309 | "rtnl: exhausted the partial read queue size (%d)", | |
310 | RTNL_RQUEUE_MAX); | |
4e996881 TG |
311 | |
312 | if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, | |
313 | rtnl->rqueue_partial_size + 1)) | |
314 | return -ENOMEM; | |
315 | ||
316 | return 0; | |
317 | } | |
318 | ||
1c4baffc | 319 | static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { |
4555ec72 | 320 | int r; |
65f568bb | 321 | |
4555ec72 TG |
322 | assert(rtnl); |
323 | assert(message); | |
324 | ||
1b89cf56 TG |
325 | if (rtnl->rqueue_size <= 0) { |
326 | /* Try to read a new message */ | |
327 | r = socket_read_message(rtnl); | |
71994cff LP |
328 | if (r == -ENOBUFS) { /* FIXME: ignore buffer overruns for now */ |
329 | log_debug_errno(r, "Got ENOBUFS from netlink socket, ignoring."); | |
330 | return 1; | |
331 | } | |
1b89cf56 TG |
332 | if (r <= 0) |
333 | return r; | |
4555ec72 TG |
334 | } |
335 | ||
1b89cf56 TG |
336 | /* Dispatch a queued message */ |
337 | *message = rtnl->rqueue[0]; | |
313cefa1 | 338 | rtnl->rqueue_size--; |
1c4baffc | 339 | memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); |
4555ec72 TG |
340 | |
341 | return 1; | |
342 | } | |
343 | ||
1c4baffc | 344 | static int process_timeout(sd_netlink *rtnl) { |
4afd3348 | 345 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
e16bcf98 | 346 | struct reply_callback *c; |
ee38400b | 347 | sd_netlink_slot *slot; |
e16bcf98 TG |
348 | usec_t n; |
349 | int r; | |
350 | ||
351 | assert(rtnl); | |
352 | ||
353 | c = prioq_peek(rtnl->reply_callbacks_prioq); | |
354 | if (!c) | |
355 | return 0; | |
356 | ||
357 | n = now(CLOCK_MONOTONIC); | |
358 | if (c->timeout > n) | |
359 | return 0; | |
360 | ||
05d0c2e3 | 361 | r = rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, c->serial, &m); |
e16bcf98 TG |
362 | if (r < 0) |
363 | return r; | |
364 | ||
365 | assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); | |
ee38400b | 366 | c->timeout = 0; |
b522c4b9 | 367 | hashmap_remove(rtnl->reply_callbacks, UINT32_TO_PTR(c->serial)); |
e16bcf98 | 368 | |
ee38400b YW |
369 | slot = container_of(c, sd_netlink_slot, reply_callback); |
370 | ||
371 | r = c->callback(rtnl, m, slot->userdata); | |
233ba5c3 | 372 | if (r < 0) |
8190a388 YW |
373 | log_debug_errno(r, "sd-netlink: timedout callback %s%s%sfailed: %m", |
374 | slot->description ? "'" : "", | |
375 | strempty(slot->description), | |
376 | slot->description ? "' " : ""); | |
233ba5c3 | 377 | |
ee38400b YW |
378 | if (slot->floating) |
379 | netlink_slot_disconnect(slot, true); | |
e16bcf98 | 380 | |
233ba5c3 | 381 | return 1; |
e16bcf98 TG |
382 | } |
383 | ||
1c4baffc | 384 | static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { |
ee38400b YW |
385 | struct reply_callback *c; |
386 | sd_netlink_slot *slot; | |
b522c4b9 | 387 | uint32_t serial; |
ea342a99 | 388 | uint16_t type; |
e16bcf98 TG |
389 | int r; |
390 | ||
391 | assert(rtnl); | |
392 | assert(m); | |
393 | ||
3815f36f | 394 | serial = rtnl_message_get_serial(m); |
b522c4b9 | 395 | c = hashmap_remove(rtnl->reply_callbacks, UINT32_TO_PTR(serial)); |
e16bcf98 TG |
396 | if (!c) |
397 | return 0; | |
398 | ||
ee38400b | 399 | if (c->timeout != 0) { |
e16bcf98 | 400 | prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); |
ee38400b YW |
401 | c->timeout = 0; |
402 | } | |
e16bcf98 | 403 | |
1c4baffc | 404 | r = sd_netlink_message_get_type(m, &type); |
ea342a99 | 405 | if (r < 0) |
ee38400b | 406 | return r; |
ea342a99 AR |
407 | |
408 | if (type == NLMSG_DONE) | |
409 | m = NULL; | |
410 | ||
ee38400b YW |
411 | slot = container_of(c, sd_netlink_slot, reply_callback); |
412 | ||
413 | r = c->callback(rtnl, m, slot->userdata); | |
233ba5c3 | 414 | if (r < 0) |
8190a388 YW |
415 | log_debug_errno(r, "sd-netlink: reply callback %s%s%sfailed: %m", |
416 | slot->description ? "'" : "", | |
417 | strempty(slot->description), | |
418 | slot->description ? "' " : ""); | |
233ba5c3 | 419 | |
ee38400b YW |
420 | if (slot->floating) |
421 | netlink_slot_disconnect(slot, true); | |
545bab1f | 422 | |
233ba5c3 | 423 | return 1; |
e16bcf98 TG |
424 | } |
425 | ||
1c4baffc | 426 | static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { |
8cec01b9 | 427 | struct match_callback *c; |
ee38400b | 428 | sd_netlink_slot *slot; |
8cec01b9 TG |
429 | uint16_t type; |
430 | int r; | |
431 | ||
432 | assert(rtnl); | |
433 | assert(m); | |
434 | ||
1c4baffc | 435 | r = sd_netlink_message_get_type(m, &type); |
8cec01b9 TG |
436 | if (r < 0) |
437 | return r; | |
438 | ||
439 | LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { | |
baf78f1a LP |
440 | if (type != c->type) |
441 | continue; | |
442 | ||
443 | slot = container_of(c, sd_netlink_slot, match_callback); | |
444 | ||
445 | r = c->callback(rtnl, m, slot->userdata); | |
446 | if (r < 0) | |
447 | log_debug_errno(r, "sd-netlink: match callback %s%s%sfailed: %m", | |
448 | slot->description ? "'" : "", | |
449 | strempty(slot->description), | |
450 | slot->description ? "' " : ""); | |
451 | if (r != 0) | |
452 | break; | |
8cec01b9 TG |
453 | } |
454 | ||
233ba5c3 | 455 | return 1; |
8cec01b9 TG |
456 | } |
457 | ||
1c4baffc | 458 | static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { |
4afd3348 | 459 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
4555ec72 TG |
460 | int r; |
461 | ||
9d0db178 TG |
462 | assert(rtnl); |
463 | ||
e16bcf98 TG |
464 | r = process_timeout(rtnl); |
465 | if (r != 0) | |
466 | goto null_message; | |
467 | ||
4555ec72 TG |
468 | r = dispatch_rqueue(rtnl, &m); |
469 | if (r < 0) | |
470 | return r; | |
471 | if (!m) | |
472 | goto null_message; | |
473 | ||
1c4baffc | 474 | if (sd_netlink_message_is_broadcast(m)) { |
f436aa11 TG |
475 | r = process_match(rtnl, m); |
476 | if (r != 0) | |
477 | goto null_message; | |
478 | } else { | |
479 | r = process_reply(rtnl, m); | |
480 | if (r != 0) | |
481 | goto null_message; | |
482 | } | |
8cec01b9 | 483 | |
4555ec72 | 484 | if (ret) { |
1cc6c93a | 485 | *ret = TAKE_PTR(m); |
4555ec72 TG |
486 | |
487 | return 1; | |
488 | } | |
489 | ||
490 | return 1; | |
491 | ||
492 | null_message: | |
493 | if (r >= 0 && ret) | |
494 | *ret = NULL; | |
495 | ||
496 | return r; | |
497 | } | |
e16bcf98 | 498 | |
1c4baffc | 499 | int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { |
4afd3348 | 500 | NETLINK_DONT_DESTROY(rtnl); |
4555ec72 TG |
501 | int r; |
502 | ||
503 | assert_return(rtnl, -EINVAL); | |
504 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
505 | assert_return(!rtnl->processing, -EBUSY); | |
506 | ||
507 | rtnl->processing = true; | |
508 | r = process_running(rtnl, ret); | |
509 | rtnl->processing = false; | |
510 | ||
511 | return r; | |
512 | } | |
513 | ||
514 | static usec_t calc_elapse(uint64_t usec) { | |
515 | if (usec == (uint64_t) -1) | |
516 | return 0; | |
517 | ||
518 | if (usec == 0) | |
519 | usec = RTNL_DEFAULT_TIMEOUT; | |
520 | ||
521 | return now(CLOCK_MONOTONIC) + usec; | |
522 | } | |
523 | ||
1c4baffc | 524 | static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { |
3a43da28 | 525 | usec_t m = USEC_INFINITY; |
b4f2a5b1 TG |
526 | int r, e; |
527 | ||
528 | assert(rtnl); | |
4555ec72 | 529 | |
1c4baffc | 530 | e = sd_netlink_get_events(rtnl); |
b4f2a5b1 TG |
531 | if (e < 0) |
532 | return e; | |
4555ec72 | 533 | |
b4f2a5b1 TG |
534 | if (need_more) |
535 | /* Caller wants more data, and doesn't care about | |
536 | * what's been read or any other timeouts. */ | |
f55dc7c9 | 537 | e |= POLLIN; |
b4f2a5b1 TG |
538 | else { |
539 | usec_t until; | |
540 | /* Caller wants to process if there is something to | |
541 | * process, but doesn't care otherwise */ | |
542 | ||
1c4baffc | 543 | r = sd_netlink_get_timeout(rtnl, &until); |
b4f2a5b1 TG |
544 | if (r < 0) |
545 | return r; | |
546 | if (r > 0) { | |
547 | usec_t nw; | |
548 | nw = now(CLOCK_MONOTONIC); | |
549 | m = until > nw ? until - nw : 0; | |
550 | } | |
551 | } | |
65f568bb | 552 | |
0f2d351f | 553 | if (timeout_usec != (uint64_t) -1 && (m == USEC_INFINITY || timeout_usec < m)) |
b4f2a5b1 TG |
554 | m = timeout_usec; |
555 | ||
0f2d351f | 556 | r = fd_wait_for_event(rtnl->fd, e, m); |
8d16f29b | 557 | if (r <= 0) |
0f2d351f | 558 | return r; |
4555ec72 | 559 | |
dad28bff | 560 | return 1; |
4555ec72 TG |
561 | } |
562 | ||
1c4baffc | 563 | int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { |
4555ec72 TG |
564 | assert_return(nl, -EINVAL); |
565 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
566 | ||
567 | if (nl->rqueue_size > 0) | |
568 | return 0; | |
569 | ||
b4f2a5b1 | 570 | return rtnl_poll(nl, false, timeout_usec); |
4555ec72 TG |
571 | } |
572 | ||
e16bcf98 TG |
573 | static int timeout_compare(const void *a, const void *b) { |
574 | const struct reply_callback *x = a, *y = b; | |
575 | ||
576 | if (x->timeout != 0 && y->timeout == 0) | |
577 | return -1; | |
578 | ||
579 | if (x->timeout == 0 && y->timeout != 0) | |
580 | return 1; | |
581 | ||
9c57a73b | 582 | return CMP(x->timeout, y->timeout); |
e16bcf98 TG |
583 | } |
584 | ||
545bab1f YW |
585 | int sd_netlink_call_async( |
586 | sd_netlink *nl, | |
ee38400b | 587 | sd_netlink_slot **ret_slot, |
545bab1f YW |
588 | sd_netlink_message *m, |
589 | sd_netlink_message_handler_t callback, | |
590 | sd_netlink_destroy_t destroy_callback, | |
591 | void *userdata, | |
8190a388 YW |
592 | uint64_t usec, |
593 | const char *description) { | |
ee38400b | 594 | _cleanup_free_ sd_netlink_slot *slot = NULL; |
e16bcf98 TG |
595 | int r, k; |
596 | ||
597 | assert_return(nl, -EINVAL); | |
598 | assert_return(m, -EINVAL); | |
599 | assert_return(callback, -EINVAL); | |
600 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
601 | ||
b522c4b9 LP |
602 | if (hashmap_size(nl->reply_callbacks) >= REPLY_CALLBACKS_MAX) |
603 | return -ERANGE; | |
604 | ||
605 | r = hashmap_ensure_allocated(&nl->reply_callbacks, &trivial_hash_ops); | |
e16bcf98 TG |
606 | if (r < 0) |
607 | return r; | |
608 | ||
609 | if (usec != (uint64_t) -1) { | |
610 | r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); | |
611 | if (r < 0) | |
612 | return r; | |
613 | } | |
614 | ||
5cd67116 | 615 | r = netlink_slot_allocate(nl, !ret_slot, NETLINK_REPLY_CALLBACK, sizeof(struct reply_callback), userdata, description, &slot); |
8190a388 YW |
616 | if (r < 0) |
617 | return r; | |
e16bcf98 | 618 | |
ee38400b YW |
619 | slot->reply_callback.callback = callback; |
620 | slot->reply_callback.timeout = calc_elapse(usec); | |
e16bcf98 | 621 | |
b522c4b9 | 622 | k = sd_netlink_send(nl, m, &slot->reply_callback.serial); |
545bab1f | 623 | if (k < 0) |
e16bcf98 | 624 | return k; |
e16bcf98 | 625 | |
b522c4b9 | 626 | r = hashmap_put(nl->reply_callbacks, UINT32_TO_PTR(slot->reply_callback.serial), &slot->reply_callback); |
545bab1f | 627 | if (r < 0) |
e16bcf98 | 628 | return r; |
e16bcf98 | 629 | |
ee38400b YW |
630 | if (slot->reply_callback.timeout != 0) { |
631 | r = prioq_put(nl->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); | |
f6bdbd9e | 632 | if (r < 0) { |
b522c4b9 | 633 | (void) hashmap_remove(nl->reply_callbacks, UINT32_TO_PTR(slot->reply_callback.serial)); |
e16bcf98 TG |
634 | return r; |
635 | } | |
636 | } | |
637 | ||
5cd67116 YW |
638 | /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ |
639 | slot->destroy_callback = destroy_callback; | |
640 | ||
ee38400b YW |
641 | if (ret_slot) |
642 | *ret_slot = slot; | |
e16bcf98 | 643 | |
ee38400b | 644 | TAKE_PTR(slot); |
545bab1f | 645 | |
e16bcf98 TG |
646 | return k; |
647 | } | |
648 | ||
4df42cd9 FW |
649 | int sd_netlink_read(sd_netlink *rtnl, |
650 | uint32_t serial, | |
651 | uint64_t usec, | |
652 | sd_netlink_message **ret) { | |
4555ec72 | 653 | usec_t timeout; |
4555ec72 TG |
654 | int r; |
655 | ||
1b89cf56 TG |
656 | assert_return(rtnl, -EINVAL); |
657 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
4555ec72 TG |
658 | |
659 | timeout = calc_elapse(usec); | |
660 | ||
65f568bb | 661 | for (;;) { |
4555ec72 | 662 | usec_t left; |
ea342a99 | 663 | unsigned i; |
65f568bb | 664 | |
ea342a99 | 665 | for (i = 0; i < rtnl->rqueue_size; i++) { |
1b89cf56 | 666 | uint32_t received_serial; |
65f568bb | 667 | |
ea342a99 | 668 | received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); |
65f568bb TG |
669 | |
670 | if (received_serial == serial) { | |
4afd3348 | 671 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; |
ea342a99 AR |
672 | uint16_t type; |
673 | ||
674 | incoming = rtnl->rqueue[i]; | |
675 | ||
1b89cf56 TG |
676 | /* found a match, remove from rqueue and return it */ |
677 | memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, | |
1c4baffc | 678 | sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); |
1b89cf56 TG |
679 | rtnl->rqueue_size--; |
680 | ||
1c4baffc | 681 | r = sd_netlink_message_get_errno(incoming); |
ea342a99 AR |
682 | if (r < 0) |
683 | return r; | |
684 | ||
1c4baffc | 685 | r = sd_netlink_message_get_type(incoming, &type); |
ea342a99 | 686 | if (r < 0) |
65f568bb | 687 | return r; |
ea342a99 AR |
688 | |
689 | if (type == NLMSG_DONE) { | |
690 | *ret = NULL; | |
691 | return 0; | |
1b89cf56 | 692 | } |
65f568bb | 693 | |
1cc6c93a YW |
694 | if (ret) |
695 | *ret = TAKE_PTR(incoming); | |
65f568bb | 696 | |
4555ec72 | 697 | return 1; |
65f568bb | 698 | } |
65f568bb | 699 | } |
1b89cf56 TG |
700 | |
701 | r = socket_read_message(rtnl); | |
702 | if (r < 0) | |
703 | return r; | |
704 | if (r > 0) | |
6ff8806e | 705 | /* received message, so try to process straight away */ |
4555ec72 | 706 | continue; |
65f568bb | 707 | |
4555ec72 TG |
708 | if (timeout > 0) { |
709 | usec_t n; | |
710 | ||
711 | n = now(CLOCK_MONOTONIC); | |
712 | if (n >= timeout) | |
713 | return -ETIMEDOUT; | |
714 | ||
715 | left = timeout - n; | |
716 | } else | |
717 | left = (uint64_t) -1; | |
718 | ||
1b89cf56 | 719 | r = rtnl_poll(rtnl, true, left); |
4555ec72 TG |
720 | if (r < 0) |
721 | return r; | |
b551ddd3 TG |
722 | else if (r == 0) |
723 | return -ETIMEDOUT; | |
b4f2a5b1 TG |
724 | } |
725 | } | |
726 | ||
4df42cd9 FW |
727 | int sd_netlink_call(sd_netlink *rtnl, |
728 | sd_netlink_message *message, | |
729 | uint64_t usec, | |
730 | sd_netlink_message **ret) { | |
731 | uint32_t serial; | |
732 | int r; | |
733 | ||
734 | assert_return(rtnl, -EINVAL); | |
735 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
736 | assert_return(message, -EINVAL); | |
737 | ||
738 | r = sd_netlink_send(rtnl, message, &serial); | |
739 | if (r < 0) | |
740 | return r; | |
741 | ||
742 | return sd_netlink_read(rtnl, serial, usec, ret); | |
743 | } | |
744 | ||
de157fe2 | 745 | int sd_netlink_get_events(const sd_netlink *rtnl) { |
b4f2a5b1 TG |
746 | assert_return(rtnl, -EINVAL); |
747 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
748 | ||
bbe181b4 TG |
749 | if (rtnl->rqueue_size == 0) |
750 | return POLLIN; | |
751 | else | |
752 | return 0; | |
b4f2a5b1 TG |
753 | } |
754 | ||
de157fe2 | 755 | int sd_netlink_get_timeout(const sd_netlink *rtnl, uint64_t *timeout_usec) { |
b4f2a5b1 TG |
756 | struct reply_callback *c; |
757 | ||
758 | assert_return(rtnl, -EINVAL); | |
759 | assert_return(timeout_usec, -EINVAL); | |
760 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
761 | ||
762 | if (rtnl->rqueue_size > 0) { | |
763 | *timeout_usec = 0; | |
764 | return 1; | |
765 | } | |
766 | ||
767 | c = prioq_peek(rtnl->reply_callbacks_prioq); | |
768 | if (!c) { | |
769 | *timeout_usec = (uint64_t) -1; | |
770 | return 0; | |
771 | } | |
772 | ||
773 | *timeout_usec = c->timeout; | |
774 | ||
775 | return 1; | |
776 | } | |
777 | ||
778 | static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
1c4baffc | 779 | sd_netlink *rtnl = userdata; |
b4f2a5b1 TG |
780 | int r; |
781 | ||
782 | assert(rtnl); | |
783 | ||
1c4baffc | 784 | r = sd_netlink_process(rtnl, NULL); |
b4f2a5b1 TG |
785 | if (r < 0) |
786 | return r; | |
787 | ||
788 | return 1; | |
789 | } | |
790 | ||
791 | static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { | |
1c4baffc | 792 | sd_netlink *rtnl = userdata; |
b4f2a5b1 TG |
793 | int r; |
794 | ||
795 | assert(rtnl); | |
796 | ||
1c4baffc | 797 | r = sd_netlink_process(rtnl, NULL); |
b4f2a5b1 TG |
798 | if (r < 0) |
799 | return r; | |
800 | ||
801 | return 1; | |
802 | } | |
803 | ||
804 | static int prepare_callback(sd_event_source *s, void *userdata) { | |
1c4baffc | 805 | sd_netlink *rtnl = userdata; |
b4f2a5b1 TG |
806 | int r, e; |
807 | usec_t until; | |
808 | ||
809 | assert(s); | |
810 | assert(rtnl); | |
811 | ||
1c4baffc | 812 | e = sd_netlink_get_events(rtnl); |
b4f2a5b1 TG |
813 | if (e < 0) |
814 | return e; | |
815 | ||
816 | r = sd_event_source_set_io_events(rtnl->io_event_source, e); | |
817 | if (r < 0) | |
818 | return r; | |
819 | ||
1c4baffc | 820 | r = sd_netlink_get_timeout(rtnl, &until); |
b4f2a5b1 TG |
821 | if (r < 0) |
822 | return r; | |
823 | if (r > 0) { | |
824 | int j; | |
825 | ||
826 | j = sd_event_source_set_time(rtnl->time_event_source, until); | |
827 | if (j < 0) | |
828 | return j; | |
829 | } | |
830 | ||
831 | r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); | |
832 | if (r < 0) | |
833 | return r; | |
834 | ||
835 | return 1; | |
836 | } | |
837 | ||
32d20645 | 838 | int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int64_t priority) { |
b4f2a5b1 TG |
839 | int r; |
840 | ||
841 | assert_return(rtnl, -EINVAL); | |
842 | assert_return(!rtnl->event, -EBUSY); | |
843 | ||
844 | assert(!rtnl->io_event_source); | |
845 | assert(!rtnl->time_event_source); | |
846 | ||
847 | if (event) | |
848 | rtnl->event = sd_event_ref(event); | |
849 | else { | |
850 | r = sd_event_default(&rtnl->event); | |
851 | if (r < 0) | |
852 | return r; | |
853 | } | |
854 | ||
151b9b96 | 855 | r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); |
b4f2a5b1 TG |
856 | if (r < 0) |
857 | goto fail; | |
858 | ||
859 | r = sd_event_source_set_priority(rtnl->io_event_source, priority); | |
860 | if (r < 0) | |
861 | goto fail; | |
862 | ||
356779df | 863 | r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); |
9021bb9f TG |
864 | if (r < 0) |
865 | goto fail; | |
866 | ||
b4f2a5b1 TG |
867 | r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); |
868 | if (r < 0) | |
869 | goto fail; | |
870 | ||
6a0f1f6d | 871 | r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); |
b4f2a5b1 TG |
872 | if (r < 0) |
873 | goto fail; | |
874 | ||
875 | r = sd_event_source_set_priority(rtnl->time_event_source, priority); | |
876 | if (r < 0) | |
877 | goto fail; | |
878 | ||
356779df | 879 | r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); |
9021bb9f TG |
880 | if (r < 0) |
881 | goto fail; | |
882 | ||
b4f2a5b1 TG |
883 | return 0; |
884 | ||
885 | fail: | |
1c4baffc | 886 | sd_netlink_detach_event(rtnl); |
b4f2a5b1 TG |
887 | return r; |
888 | } | |
889 | ||
1c4baffc | 890 | int sd_netlink_detach_event(sd_netlink *rtnl) { |
b4f2a5b1 TG |
891 | assert_return(rtnl, -EINVAL); |
892 | assert_return(rtnl->event, -ENXIO); | |
893 | ||
bbe181b4 | 894 | rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); |
b4f2a5b1 | 895 | |
bbe181b4 | 896 | rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); |
b4f2a5b1 | 897 | |
bbe181b4 | 898 | rtnl->event = sd_event_unref(rtnl->event); |
b4f2a5b1 TG |
899 | |
900 | return 0; | |
901 | } | |
8cec01b9 | 902 | |
ee38400b YW |
903 | int sd_netlink_add_match( |
904 | sd_netlink *rtnl, | |
905 | sd_netlink_slot **ret_slot, | |
906 | uint16_t type, | |
907 | sd_netlink_message_handler_t callback, | |
908 | sd_netlink_destroy_t destroy_callback, | |
8190a388 YW |
909 | void *userdata, |
910 | const char *description) { | |
ee38400b | 911 | _cleanup_free_ sd_netlink_slot *slot = NULL; |
31710be5 | 912 | int r; |
8cec01b9 TG |
913 | |
914 | assert_return(rtnl, -EINVAL); | |
915 | assert_return(callback, -EINVAL); | |
8cec01b9 TG |
916 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
917 | ||
5cd67116 | 918 | r = netlink_slot_allocate(rtnl, !ret_slot, NETLINK_MATCH_CALLBACK, sizeof(struct match_callback), userdata, description, &slot); |
8190a388 YW |
919 | if (r < 0) |
920 | return r; | |
8cec01b9 | 921 | |
ee38400b YW |
922 | slot->match_callback.callback = callback; |
923 | slot->match_callback.type = type; | |
8cec01b9 | 924 | |
31710be5 TG |
925 | switch (type) { |
926 | case RTM_NEWLINK: | |
31710be5 | 927 | case RTM_DELLINK: |
9c5a882b | 928 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); |
31710be5 TG |
929 | if (r < 0) |
930 | return r; | |
931 | ||
932 | break; | |
933 | case RTM_NEWADDR: | |
31710be5 | 934 | case RTM_DELADDR: |
9c5a882b | 935 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); |
31710be5 TG |
936 | if (r < 0) |
937 | return r; | |
938 | ||
9c5a882b | 939 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); |
31710be5 TG |
940 | if (r < 0) |
941 | return r; | |
942 | ||
d1bdafd2 WKI |
943 | break; |
944 | case RTM_NEWNEIGH: | |
945 | case RTM_DELNEIGH: | |
946 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_NEIGH); | |
947 | if (r < 0) | |
948 | return r; | |
949 | ||
31710be5 | 950 | break; |
87e4c847 TG |
951 | case RTM_NEWROUTE: |
952 | case RTM_DELROUTE: | |
9c5a882b | 953 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); |
87e4c847 TG |
954 | if (r < 0) |
955 | return r; | |
956 | ||
9c5a882b | 957 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); |
87e4c847 TG |
958 | if (r < 0) |
959 | return r; | |
960 | break; | |
bce67bbe SS |
961 | case RTM_NEWRULE: |
962 | case RTM_DELRULE: | |
963 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_RULE); | |
964 | if (r < 0) | |
965 | return r; | |
966 | ||
967 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_RULE); | |
968 | if (r < 0) | |
969 | return r; | |
970 | break; | |
c16c7808 SS |
971 | case RTM_NEWNEXTHOP: |
972 | case RTM_DELNEXTHOP: | |
973 | r = socket_broadcast_group_ref(rtnl, RTNLGRP_NEXTHOP); | |
974 | if (r < 0) | |
975 | return r; | |
976 | break; | |
977 | ||
31710be5 TG |
978 | default: |
979 | return -EOPNOTSUPP; | |
980 | } | |
981 | ||
ee38400b | 982 | LIST_PREPEND(match_callbacks, rtnl->match_callbacks, &slot->match_callback); |
8cec01b9 | 983 | |
5cd67116 YW |
984 | /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ |
985 | slot->destroy_callback = destroy_callback; | |
986 | ||
ee38400b YW |
987 | if (ret_slot) |
988 | *ret_slot = slot; | |
8cec01b9 | 989 | |
ee38400b | 990 | TAKE_PTR(slot); |
8cec01b9 TG |
991 | |
992 | return 0; | |
993 | } |