]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-netlink/netlink-socket.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / libsystemd / sd-netlink / netlink-socket.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
89489ef7
TG
2/***
3 This file is part of systemd.
4
5 Copyright 2013 Tom Gundersen <teg@jklm.no>
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
21#include <netinet/in.h>
22#include <stdbool.h>
23#include <unistd.h>
24
07630cea
LP
25#include "sd-netlink.h"
26
b5efdb8a 27#include "alloc-util.h"
7fe2903c 28#include "fd-util.h"
f97b34a6 29#include "format-util.h"
89489ef7 30#include "missing.h"
89489ef7
TG
31#include "netlink-internal.h"
32#include "netlink-types.h"
07630cea
LP
33#include "netlink-util.h"
34#include "refcnt.h"
35#include "socket-util.h"
36#include "util.h"
89489ef7 37
b95cc756
TG
38int socket_open(int family) {
39 int fd;
40
41 fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family);
42 if (fd < 0)
43 return -errno;
44
7fe2903c 45 return fd_move_above_stdio(fd);
b95cc756
TG
46}
47
9c5a882b
TG
48static int broadcast_groups_get(sd_netlink *nl) {
49 _cleanup_free_ uint32_t *groups = NULL;
50 socklen_t len = 0, old_len;
51 unsigned i, j;
52 int r;
53
54 assert(nl);
f78bc916 55 assert(nl->fd >= 0);
9c5a882b
TG
56
57 r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len);
58 if (r < 0) {
59 if (errno == ENOPROTOOPT) {
60 nl->broadcast_group_dont_leave = true;
61 return 0;
62 } else
63 return -errno;
64 }
65
66 if (len == 0)
67 return 0;
68
69 groups = new0(uint32_t, len);
70 if (!groups)
71 return -ENOMEM;
72
73 old_len = len;
74
75 r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len);
76 if (r < 0)
77 return -errno;
78
79 if (old_len != len)
80 return -EIO;
81
82 r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL);
83 if (r < 0)
84 return r;
85
86 for (i = 0; i < len; i++) {
313cefa1 87 for (j = 0; j < sizeof(uint32_t) * 8; j++) {
9c5a882b
TG
88 uint32_t offset;
89 unsigned group;
90
91 offset = 1U << j;
92
93 if (!(groups[i] & offset))
94 continue;
95
96 group = i * sizeof(uint32_t) * 8 + j + 1;
97
98 r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1));
99 if (r < 0)
100 return r;
101 }
102 }
103
104 return 0;
105}
106
b95cc756
TG
107int socket_bind(sd_netlink *nl) {
108 socklen_t addrlen;
109 int r, one = 1;
110
111 r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one));
112 if (r < 0)
113 return -errno;
114
115 addrlen = sizeof(nl->sockaddr);
116
117 r = bind(nl->fd, &nl->sockaddr.sa, addrlen);
118 /* ignore EINVAL to allow opening an already bound socket */
119 if (r < 0 && errno != EINVAL)
120 return -errno;
121
122 r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen);
123 if (r < 0)
124 return -errno;
125
9c5a882b
TG
126 r = broadcast_groups_get(nl);
127 if (r < 0)
128 return r;
129
b95cc756
TG
130 return 0;
131}
132
9c5a882b
TG
133static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) {
134 assert(nl);
135
136 return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group)));
137}
138
139static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) {
140 int r;
141
142 assert(nl);
143
144 r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref));
145 if (r < 0)
146 return r;
147
148 return 0;
149}
b95cc756 150
9c5a882b 151static int broadcast_group_join(sd_netlink *nl, unsigned group) {
b95cc756
TG
152 int r;
153
154 assert(nl);
155 assert(nl->fd >= 0);
156 assert(group > 0);
157
158 r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
159 if (r < 0)
160 return -errno;
161
162 return 0;
163}
164
9c5a882b
TG
165int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) {
166 unsigned n_ref;
167 int r;
168
169 assert(nl);
170
171 n_ref = broadcast_group_get_ref(nl, group);
172
313cefa1 173 n_ref++;
9c5a882b
TG
174
175 r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL);
176 if (r < 0)
177 return r;
178
179 r = broadcast_group_set_ref(nl, group, n_ref);
180 if (r < 0)
181 return r;
182
183 if (n_ref > 1)
184 /* not yet in the group */
185 return 0;
186
187 r = broadcast_group_join(nl, group);
188 if (r < 0)
189 return r;
190
191 return 0;
192}
193
194static int broadcast_group_leave(sd_netlink *nl, unsigned group) {
195 int r;
196
197 assert(nl);
198 assert(nl->fd >= 0);
199 assert(group > 0);
200
201 if (nl->broadcast_group_dont_leave)
202 return 0;
203
204 r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group));
205 if (r < 0)
206 return -errno;
207
208 return 0;
209}
210
211int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) {
212 unsigned n_ref;
213 int r;
214
215 assert(nl);
216
217 n_ref = broadcast_group_get_ref(nl, group);
218
219 assert(n_ref > 0);
220
313cefa1 221 n_ref--;
9c5a882b
TG
222
223 r = broadcast_group_set_ref(nl, group, n_ref);
224 if (r < 0)
225 return r;
226
227 if (n_ref > 0)
228 /* still refs left */
229 return 0;
230
231 r = broadcast_group_leave(nl, group);
232 if (r < 0)
233 return r;
234
235 return 0;
236}
237
89489ef7
TG
238/* returns the number of bytes sent, or a negative error code */
239int socket_write_message(sd_netlink *nl, sd_netlink_message *m) {
240 union {
241 struct sockaddr sa;
242 struct sockaddr_nl nl;
243 } addr = {
244 .nl.nl_family = AF_NETLINK,
245 };
246 ssize_t k;
247
248 assert(nl);
249 assert(m);
250 assert(m->hdr);
251
252 k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len,
253 0, &addr.sa, sizeof(addr));
254 if (k < 0)
255 return -errno;
256
257 return k;
258}
259
260static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) {
261 union sockaddr_union sender;
262 uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))];
263 struct msghdr msg = {
264 .msg_iov = iov,
265 .msg_iovlen = 1,
266 .msg_name = &sender,
267 .msg_namelen = sizeof(sender),
268 .msg_control = cmsg_buffer,
269 .msg_controllen = sizeof(cmsg_buffer),
270 };
271 struct cmsghdr *cmsg;
272 uint32_t group = 0;
f1dd72c2 273 ssize_t n;
89489ef7
TG
274
275 assert(fd >= 0);
276 assert(iov);
277
f1dd72c2
LP
278 n = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0));
279 if (n < 0) {
89489ef7
TG
280 /* no data */
281 if (errno == ENOBUFS)
282 log_debug("rtnl: kernel receive buffer overrun");
283 else if (errno == EAGAIN)
284 log_debug("rtnl: no data in socket");
285
71994cff 286 return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno;
89489ef7
TG
287 }
288
289 if (sender.nl.nl_pid != 0) {
290 /* not from the kernel, ignore */
291 log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid);
292
293 if (peek) {
294 /* drop the message */
f1dd72c2
LP
295 n = recvmsg(fd, &msg, 0);
296 if (n < 0)
71994cff 297 return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno;
89489ef7
TG
298 }
299
300 return 0;
301 }
302
303 CMSG_FOREACH(cmsg, &msg) {
304 if (cmsg->cmsg_level == SOL_NETLINK &&
305 cmsg->cmsg_type == NETLINK_PKTINFO &&
306 cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) {
307 struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg);
308
309 /* multi-cast group */
310 group = pktinfo->group;
311 }
312 }
313
314 if (_group)
315 *_group = group;
316
f1dd72c2 317 return (int) n;
89489ef7
TG
318}
319
320/* On success, the number of bytes received is returned and *ret points to the received message
321 * which has a valid header and the correct size.
322 * If nothing useful was received 0 is returned.
323 * On failure, a negative error code is returned.
324 */
325int socket_read_message(sd_netlink *rtnl) {
4afd3348 326 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL;
89489ef7
TG
327 struct iovec iov = {};
328 uint32_t group = 0;
329 bool multi_part = false, done = false;
330 struct nlmsghdr *new_msg;
331 size_t len;
332 int r;
333 unsigned i = 0;
05d0c2e3 334 const NLTypeSystem *type_system_root;
89489ef7
TG
335
336 assert(rtnl);
337 assert(rtnl->rbuffer);
338 assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr));
339
05d0c2e3
JT
340 type_system_root = type_system_get_root(rtnl->protocol);
341
89489ef7
TG
342 /* read nothing, just get the pending message size */
343 r = socket_recv_message(rtnl->fd, &iov, NULL, true);
344 if (r <= 0)
345 return r;
346 else
f1dd72c2 347 len = (size_t) r;
89489ef7
TG
348
349 /* make room for the pending message */
350 if (!greedy_realloc((void **)&rtnl->rbuffer,
351 &rtnl->rbuffer_allocated,
352 len, sizeof(uint8_t)))
353 return -ENOMEM;
354
355 iov.iov_base = rtnl->rbuffer;
356 iov.iov_len = rtnl->rbuffer_allocated;
357
358 /* read the pending message */
359 r = socket_recv_message(rtnl->fd, &iov, &group, false);
360 if (r <= 0)
361 return r;
362 else
f1dd72c2 363 len = (size_t) r;
89489ef7
TG
364
365 if (len > rtnl->rbuffer_allocated)
366 /* message did not fit in read buffer */
367 return -EIO;
368
369 if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) {
370 multi_part = true;
371
372 for (i = 0; i < rtnl->rqueue_partial_size; i++) {
373 if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) ==
374 rtnl->rbuffer->nlmsg_seq) {
375 first = rtnl->rqueue_partial[i];
376 break;
377 }
378 }
379 }
380
381 for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) {
4afd3348 382 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
89489ef7
TG
383 const NLType *nl_type;
384
385 if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid)
386 /* not broadcast and not for us */
387 continue;
388
389 if (new_msg->nlmsg_type == NLMSG_NOOP)
390 /* silently drop noop messages */
391 continue;
392
393 if (new_msg->nlmsg_type == NLMSG_DONE) {
394 /* finished reading multi-part message */
395 done = true;
396
397 /* if first is not defined, put NLMSG_DONE into the receive queue. */
398 if (first)
399 continue;
400 }
401
402 /* check that we support this message type */
05d0c2e3 403 r = type_system_get_type(type_system_root, &nl_type, new_msg->nlmsg_type);
89489ef7
TG
404 if (r < 0) {
405 if (r == -EOPNOTSUPP)
406 log_debug("sd-netlink: ignored message with unknown type: %i",
407 new_msg->nlmsg_type);
408
409 continue;
410 }
411
412 /* check that the size matches the message type */
817d1cd8 413 if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) {
89489ef7
TG
414 log_debug("sd-netlink: message larger than expected, dropping");
415 continue;
416 }
417
418 r = message_new_empty(rtnl, &m);
419 if (r < 0)
420 return r;
421
422 m->broadcast = !!group;
423
424 m->hdr = memdup(new_msg, new_msg->nlmsg_len);
425 if (!m->hdr)
426 return -ENOMEM;
427
428 /* seal and parse the top-level message */
429 r = sd_netlink_message_rewind(m);
430 if (r < 0)
431 return r;
432
433 /* push the message onto the multi-part message stack */
434 if (first)
435 m->next = first;
1cc6c93a 436 first = TAKE_PTR(m);
89489ef7
TG
437 }
438
f1dd72c2 439 if (len > 0)
89489ef7
TG
440 log_debug("sd-netlink: discarding %zu bytes of incoming message", len);
441
442 if (!first)
443 return 0;
444
445 if (!multi_part || done) {
446 /* we got a complete message, push it on the read queue */
447 r = rtnl_rqueue_make_room(rtnl);
448 if (r < 0)
449 return r;
450
1cc6c93a 451 rtnl->rqueue[rtnl->rqueue_size++] = TAKE_PTR(first);
89489ef7
TG
452
453 if (multi_part && (i < rtnl->rqueue_partial_size)) {
454 /* remove the message form the partial read queue */
455 memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1,
456 sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1));
313cefa1 457 rtnl->rqueue_partial_size--;
89489ef7
TG
458 }
459
460 return 1;
461 } else {
462 /* we only got a partial multi-part message, push it on the
463 partial read queue */
f1dd72c2 464 if (i < rtnl->rqueue_partial_size)
1cc6c93a 465 rtnl->rqueue_partial[i] = TAKE_PTR(first);
f1dd72c2 466 else {
89489ef7
TG
467 r = rtnl_rqueue_partial_make_room(rtnl);
468 if (r < 0)
469 return r;
470
1cc6c93a 471 rtnl->rqueue_partial[rtnl->rqueue_partial_size++] = TAKE_PTR(first);
89489ef7 472 }
89489ef7
TG
473
474 return 0;
475 }
476}