]>
Commit | Line | Data |
---|---|---|
b1c097af YW |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include <errno.h> | |
4 | #include <linux/filter.h> | |
5 | #include <linux/netlink.h> | |
f5947a5e | 6 | #include <unistd.h> |
b1c097af YW |
7 | |
8 | #include "sd-device.h" | |
9 | #include "sd-event.h" | |
10 | ||
11 | #include "MurmurHash2.h" | |
12 | #include "alloc-util.h" | |
13 | #include "device-monitor-private.h" | |
14 | #include "device-private.h" | |
15 | #include "device-util.h" | |
16 | #include "fd-util.h" | |
17 | #include "format-util.h" | |
18 | #include "hashmap.h" | |
cb310866 | 19 | #include "io-util.h" |
049af8ad | 20 | #include "mountpoint-util.h" |
b1c097af YW |
21 | #include "set.h" |
22 | #include "socket-util.h" | |
23 | #include "string-util.h" | |
24 | #include "strv.h" | |
25 | ||
26 | struct sd_device_monitor { | |
27 | unsigned n_ref; | |
28 | ||
29 | int sock; | |
30 | union sockaddr_union snl; | |
31 | union sockaddr_union snl_trusted_sender; | |
32 | bool bound; | |
33 | ||
34 | Hashmap *subsystem_filter; | |
35 | Set *tag_filter; | |
36 | bool filter_uptodate; | |
37 | ||
38 | sd_event *event; | |
39 | sd_event_source *event_source; | |
b1c097af YW |
40 | sd_device_monitor_handler_t callback; |
41 | void *userdata; | |
42 | }; | |
43 | ||
44 | #define UDEV_MONITOR_MAGIC 0xfeedcafe | |
45 | ||
46 | typedef struct monitor_netlink_header { | |
47 | /* "libudev" prefix to distinguish libudev and kernel messages */ | |
48 | char prefix[8]; | |
49 | /* Magic to protect against daemon <-> Library message format mismatch | |
50 | * Used in the kernel from socket filter rules; needs to be stored in network order */ | |
51 | unsigned magic; | |
52 | /* Total length of header structure known to the sender */ | |
53 | unsigned header_size; | |
54 | /* Properties string buffer */ | |
55 | unsigned properties_off; | |
56 | unsigned properties_len; | |
57 | /* Hashes of primary device properties strings, to let libudev subscribers | |
58 | * use in-kernel socket filters; values need to be stored in network order */ | |
59 | unsigned filter_subsystem_hash; | |
60 | unsigned filter_devtype_hash; | |
61 | unsigned filter_tag_bloom_hi; | |
62 | unsigned filter_tag_bloom_lo; | |
63 | } monitor_netlink_header; | |
64 | ||
65 | static int monitor_set_nl_address(sd_device_monitor *m) { | |
66 | union sockaddr_union snl; | |
67 | socklen_t addrlen; | |
68 | ||
69 | assert(m); | |
70 | ||
71 | /* Get the address the kernel has assigned us. | |
72 | * It is usually, but not necessarily the pid. */ | |
73 | addrlen = sizeof(struct sockaddr_nl); | |
74 | if (getsockname(m->sock, &snl.sa, &addrlen) < 0) | |
75 | return -errno; | |
76 | ||
77 | m->snl.nl.nl_pid = snl.nl.nl_pid; | |
78 | return 0; | |
79 | } | |
80 | ||
81 | int device_monitor_allow_unicast_sender(sd_device_monitor *m, sd_device_monitor *sender) { | |
82 | assert_return(m, -EINVAL); | |
83 | assert_return(sender, -EINVAL); | |
84 | ||
85 | m->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; | |
86 | return 0; | |
87 | } | |
88 | ||
89 | _public_ int sd_device_monitor_set_receive_buffer_size(sd_device_monitor *m, size_t size) { | |
9e5b6496 | 90 | int r, n = (int) size; |
b1c097af YW |
91 | |
92 | assert_return(m, -EINVAL); | |
dcdc2f61 | 93 | assert_return((size_t) n == size, -EINVAL); |
b1c097af | 94 | |
ee0b9e72 YW |
95 | if (setsockopt_int(m->sock, SOL_SOCKET, SO_RCVBUFFORCE, n) < 0) { |
96 | r = setsockopt_int(m->sock, SOL_SOCKET, SO_RCVBUF, n); | |
9e5b6496 YW |
97 | if (r < 0) |
98 | return r; | |
99 | } | |
b1c097af YW |
100 | |
101 | return 0; | |
102 | } | |
103 | ||
104 | int device_monitor_disconnect(sd_device_monitor *m) { | |
105 | assert(m); | |
106 | ||
107 | m->sock = safe_close(m->sock); | |
108 | return 0; | |
109 | } | |
110 | ||
111 | int device_monitor_get_fd(sd_device_monitor *m) { | |
112 | assert_return(m, -EINVAL); | |
113 | ||
114 | return m->sock; | |
115 | } | |
116 | ||
117 | int device_monitor_new_full(sd_device_monitor **ret, MonitorNetlinkGroup group, int fd) { | |
118 | _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; | |
119 | _cleanup_close_ int sock = -1; | |
120 | int r; | |
121 | ||
122 | assert_return(ret, -EINVAL); | |
123 | assert_return(group >= 0 && group < _MONITOR_NETLINK_GROUP_MAX, -EINVAL); | |
124 | ||
125 | if (group == MONITOR_GROUP_UDEV && | |
126 | access("/run/udev/control", F_OK) < 0 && | |
127 | dev_is_devtmpfs() <= 0) { | |
128 | ||
129 | /* | |
130 | * We do not support subscribing to uevents if no instance of | |
131 | * udev is running. Uevents would otherwise broadcast the | |
132 | * processing data of the host into containers, which is not | |
133 | * desired. | |
134 | * | |
135 | * Containers will currently not get any udev uevents, until | |
136 | * a supporting infrastructure is available. | |
137 | * | |
138 | * We do not set a netlink multicast group here, so the socket | |
139 | * will not receive any messages. | |
140 | */ | |
141 | ||
c7d54dae | 142 | log_debug("sd-device-monitor: The udev service seems not to be active, disabling the monitor"); |
b1c097af YW |
143 | group = MONITOR_GROUP_NONE; |
144 | } | |
145 | ||
146 | if (fd < 0) { | |
147 | sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); | |
148 | if (sock < 0) | |
c7d54dae | 149 | return log_debug_errno(errno, "sd-device-monitor: Failed to create socket: %m"); |
b1c097af YW |
150 | } |
151 | ||
152 | m = new(sd_device_monitor, 1); | |
153 | if (!m) | |
154 | return -ENOMEM; | |
155 | ||
156 | *m = (sd_device_monitor) { | |
157 | .n_ref = 1, | |
158 | .sock = fd >= 0 ? fd : TAKE_FD(sock), | |
159 | .bound = fd >= 0, | |
160 | .snl.nl.nl_family = AF_NETLINK, | |
161 | .snl.nl.nl_groups = group, | |
162 | }; | |
163 | ||
164 | if (fd >= 0) { | |
165 | r = monitor_set_nl_address(m); | |
166 | if (r < 0) | |
c7d54dae | 167 | return log_debug_errno(r, "sd-device-monitor: Failed to set netlink address: %m"); |
b1c097af YW |
168 | } |
169 | ||
170 | *ret = TAKE_PTR(m); | |
171 | return 0; | |
172 | } | |
173 | ||
174 | _public_ int sd_device_monitor_new(sd_device_monitor **ret) { | |
175 | return device_monitor_new_full(ret, MONITOR_GROUP_UDEV, -1); | |
176 | } | |
177 | ||
178 | _public_ int sd_device_monitor_stop(sd_device_monitor *m) { | |
179 | assert_return(m, -EINVAL); | |
180 | ||
181 | m->event_source = sd_event_source_unref(m->event_source); | |
182 | (void) device_monitor_disconnect(m); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int device_monitor_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
188 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; | |
189 | sd_device_monitor *m = userdata; | |
190 | ||
191 | assert(m); | |
192 | ||
193 | if (device_monitor_receive_device(m, &device) <= 0) | |
194 | return 0; | |
195 | ||
196 | if (m->callback) | |
197 | return m->callback(m, device, m->userdata); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
deb2b734 | 202 | _public_ int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata) { |
b1c097af YW |
203 | int r; |
204 | ||
205 | assert_return(m, -EINVAL); | |
206 | ||
207 | if (!m->event) { | |
deb2b734 | 208 | r = sd_device_monitor_attach_event(m, NULL); |
b1c097af YW |
209 | if (r < 0) |
210 | return r; | |
211 | } | |
212 | ||
a153a1de YW |
213 | r = device_monitor_enable_receiving(m); |
214 | if (r < 0) | |
215 | return r; | |
b1c097af YW |
216 | |
217 | m->callback = callback; | |
218 | m->userdata = userdata; | |
219 | ||
deb2b734 | 220 | r = sd_event_add_io(m->event, &m->event_source, m->sock, EPOLLIN, device_monitor_event_handler, m); |
b1c097af YW |
221 | if (r < 0) |
222 | return r; | |
223 | ||
deb2b734 | 224 | (void) sd_event_source_set_description(m->event_source, "sd-device-monitor"); |
b1c097af YW |
225 | |
226 | return 0; | |
227 | } | |
228 | ||
229 | _public_ int sd_device_monitor_detach_event(sd_device_monitor *m) { | |
230 | assert_return(m, -EINVAL); | |
231 | ||
232 | (void) sd_device_monitor_stop(m); | |
233 | m->event = sd_event_unref(m->event); | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
deb2b734 | 238 | _public_ int sd_device_monitor_attach_event(sd_device_monitor *m, sd_event *event) { |
b1c097af YW |
239 | int r; |
240 | ||
241 | assert_return(m, -EINVAL); | |
242 | assert_return(!m->event, -EBUSY); | |
243 | ||
244 | if (event) | |
245 | m->event = sd_event_ref(event); | |
246 | else { | |
247 | r = sd_event_default(&m->event); | |
248 | if (r < 0) | |
779c9234 | 249 | return r; |
b1c097af YW |
250 | } |
251 | ||
b1c097af YW |
252 | return 0; |
253 | } | |
254 | ||
255 | _public_ sd_event *sd_device_monitor_get_event(sd_device_monitor *m) { | |
256 | assert_return(m, NULL); | |
257 | ||
258 | return m->event; | |
259 | } | |
260 | ||
bf7712b6 YW |
261 | _public_ sd_event_source *sd_device_monitor_get_event_source(sd_device_monitor *m) { |
262 | assert_return(m, NULL); | |
263 | ||
264 | return m->event_source; | |
265 | } | |
266 | ||
b1c097af YW |
267 | int device_monitor_enable_receiving(sd_device_monitor *m) { |
268 | int r; | |
269 | ||
270 | assert_return(m, -EINVAL); | |
271 | ||
f1d7b787 YW |
272 | r = sd_device_monitor_filter_update(m); |
273 | if (r < 0) | |
274 | return log_debug_errno(r, "sd-device-monitor: Failed to update filter: %m"); | |
b1c097af YW |
275 | |
276 | if (!m->bound) { | |
c821e84a YW |
277 | /* enable receiving of sender credentials */ |
278 | r = setsockopt_int(m->sock, SOL_SOCKET, SO_PASSCRED, true); | |
279 | if (r < 0) | |
280 | return log_debug_errno(r, "sd-device-monitor: Failed to set socket option SO_PASSCRED: %m"); | |
281 | ||
b1c097af | 282 | if (bind(m->sock, &m->snl.sa, sizeof(struct sockaddr_nl)) < 0) |
5cee547a | 283 | return log_debug_errno(errno, "sd-device-monitor: Failed to bind monitoring socket: %m"); |
b1c097af YW |
284 | |
285 | m->bound = true; | |
b1c097af | 286 | |
c821e84a YW |
287 | r = monitor_set_nl_address(m); |
288 | if (r < 0) | |
289 | return log_debug_errno(r, "sd-device-monitor: Failed to set address: %m"); | |
290 | } | |
b1c097af YW |
291 | |
292 | return 0; | |
293 | } | |
294 | ||
295 | static sd_device_monitor *device_monitor_free(sd_device_monitor *m) { | |
296 | assert(m); | |
297 | ||
298 | (void) sd_device_monitor_detach_event(m); | |
299 | ||
300 | hashmap_free_free_free(m->subsystem_filter); | |
301 | set_free_free(m->tag_filter); | |
302 | ||
303 | return mfree(m); | |
304 | } | |
305 | ||
306 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device_monitor, sd_device_monitor, device_monitor_free); | |
307 | ||
308 | static int passes_filter(sd_device_monitor *m, sd_device *device) { | |
309 | const char *tag, *subsystem, *devtype, *s, *d = NULL; | |
310 | Iterator i; | |
311 | int r; | |
312 | ||
313 | assert_return(m, -EINVAL); | |
314 | assert_return(device, -EINVAL); | |
315 | ||
316 | if (hashmap_isempty(m->subsystem_filter)) | |
317 | goto tag; | |
318 | ||
319 | r = sd_device_get_subsystem(device, &s); | |
320 | if (r < 0) | |
321 | return r; | |
322 | ||
323 | r = sd_device_get_devtype(device, &d); | |
324 | if (r < 0 && r != -ENOENT) | |
325 | return r; | |
326 | ||
327 | HASHMAP_FOREACH_KEY(devtype, subsystem, m->subsystem_filter, i) { | |
328 | if (!streq(s, subsystem)) | |
329 | continue; | |
330 | ||
331 | if (!devtype) | |
332 | goto tag; | |
333 | ||
334 | if (!d) | |
335 | continue; | |
336 | ||
337 | if (streq(d, devtype)) | |
338 | goto tag; | |
339 | } | |
340 | ||
341 | return 0; | |
342 | ||
343 | tag: | |
344 | if (set_isempty(m->tag_filter)) | |
345 | return 1; | |
346 | ||
347 | SET_FOREACH(tag, m->tag_filter, i) | |
348 | if (sd_device_has_tag(device, tag) > 0) | |
349 | return 1; | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
354 | int device_monitor_receive_device(sd_device_monitor *m, sd_device **ret) { | |
355 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; | |
356 | union { | |
357 | monitor_netlink_header nlh; | |
358 | char raw[8192]; | |
359 | } buf; | |
360 | struct iovec iov = { | |
361 | .iov_base = &buf, | |
362 | .iov_len = sizeof(buf) | |
363 | }; | |
364 | char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; | |
365 | union sockaddr_union snl; | |
366 | struct msghdr smsg = { | |
367 | .msg_iov = &iov, | |
368 | .msg_iovlen = 1, | |
369 | .msg_control = cred_msg, | |
370 | .msg_controllen = sizeof(cred_msg), | |
371 | .msg_name = &snl, | |
372 | .msg_namelen = sizeof(snl), | |
373 | }; | |
374 | struct cmsghdr *cmsg; | |
375 | struct ucred *cred; | |
376 | ssize_t buflen, bufpos; | |
377 | bool is_initialized = false; | |
378 | int r; | |
379 | ||
380 | assert(ret); | |
381 | ||
382 | buflen = recvmsg(m->sock, &smsg, 0); | |
383 | if (buflen < 0) { | |
384 | if (errno != EINTR) | |
c7d54dae | 385 | log_debug_errno(errno, "sd-device-monitor: Failed to receive message: %m"); |
b1c097af YW |
386 | return -errno; |
387 | } | |
388 | ||
389 | if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) | |
886cf317 ZJS |
390 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), |
391 | "sd-device-monitor: Invalid message length."); | |
b1c097af YW |
392 | |
393 | if (snl.nl.nl_groups == MONITOR_GROUP_NONE) { | |
394 | /* unicast message, check if we trust the sender */ | |
395 | if (m->snl_trusted_sender.nl.nl_pid == 0 || | |
396 | snl.nl.nl_pid != m->snl_trusted_sender.nl.nl_pid) | |
886cf317 ZJS |
397 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
398 | "sd-device-monitor: Unicast netlink message ignored."); | |
b1c097af YW |
399 | |
400 | } else if (snl.nl.nl_groups == MONITOR_GROUP_KERNEL) { | |
401 | if (snl.nl.nl_pid > 0) | |
886cf317 ZJS |
402 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
403 | "sd-device-monitor: Multicast kernel netlink message from PID %"PRIu32" ignored.", snl.nl.nl_pid); | |
b1c097af YW |
404 | } |
405 | ||
406 | cmsg = CMSG_FIRSTHDR(&smsg); | |
407 | if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) | |
886cf317 ZJS |
408 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
409 | "sd-device-monitor: No sender credentials received, message ignored."); | |
b1c097af YW |
410 | |
411 | cred = (struct ucred*) CMSG_DATA(cmsg); | |
412 | if (cred->uid != 0) | |
886cf317 ZJS |
413 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
414 | "sd-device-monitor: Sender uid="UID_FMT", message ignored.", cred->uid); | |
b1c097af YW |
415 | |
416 | if (streq(buf.raw, "libudev")) { | |
417 | /* udev message needs proper version magic */ | |
418 | if (buf.nlh.magic != htobe32(UDEV_MONITOR_MAGIC)) | |
886cf317 ZJS |
419 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
420 | "sd-device-monitor: Invalid message signature (%x != %x)", | |
b1c097af YW |
421 | buf.nlh.magic, htobe32(UDEV_MONITOR_MAGIC)); |
422 | ||
423 | if (buf.nlh.properties_off+32 > (size_t) buflen) | |
886cf317 ZJS |
424 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
425 | "sd-device-monitor: Invalid message length (%u > %zd)", | |
b1c097af YW |
426 | buf.nlh.properties_off+32, buflen); |
427 | ||
428 | bufpos = buf.nlh.properties_off; | |
429 | ||
430 | /* devices received from udev are always initialized */ | |
431 | is_initialized = true; | |
432 | ||
433 | } else { | |
434 | /* kernel message with header */ | |
435 | bufpos = strlen(buf.raw) + 1; | |
436 | if ((size_t) bufpos < sizeof("a@/d") || bufpos >= buflen) | |
886cf317 ZJS |
437 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
438 | "sd-device-monitor: Invalid message length"); | |
b1c097af YW |
439 | |
440 | /* check message header */ | |
441 | if (!strstr(buf.raw, "@/")) | |
886cf317 ZJS |
442 | return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), |
443 | "sd-device-monitor: Invalid message header"); | |
b1c097af YW |
444 | } |
445 | ||
446 | r = device_new_from_nulstr(&device, (uint8_t*) &buf.raw[bufpos], buflen - bufpos); | |
447 | if (r < 0) | |
c7d54dae | 448 | return log_debug_errno(r, "sd-device-monitor: Failed to create device from received message: %m"); |
b1c097af YW |
449 | |
450 | if (is_initialized) | |
451 | device_set_is_initialized(device); | |
452 | ||
453 | /* Skip device, if it does not pass the current filter */ | |
454 | r = passes_filter(m, device); | |
455 | if (r < 0) | |
c7d54dae | 456 | return log_device_debug_errno(device, r, "sd-device-monitor: Failed to check received device passing filter: %m"); |
b1c097af | 457 | if (r == 0) |
c7d54dae | 458 | log_device_debug(device, "sd-device-monitor: Received device does not pass filter, ignoring"); |
b1c097af YW |
459 | else |
460 | *ret = TAKE_PTR(device); | |
461 | ||
462 | return r; | |
463 | } | |
464 | ||
465 | static uint32_t string_hash32(const char *str) { | |
466 | return MurmurHash2(str, strlen(str), 0); | |
467 | } | |
468 | ||
469 | /* Get a bunch of bit numbers out of the hash, and set the bits in our bit field */ | |
470 | static uint64_t string_bloom64(const char *str) { | |
471 | uint64_t bits = 0; | |
472 | uint32_t hash = string_hash32(str); | |
473 | ||
474 | bits |= 1LLU << (hash & 63); | |
475 | bits |= 1LLU << ((hash >> 6) & 63); | |
476 | bits |= 1LLU << ((hash >> 12) & 63); | |
477 | bits |= 1LLU << ((hash >> 18) & 63); | |
478 | return bits; | |
479 | } | |
480 | ||
481 | int device_monitor_send_device( | |
482 | sd_device_monitor *m, | |
483 | sd_device_monitor *destination, | |
484 | sd_device *device) { | |
485 | ||
486 | monitor_netlink_header nlh = { | |
487 | .prefix = "libudev", | |
488 | .magic = htobe32(UDEV_MONITOR_MAGIC), | |
489 | .header_size = sizeof nlh, | |
490 | }; | |
491 | struct iovec iov[2] = { | |
492 | { .iov_base = &nlh, .iov_len = sizeof nlh }, | |
493 | }; | |
494 | struct msghdr smsg = { | |
495 | .msg_iov = iov, | |
496 | .msg_iovlen = 2, | |
497 | }; | |
498 | /* default destination for sending */ | |
499 | union sockaddr_union default_destination = { | |
500 | .nl.nl_family = AF_NETLINK, | |
501 | .nl.nl_groups = MONITOR_GROUP_UDEV, | |
502 | }; | |
503 | uint64_t tag_bloom_bits; | |
504 | const char *buf, *val; | |
505 | ssize_t count; | |
506 | size_t blen; | |
507 | int r; | |
508 | ||
509 | assert(m); | |
510 | assert(device); | |
511 | ||
512 | r = device_get_properties_nulstr(device, (const uint8_t **) &buf, &blen); | |
513 | if (r < 0) | |
c7d54dae | 514 | return log_device_debug_errno(device, r, "sd-device-monitor: Failed to get device properties: %m"); |
b1c097af | 515 | if (blen < 32) { |
c7d54dae | 516 | log_device_debug(device, "sd-device-monitor: Length of device property nulstr is too small to contain valid device information"); |
b1c097af YW |
517 | return -EINVAL; |
518 | } | |
519 | ||
520 | /* fill in versioned header */ | |
521 | r = sd_device_get_subsystem(device, &val); | |
522 | if (r < 0) | |
c7d54dae | 523 | return log_device_debug_errno(device, r, "sd-device-monitor: Failed to get device subsystem: %m"); |
b1c097af YW |
524 | nlh.filter_subsystem_hash = htobe32(string_hash32(val)); |
525 | ||
a537eafe | 526 | if (sd_device_get_devtype(device, &val) >= 0) |
b1c097af YW |
527 | nlh.filter_devtype_hash = htobe32(string_hash32(val)); |
528 | ||
529 | /* add tag bloom filter */ | |
530 | tag_bloom_bits = 0; | |
531 | FOREACH_DEVICE_TAG(device, val) | |
532 | tag_bloom_bits |= string_bloom64(val); | |
533 | ||
534 | if (tag_bloom_bits > 0) { | |
535 | nlh.filter_tag_bloom_hi = htobe32(tag_bloom_bits >> 32); | |
536 | nlh.filter_tag_bloom_lo = htobe32(tag_bloom_bits & 0xffffffff); | |
537 | } | |
538 | ||
539 | /* add properties list */ | |
540 | nlh.properties_off = iov[0].iov_len; | |
541 | nlh.properties_len = blen; | |
cb310866 | 542 | iov[1] = IOVEC_MAKE((char*) buf, blen); |
b1c097af YW |
543 | |
544 | /* | |
545 | * Use custom address for target, or the default one. | |
546 | * | |
547 | * If we send to a multicast group, we will get | |
548 | * ECONNREFUSED, which is expected. | |
549 | */ | |
550 | smsg.msg_name = destination ? &destination->snl : &default_destination; | |
551 | smsg.msg_namelen = sizeof(struct sockaddr_nl); | |
552 | count = sendmsg(m->sock, &smsg, 0); | |
553 | if (count < 0) { | |
554 | if (!destination && errno == ECONNREFUSED) { | |
c7d54dae | 555 | log_device_debug(device, "sd-device-monitor: Passed to netlink monitor"); |
b1c097af YW |
556 | return 0; |
557 | } else | |
c7d54dae | 558 | return log_device_debug_errno(device, errno, "sd-device-monitor: Failed to send device to netlink monitor: %m"); |
b1c097af YW |
559 | } |
560 | ||
c7d54dae | 561 | log_device_debug(device, "sd-device-monitor: Passed %zi byte to netlink monitor", count); |
b1c097af YW |
562 | return count; |
563 | } | |
564 | ||
565 | static void bpf_stmt(struct sock_filter *ins, unsigned *i, | |
566 | unsigned short code, unsigned data) { | |
567 | ins[(*i)++] = (struct sock_filter) { | |
568 | .code = code, | |
569 | .k = data, | |
570 | }; | |
571 | } | |
572 | ||
573 | static void bpf_jmp(struct sock_filter *ins, unsigned *i, | |
574 | unsigned short code, unsigned data, | |
575 | unsigned short jt, unsigned short jf) { | |
576 | ins[(*i)++] = (struct sock_filter) { | |
577 | .code = code, | |
578 | .jt = jt, | |
579 | .jf = jf, | |
580 | .k = data, | |
581 | }; | |
582 | } | |
583 | ||
584 | _public_ int sd_device_monitor_filter_update(sd_device_monitor *m) { | |
585 | struct sock_filter ins[512] = {}; | |
586 | struct sock_fprog filter; | |
587 | const char *subsystem, *devtype, *tag; | |
588 | unsigned i = 0; | |
589 | Iterator it; | |
590 | ||
591 | assert_return(m, -EINVAL); | |
592 | ||
f1d7b787 YW |
593 | if (m->filter_uptodate) |
594 | return 0; | |
595 | ||
b1c097af YW |
596 | if (hashmap_isempty(m->subsystem_filter) && |
597 | set_isempty(m->tag_filter)) { | |
598 | m->filter_uptodate = true; | |
599 | return 0; | |
600 | } | |
601 | ||
602 | /* load magic in A */ | |
603 | bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, magic)); | |
604 | /* jump if magic matches */ | |
605 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); | |
606 | /* wrong magic, pass packet */ | |
607 | bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); | |
608 | ||
609 | if (!set_isempty(m->tag_filter)) { | |
610 | int tag_matches = set_size(m->tag_filter); | |
611 | ||
612 | /* add all tags matches */ | |
613 | SET_FOREACH(tag, m->tag_filter, it) { | |
614 | uint64_t tag_bloom_bits = string_bloom64(tag); | |
615 | uint32_t tag_bloom_hi = tag_bloom_bits >> 32; | |
616 | uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; | |
617 | ||
618 | /* load device bloom bits in A */ | |
619 | bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_hi)); | |
620 | /* clear bits (tag bits & bloom bits) */ | |
621 | bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); | |
622 | /* jump to next tag if it does not match */ | |
623 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); | |
624 | ||
625 | /* load device bloom bits in A */ | |
626 | bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_lo)); | |
627 | /* clear bits (tag bits & bloom bits) */ | |
628 | bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); | |
629 | /* jump behind end of tag match block if tag matches */ | |
630 | tag_matches--; | |
631 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); | |
632 | } | |
633 | ||
634 | /* nothing matched, drop packet */ | |
635 | bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); | |
636 | } | |
637 | ||
638 | /* add all subsystem matches */ | |
639 | if (!hashmap_isempty(m->subsystem_filter)) { | |
640 | HASHMAP_FOREACH_KEY(devtype, subsystem, m->subsystem_filter, it) { | |
641 | uint32_t hash = string_hash32(subsystem); | |
642 | ||
643 | /* load device subsystem value in A */ | |
644 | bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_subsystem_hash)); | |
645 | if (!devtype) { | |
646 | /* jump if subsystem does not match */ | |
647 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); | |
648 | } else { | |
b1c097af YW |
649 | /* jump if subsystem does not match */ |
650 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); | |
651 | /* load device devtype value in A */ | |
652 | bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_devtype_hash)); | |
653 | /* jump if value does not match */ | |
65fe9c31 | 654 | hash = string_hash32(devtype); |
b1c097af YW |
655 | bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); |
656 | } | |
657 | ||
658 | /* matched, pass packet */ | |
659 | bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); | |
660 | ||
661 | if (i+1 >= ELEMENTSOF(ins)) | |
662 | return -E2BIG; | |
663 | } | |
664 | ||
665 | /* nothing matched, drop packet */ | |
666 | bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); | |
667 | } | |
668 | ||
669 | /* matched, pass packet */ | |
670 | bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); | |
671 | ||
672 | /* install filter */ | |
673 | filter = (struct sock_fprog) { | |
674 | .len = i, | |
675 | .filter = ins, | |
676 | }; | |
677 | if (setsockopt(m->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) | |
678 | return -errno; | |
679 | ||
680 | m->filter_uptodate = true; | |
681 | return 0; | |
682 | } | |
683 | ||
684 | _public_ int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype) { | |
685 | _cleanup_free_ char *s = NULL, *d = NULL; | |
686 | int r; | |
687 | ||
688 | assert_return(m, -EINVAL); | |
689 | assert_return(subsystem, -EINVAL); | |
690 | ||
691 | s = strdup(subsystem); | |
692 | if (!s) | |
693 | return -ENOMEM; | |
694 | ||
695 | if (devtype) { | |
696 | d = strdup(devtype); | |
697 | if (!d) | |
698 | return -ENOMEM; | |
699 | } | |
700 | ||
701 | r = hashmap_ensure_allocated(&m->subsystem_filter, NULL); | |
702 | if (r < 0) | |
703 | return r; | |
704 | ||
705 | r = hashmap_put(m->subsystem_filter, s, d); | |
706 | if (r < 0) | |
707 | return r; | |
708 | ||
709 | s = d = NULL; | |
710 | m->filter_uptodate = false; | |
711 | ||
712 | return 0; | |
713 | } | |
714 | ||
715 | _public_ int sd_device_monitor_filter_add_match_tag(sd_device_monitor *m, const char *tag) { | |
716 | _cleanup_free_ char *t = NULL; | |
717 | int r; | |
718 | ||
719 | assert_return(m, -EINVAL); | |
720 | assert_return(tag, -EINVAL); | |
721 | ||
722 | t = strdup(tag); | |
723 | if (!t) | |
724 | return -ENOMEM; | |
725 | ||
726 | r = set_ensure_allocated(&m->tag_filter, &string_hash_ops); | |
727 | if (r < 0) | |
728 | return r; | |
729 | ||
730 | r = set_put(m->tag_filter, t); | |
731 | if (r == -EEXIST) | |
732 | return 0; | |
733 | if (r < 0) | |
734 | return r; | |
735 | ||
736 | TAKE_PTR(t); | |
737 | m->filter_uptodate = false; | |
738 | ||
739 | return 0; | |
740 | } | |
741 | ||
742 | _public_ int sd_device_monitor_filter_remove(sd_device_monitor *m) { | |
743 | static const struct sock_fprog filter = { 0, NULL }; | |
744 | ||
745 | assert_return(m, -EINVAL); | |
746 | ||
747 | m->subsystem_filter = hashmap_free_free_free(m->subsystem_filter); | |
748 | m->tag_filter = set_free_free(m->tag_filter); | |
749 | ||
b0757173 | 750 | if (setsockopt(m->sock, SOL_SOCKET, SO_DETACH_FILTER, &filter, sizeof(filter)) < 0) |
b1c097af YW |
751 | return -errno; |
752 | ||
753 | m->filter_uptodate = true; | |
754 | return 0; | |
755 | } |