]>
Commit | Line | Data |
---|---|---|
edb69db2 YW |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
a9d2d037 | 3 | #include <net/if_arp.h> |
edb69db2 YW |
4 | #include <linux/nl80211.h> |
5 | ||
c49d5362 | 6 | #include "device-private.h" |
edb69db2 YW |
7 | #include "device-util.h" |
8 | #include "networkd-manager.h" | |
9 | #include "networkd-wiphy.h" | |
10 | #include "parse-util.h" | |
8642e04b | 11 | #include "path-util.h" |
c49d5362 | 12 | #include "udev-util.h" |
edb69db2 YW |
13 | #include "wifi-util.h" |
14 | ||
15 | Wiphy *wiphy_free(Wiphy *w) { | |
16 | if (!w) | |
17 | return NULL; | |
18 | ||
19 | if (w->manager) { | |
20 | hashmap_remove_value(w->manager->wiphy_by_index, UINT32_TO_PTR(w->index), w); | |
21 | if (w->name) | |
22 | hashmap_remove_value(w->manager->wiphy_by_name, w->name, w); | |
23 | } | |
24 | ||
c49d5362 | 25 | sd_device_unref(w->dev); |
8642e04b | 26 | sd_device_unref(w->rfkill); |
c49d5362 | 27 | |
edb69db2 YW |
28 | free(w->name); |
29 | return mfree(w); | |
30 | } | |
31 | ||
fc85b323 | 32 | static int wiphy_new(Manager *manager, sd_netlink_message *message, Wiphy **ret) { |
edb69db2 | 33 | _cleanup_(wiphy_freep) Wiphy *w = NULL; |
fc85b323 YW |
34 | _cleanup_free_ char *name = NULL; |
35 | uint32_t index; | |
edb69db2 YW |
36 | int r; |
37 | ||
38 | assert(manager); | |
fc85b323 YW |
39 | assert(message); |
40 | ||
41 | r = sd_netlink_message_read_u32(message, NL80211_ATTR_WIPHY, &index); | |
42 | if (r < 0) | |
43 | return r; | |
44 | ||
45 | r = sd_netlink_message_read_string_strdup(message, NL80211_ATTR_WIPHY_NAME, &name); | |
46 | if (r < 0) | |
47 | return r; | |
edb69db2 YW |
48 | |
49 | w = new(Wiphy, 1); | |
50 | if (!w) | |
51 | return -ENOMEM; | |
52 | ||
53 | *w = (Wiphy) { | |
fc85b323 | 54 | .manager = manager, |
edb69db2 | 55 | .index = index, |
fc85b323 | 56 | .name = TAKE_PTR(name), |
edb69db2 YW |
57 | }; |
58 | ||
59 | r = hashmap_ensure_put(&manager->wiphy_by_index, NULL, UINT32_TO_PTR(w->index), w); | |
60 | if (r < 0) | |
61 | return r; | |
62 | ||
fc85b323 YW |
63 | r = hashmap_ensure_put(&w->manager->wiphy_by_name, &string_hash_ops, w->name, w); |
64 | if (r < 0) | |
65 | return r; | |
66 | ||
67 | log_wiphy_debug(w, "Saved new wiphy: index=%"PRIu32, w->index); | |
edb69db2 YW |
68 | |
69 | if (ret) | |
70 | *ret = w; | |
71 | ||
72 | TAKE_PTR(w); | |
73 | return 0; | |
74 | } | |
75 | ||
76 | int wiphy_get_by_index(Manager *manager, uint32_t index, Wiphy **ret) { | |
77 | Wiphy *w; | |
78 | ||
79 | assert(manager); | |
80 | ||
81 | w = hashmap_get(manager->wiphy_by_index, UINT32_TO_PTR(index)); | |
82 | if (!w) | |
83 | return -ENODEV; | |
84 | ||
85 | if (ret) | |
86 | *ret = w; | |
87 | ||
88 | return 0; | |
89 | } | |
90 | ||
91 | int wiphy_get_by_name(Manager *manager, const char *name, Wiphy **ret) { | |
92 | Wiphy *w; | |
93 | ||
94 | assert(manager); | |
95 | assert(name); | |
96 | ||
97 | w = hashmap_get(manager->wiphy_by_name, name); | |
98 | if (!w) | |
99 | return -ENODEV; | |
100 | ||
101 | if (ret) | |
102 | *ret = w; | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
a9d2d037 YW |
107 | static int link_get_wiphy(Link *link, Wiphy **ret) { |
108 | _cleanup_(sd_device_unrefp) sd_device *phy = NULL; | |
109 | const char *s; | |
110 | int r; | |
111 | ||
112 | assert(link); | |
113 | assert(link->manager); | |
114 | ||
115 | if (link->iftype != ARPHRD_ETHER) | |
116 | return -EOPNOTSUPP; | |
117 | ||
118 | if (!link->dev) | |
4d79af57 | 119 | return -ENODEV; |
a9d2d037 YW |
120 | |
121 | r = sd_device_get_devtype(link->dev, &s); | |
122 | if (r < 0) | |
123 | return r; | |
124 | ||
125 | if (!streq_ptr(s, "wlan")) | |
126 | return -EOPNOTSUPP; | |
127 | ||
68a52f59 | 128 | r = sd_device_new_child(&phy, link->dev, "phy80211"); |
a9d2d037 YW |
129 | if (r < 0) |
130 | return r; | |
131 | ||
132 | r = sd_device_get_sysname(phy, &s); | |
133 | if (r < 0) | |
134 | return r; | |
135 | ||
136 | /* TODO: | |
137 | * Maybe, it is better to cache the found Wiphy object in the Link object. | |
138 | * To support that, we need to investigate what happens when the _phy_ is renamed. */ | |
139 | ||
140 | return wiphy_get_by_name(link->manager, s, ret); | |
141 | } | |
142 | ||
143 | static int rfkill_get_state(sd_device *dev) { | |
144 | int r; | |
145 | ||
146 | assert(dev); | |
147 | ||
148 | /* The previous values may be outdated. Let's clear cache and re-read the values. */ | |
149 | device_clear_sysattr_cache(dev); | |
150 | ||
151 | r = device_get_sysattr_bool(dev, "soft"); | |
152 | if (r < 0 && r != -ENOENT) | |
153 | return r; | |
154 | if (r > 0) | |
155 | return RFKILL_SOFT; | |
156 | ||
157 | r = device_get_sysattr_bool(dev, "hard"); | |
158 | if (r < 0 && r != -ENOENT) | |
159 | return r; | |
160 | if (r > 0) | |
161 | return RFKILL_HARD; | |
162 | ||
163 | return RFKILL_UNBLOCKED; | |
164 | } | |
165 | ||
166 | static int wiphy_rfkilled(Wiphy *w) { | |
167 | int r; | |
168 | ||
169 | assert(w); | |
170 | ||
171 | if (!udev_available()) { | |
172 | if (w->rfkill_state != RFKILL_UNBLOCKED) { | |
173 | log_wiphy_debug(w, "Running in container, assuming the radio transmitter is unblocked."); | |
174 | w->rfkill_state = RFKILL_UNBLOCKED; /* To suppress the above log message, cache the state. */ | |
175 | } | |
176 | return false; | |
177 | } | |
178 | ||
179 | if (!w->rfkill) { | |
180 | if (w->rfkill_state != RFKILL_UNBLOCKED) { | |
181 | log_wiphy_debug(w, "No rfkill device found, assuming the radio transmitter is unblocked."); | |
182 | w->rfkill_state = RFKILL_UNBLOCKED; /* To suppress the above log message, cache the state. */ | |
183 | } | |
184 | return false; | |
185 | } | |
186 | ||
187 | r = rfkill_get_state(w->rfkill); | |
188 | if (r < 0) | |
189 | return log_wiphy_debug_errno(w, r, "Could not get rfkill state: %m"); | |
190 | ||
191 | if (w->rfkill_state != r) | |
192 | switch (r) { | |
193 | case RFKILL_UNBLOCKED: | |
194 | log_wiphy_debug(w, "The radio transmitter is unblocked."); | |
195 | break; | |
196 | case RFKILL_SOFT: | |
197 | log_wiphy_debug(w, "The radio transmitter is turned off by software."); | |
198 | break; | |
199 | case RFKILL_HARD: | |
200 | log_wiphy_debug(w, "The radio transmitter is forced off by something outside of the driver's control."); | |
201 | break; | |
202 | default: | |
203 | assert_not_reached(); | |
204 | } | |
205 | ||
206 | w->rfkill_state = r; /* Cache the state to suppress the above log messages. */ | |
207 | return r != RFKILL_UNBLOCKED; | |
208 | } | |
209 | ||
210 | int link_rfkilled(Link *link) { | |
211 | Wiphy *w; | |
212 | int r; | |
213 | ||
214 | assert(link); | |
215 | ||
216 | r = link_get_wiphy(link, &w); | |
bb44fd07 ZJS |
217 | if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || ERRNO_IS_NEG_DEVICE_ABSENT(r)) |
218 | return false; /* Typically, non-wifi interface or running in container */ | |
219 | if (r < 0) | |
a9d2d037 YW |
220 | return log_link_debug_errno(link, r, "Could not get phy: %m"); |
221 | ||
222 | return wiphy_rfkilled(w); | |
223 | } | |
224 | ||
edb69db2 YW |
225 | static int wiphy_update_name(Wiphy *w, sd_netlink_message *message) { |
226 | const char *name; | |
227 | int r; | |
228 | ||
229 | assert(w); | |
230 | assert(w->manager); | |
231 | assert(message); | |
232 | ||
233 | r = sd_netlink_message_read_string(message, NL80211_ATTR_WIPHY_NAME, &name); | |
234 | if (r == -ENODATA) | |
235 | return 0; | |
236 | if (r < 0) | |
237 | return r; | |
238 | ||
fc85b323 | 239 | if (streq(w->name, name)) |
edb69db2 YW |
240 | return 0; |
241 | ||
fc85b323 YW |
242 | log_wiphy_debug(w, "Wiphy name change detected, renamed to %s.", name); |
243 | ||
244 | hashmap_remove_value(w->manager->wiphy_by_name, w->name, w); | |
edb69db2 YW |
245 | |
246 | r = free_and_strdup(&w->name, name); | |
247 | if (r < 0) | |
248 | return r; | |
249 | ||
fc85b323 | 250 | r = hashmap_ensure_put(&w->manager->wiphy_by_name, &string_hash_ops, w->name, w); |
edb69db2 | 251 | if (r < 0) |
fc85b323 | 252 | return r; |
edb69db2 | 253 | |
fc85b323 | 254 | return 1; /* updated */ |
edb69db2 YW |
255 | } |
256 | ||
c49d5362 YW |
257 | static int wiphy_update_device(Wiphy *w) { |
258 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; | |
259 | int r; | |
260 | ||
261 | assert(w); | |
262 | assert(w->name); | |
263 | ||
264 | if (!udev_available()) | |
265 | return 0; | |
266 | ||
267 | w->dev = sd_device_unref(w->dev); | |
268 | ||
269 | r = sd_device_new_from_subsystem_sysname(&dev, "ieee80211", w->name); | |
4d79af57 YW |
270 | if (r < 0) |
271 | return r; | |
c49d5362 YW |
272 | |
273 | if (DEBUG_LOGGING) { | |
274 | const char *s = NULL; | |
275 | ||
276 | (void) sd_device_get_syspath(dev, &s); | |
277 | log_wiphy_debug(w, "Found device: %s", strna(s)); | |
278 | } | |
279 | ||
280 | w->dev = TAKE_PTR(dev); | |
281 | return 0; | |
282 | } | |
283 | ||
8642e04b YW |
284 | static int wiphy_update_rfkill(Wiphy *w) { |
285 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; | |
286 | sd_device *rfkill; | |
287 | int r; | |
288 | ||
289 | assert(w); | |
290 | ||
291 | if (!udev_available()) | |
292 | return 0; | |
293 | ||
294 | w->rfkill = sd_device_unref(w->rfkill); | |
295 | ||
296 | if (!w->dev) | |
297 | return 0; | |
298 | ||
299 | r = sd_device_enumerator_new(&e); | |
300 | if (r < 0) | |
301 | return r; | |
302 | ||
303 | r = sd_device_enumerator_allow_uninitialized(e); | |
304 | if (r < 0) | |
305 | return r; | |
306 | ||
307 | r = sd_device_enumerator_add_match_subsystem(e, "rfkill", true); | |
308 | if (r < 0) | |
309 | return r; | |
310 | ||
311 | r = sd_device_enumerator_add_match_parent(e, w->dev); | |
312 | if (r < 0) | |
313 | return r; | |
314 | ||
315 | rfkill = sd_device_enumerator_get_device_first(e); | |
4d79af57 | 316 | if (!rfkill) |
8642e04b | 317 | /* rfkill device may not detected by the kernel yet, and may appear later. */ |
4d79af57 | 318 | return -ENODEV; |
8642e04b YW |
319 | |
320 | if (sd_device_enumerator_get_device_next(e)) | |
4d79af57 | 321 | return -ENXIO; /* multiple devices found */ |
8642e04b YW |
322 | |
323 | w->rfkill = sd_device_ref(rfkill); | |
324 | ||
325 | if (DEBUG_LOGGING) { | |
326 | const char *s = NULL; | |
327 | ||
328 | (void) sd_device_get_syspath(rfkill, &s); | |
329 | log_wiphy_debug(w, "Found rfkill device: %s", strna(s)); | |
330 | } | |
331 | ||
332 | return 0; | |
333 | } | |
334 | ||
c49d5362 YW |
335 | static int wiphy_update(Wiphy *w) { |
336 | int r; | |
337 | ||
338 | assert(w); | |
339 | ||
340 | r = wiphy_update_device(w); | |
bb44fd07 ZJS |
341 | if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) |
342 | log_wiphy_debug_errno(w, r, "Failed to update wiphy device, ignoring: %m"); | |
343 | else if (r < 0) | |
344 | return log_wiphy_warning_errno(w, r, "Failed to update wiphy device: %m"); | |
c49d5362 | 345 | |
8642e04b | 346 | r = wiphy_update_rfkill(w); |
bb44fd07 ZJS |
347 | if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) |
348 | log_wiphy_debug_errno(w, r, "Failed to update rfkill device, ignoring: %m"); | |
349 | else if (r < 0) | |
350 | return log_wiphy_warning_errno(w, r, "Failed to update rfkill device: %m"); | |
8642e04b | 351 | |
c49d5362 YW |
352 | return 0; |
353 | } | |
354 | ||
edb69db2 YW |
355 | int manager_genl_process_nl80211_wiphy(sd_netlink *genl, sd_netlink_message *message, Manager *manager) { |
356 | const char *family; | |
357 | uint32_t index; | |
358 | uint8_t cmd; | |
359 | Wiphy *w = NULL; | |
360 | int r; | |
361 | ||
362 | assert(genl); | |
363 | assert(message); | |
364 | assert(manager); | |
365 | ||
366 | if (sd_netlink_message_is_error(message)) { | |
367 | r = sd_netlink_message_get_errno(message); | |
368 | if (r < 0) | |
369 | log_message_warning_errno(message, r, "nl80211: received error message, ignoring"); | |
370 | ||
371 | return 0; | |
372 | } | |
373 | ||
374 | r = sd_genl_message_get_family_name(genl, message, &family); | |
375 | if (r < 0) { | |
376 | log_debug_errno(r, "nl80211: failed to determine genl family, ignoring: %m"); | |
377 | return 0; | |
378 | } | |
379 | if (!streq(family, NL80211_GENL_NAME)) { | |
380 | log_debug("nl80211: Received message of unexpected genl family '%s', ignoring.", family); | |
381 | return 0; | |
382 | } | |
383 | ||
384 | r = sd_genl_message_get_command(genl, message, &cmd); | |
385 | if (r < 0) { | |
386 | log_debug_errno(r, "nl80211: failed to determine genl message command, ignoring: %m"); | |
387 | return 0; | |
388 | } | |
389 | ||
390 | r = sd_netlink_message_read_u32(message, NL80211_ATTR_WIPHY, &index); | |
391 | if (r < 0) { | |
392 | log_debug_errno(r, "nl80211: received %s(%u) message without valid index, ignoring: %m", | |
393 | strna(nl80211_cmd_to_string(cmd)), cmd); | |
394 | return 0; | |
395 | } | |
396 | ||
397 | (void) wiphy_get_by_index(manager, index, &w); | |
398 | ||
399 | switch (cmd) { | |
400 | case NL80211_CMD_NEW_WIPHY: { | |
edb69db2 YW |
401 | |
402 | if (!w) { | |
fc85b323 | 403 | r = wiphy_new(manager, message, &w); |
edb69db2 | 404 | if (r < 0) { |
fc85b323 | 405 | log_warning_errno(r, "Failed to save new wiphy, ignoring: %m"); |
edb69db2 YW |
406 | return 0; |
407 | } | |
fc85b323 YW |
408 | } else { |
409 | r = wiphy_update_name(w, message); | |
410 | if (r < 0) { | |
411 | log_wiphy_warning_errno(w, r, "Failed to update wiphy name, ignoring: %m"); | |
412 | return 0; | |
413 | } | |
414 | if (r == 0) | |
415 | return 0; | |
edb69db2 YW |
416 | } |
417 | ||
c49d5362 YW |
418 | r = wiphy_update(w); |
419 | if (r < 0) | |
420 | log_wiphy_warning_errno(w, r, "Failed to update wiphy, ignoring: %m"); | |
421 | ||
edb69db2 YW |
422 | break; |
423 | } | |
424 | case NL80211_CMD_DEL_WIPHY: | |
425 | ||
426 | if (!w) { | |
427 | log_debug("The kernel removes wiphy we do not know, ignoring: %m"); | |
428 | return 0; | |
429 | } | |
430 | ||
431 | log_wiphy_debug(w, "Removed."); | |
432 | wiphy_free(w); | |
433 | break; | |
434 | ||
435 | default: | |
436 | log_wiphy_debug(w, "nl80211: received %s(%u) message.", | |
437 | strna(nl80211_cmd_to_string(cmd)), cmd); | |
438 | } | |
439 | ||
440 | return 0; | |
441 | } | |
c49d5362 YW |
442 | |
443 | int manager_udev_process_wiphy(Manager *m, sd_device *device, sd_device_action_t action) { | |
444 | const char *name; | |
445 | Wiphy *w; | |
446 | int r; | |
447 | ||
448 | assert(m); | |
449 | assert(device); | |
450 | ||
451 | r = sd_device_get_sysname(device, &name); | |
452 | if (r < 0) | |
453 | return log_device_debug_errno(device, r, "Failed to get sysname: %m"); | |
454 | ||
455 | r = wiphy_get_by_name(m, name, &w); | |
456 | if (r < 0) { | |
457 | /* This error is not critical, as the corresponding genl message may be received later. */ | |
458 | log_device_debug_errno(device, r, "Failed to get Wiphy object, ignoring: %m"); | |
459 | return 0; | |
460 | } | |
461 | ||
462 | return device_unref_and_replace(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); | |
463 | } | |
8642e04b YW |
464 | |
465 | int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_t action) { | |
466 | _cleanup_free_ char *parent_path = NULL, *parent_name = NULL; | |
467 | const char *s; | |
468 | Wiphy *w; | |
469 | int r; | |
470 | ||
471 | assert(m); | |
472 | assert(device); | |
473 | ||
474 | r = sd_device_get_syspath(device, &s); | |
475 | if (r < 0) | |
476 | return log_device_debug_errno(device, r, "Failed to get syspath: %m"); | |
477 | ||
478 | /* Do not use sd_device_get_parent() here, as this might be a 'remove' uevent. */ | |
479 | r = path_extract_directory(s, &parent_path); | |
480 | if (r < 0) | |
481 | return log_device_debug_errno(device, r, "Failed to get parent syspath: %m"); | |
482 | ||
483 | r = path_extract_filename(parent_path, &parent_name); | |
484 | if (r < 0) | |
485 | return log_device_debug_errno(device, r, "Failed to get parent name: %m"); | |
486 | ||
487 | r = wiphy_get_by_name(m, parent_name, &w); | |
488 | if (r < 0) { | |
489 | /* This error is not critical, as the corresponding genl message may be received later. */ | |
490 | log_device_debug_errno(device, r, "Failed to get Wiphy object: %m"); | |
491 | return 0; | |
492 | } | |
493 | ||
494 | return device_unref_and_replace(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); | |
495 | } |