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