/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2005 - 2018 Red Hat, Inc. * Copyright (C) 2006 - 2008 Novell, Inc. * Copyright (C) 2011 Intel Corporation. All rights reserved. */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-wifi-utils-nl80211.h" #include "libnm-std-aux/nm-linux-compat.h" #include #include #include #include #include "libnm-log-core/nm-logging.h" #include "libnm-platform/nm-netlink.h" #include "nm-wifi-utils-private.h" #include "libnm-platform/nm-platform-utils.h" #define _NMLOG_PREFIX_NAME "wifi-nl80211" #define _NMLOG_DOMAIN LOGD_PLATFORM | LOGD_WIFI #define _NMLOG(level, ...) \ G_STMT_START \ { \ char _ifname_buf[IFNAMSIZ]; \ const char *_ifname = \ self ? nmp_utils_if_indextoname(self->parent.ifindex, _ifname_buf) : NULL; \ \ nm_log((level), \ _NMLOG_DOMAIN, \ _ifname ?: NULL, \ NULL, \ "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ G_STMT_END typedef struct { guint32 freq; bool disabled : 1; bool no_ir : 1; } Nl80211Freq; typedef struct { NMWifiUtils parent; struct nl_sock *nl_sock; Nl80211Freq *freqs; int num_freqs; int phy; guint16 genl_family_id; bool can_wowlan : 1; } NMWifiUtilsNl80211; typedef struct { NMWifiUtilsClass parent; } NMWifiUtilsNl80211Class; G_DEFINE_TYPE(NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS) static int ack_handler(const struct nl_msg *msg, void *arg) { int *done = arg; *done = 1; return NL_STOP; } static int finish_handler(const struct nl_msg *msg, void *arg) { int *done = arg; *done = 1; return NL_SKIP; } static int error_handler(const struct sockaddr_nl *nla, const struct nlmsgerr *err, void *arg) { int *done = arg; *done = err->error; return NL_SKIP; } static struct nl_msg * _nl80211_alloc_msg(guint16 genl_family_id, int ifindex, int phy, uint8_t cmd, uint16_t flags) { nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nlmsg_alloc(0); genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family_id, 0, flags, cmd, 0); NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); if (phy != -1) NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, phy); return g_steal_pointer(&msg); nla_put_failure: g_return_val_if_reached(NULL); } static struct nl_msg * nl80211_alloc_msg(NMWifiUtilsNl80211 *self, uint8_t cmd, uint16_t flags) { return _nl80211_alloc_msg(self->genl_family_id, self->parent.ifindex, self->phy, cmd, flags); } static int nl80211_send_and_recv(NMWifiUtilsNl80211 *self, struct nl_msg *msg, int (*valid_handler)(const struct nl_msg *, void *), void *valid_data) { int err; int done = 0; const struct nl_cb cb = { .err_cb = error_handler, .err_arg = &done, .finish_cb = finish_handler, .finish_arg = &done, .ack_cb = ack_handler, .ack_arg = &done, .valid_cb = valid_handler, .valid_arg = valid_data, }; g_return_val_if_fail(msg != NULL, -ENOMEM); err = nl_send_auto(self->nl_sock, msg); if (err < 0) return err; /* Loop until one of our NL callbacks says we're done; on success * done will be 1, on error it will be < 0. */ while (!done) { err = nl_recvmsgs(self->nl_sock, &cb); if (err < 0 && err != -EAGAIN) { /* Kernel scan list can change while we are dumping it, as new scan * results from H/W can arrive. BSS info is assured to be consistent * and we don't need consistent view of whole scan list. Hence do * not warn on DUMP_INTR error for get scan command. */ if (err == -NME_NL_DUMP_INTR && genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN) break; _LOGW("nl_recvmsgs() error: (%d) %s", err, nm_strerror(err)); break; } } if (err >= 0 && done < 0) err = done; return err; } static void dispose(GObject *object) { NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211(object); nm_clear_g_free(&self->freqs); } struct nl80211_iface_info { _NM80211Mode mode; uint32_t freq; }; static int nl80211_iface_info_handler(const struct nl_msg *msg, void *arg) { struct nl80211_iface_info *info = arg; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *tb[NL80211_ATTR_MAX + 1]; if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) return NL_SKIP; if (!tb[NL80211_ATTR_IFTYPE]) return NL_SKIP; switch (nla_get_u32(tb[NL80211_ATTR_IFTYPE])) { case NL80211_IFTYPE_ADHOC: info->mode = _NM_802_11_MODE_ADHOC; break; case NL80211_IFTYPE_AP: info->mode = _NM_802_11_MODE_AP; break; case NL80211_IFTYPE_STATION: info->mode = _NM_802_11_MODE_INFRA; break; case NL80211_IFTYPE_MESH_POINT: info->mode = _NM_802_11_MODE_MESH; break; } if (tb[NL80211_ATTR_WIPHY_FREQ] != NULL) info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); return NL_SKIP; } static _NM80211Mode wifi_nl80211_get_mode(NMWifiUtils *data) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; struct nl80211_iface_info iface_info = { .mode = _NM_802_11_MODE_UNKNOWN, }; nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) return _NM_802_11_MODE_UNKNOWN; return iface_info.mode; } static gboolean wifi_nl80211_set_mode(NMWifiUtils *data, const _NM80211Mode mode) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); switch (mode) { case _NM_802_11_MODE_INFRA: NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION); break; case _NM_802_11_MODE_ADHOC: NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_ADHOC); break; case _NM_802_11_MODE_AP: NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP); break; case _NM_802_11_MODE_MESH: NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT); break; default: g_assert_not_reached(); } err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } static gboolean wifi_nl80211_set_powersave(NMWifiUtils *data, guint32 powersave) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg(self, NL80211_CMD_SET_POWER_SAVE, 0); NLA_PUT_U32(msg, NL80211_ATTR_PS_STATE, powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED); err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } static int nl80211_get_wake_on_wlan_handler(const struct nl_msg *msg, void *arg) { _NMSettingWirelessWakeOnWLan *wowl = arg; struct nlattr *attrs[NL80211_ATTR_MAX + 1]; struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); nla_parse_arr(attrs, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) return NL_SKIP; nla_parse_arr(trig, nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), NULL); *wowl = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE; if (trig[NL80211_WOWLAN_TRIG_ANY]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY; if (trig[NL80211_WOWLAN_TRIG_DISCONNECT]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT; if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC; if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE; if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST; if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE; if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE; if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP; return NL_SKIP; } static _NMSettingWirelessWakeOnWLan wifi_nl80211_get_wake_on_wlan(NMWifiUtils *data) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; _NMSettingWirelessWakeOnWLan wowl = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WOWLAN, 0); nl80211_send_and_recv(self, msg, nl80211_get_wake_on_wlan_handler, &wowl); return wowl; } static gboolean wifi_nl80211_set_wake_on_wlan(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; struct nlattr *triggers; int err; if (wowl == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) return TRUE; msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WOWLAN, 0); triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); if (!triggers) goto nla_put_failure; if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE)) NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); nla_nest_end(msg, triggers); err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } static guint32 wifi_nl80211_get_freq(NMWifiUtils *data) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; struct nl80211_iface_info iface_info = {}; nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) return 0; return iface_info.freq; } static guint32 wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs, gboolean ap) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; int i; int j; /* It's important to check the values in the order of @freqs, because * that array might be sorted to contain preferred frequencies first. */ for (j = 0; freqs[j] != 0; j++) { for (i = 0; i < self->num_freqs; i++) { if (self->freqs[i].disabled) continue; if (ap && self->freqs[i].no_ir) continue; if (self->freqs[i].freq == freqs[j]) return freqs[j]; } } return 0; } /* @divisor: pass what value @xbm should be divided by to get dBm */ static guint32 nl80211_xbm_to_percent(gint32 xbm, guint32 divisor) { #define NOISE_FLOOR_DBM -90 #define SIGNAL_MAX_DBM -20 xbm /= divisor; xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM); return 100 - 70 * (((float) SIGNAL_MAX_DBM - (float) xbm) / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM)); } struct nl80211_station_info { gboolean valid; guint8 bssid[ETH_ALEN]; guint32 txrate; gboolean txrate_valid; guint8 signal; gboolean signal_valid; }; static int nl80211_station_dump_handler(const struct nl_msg *msg, void *arg) { static const struct nla_policy stats_policy[] = { [NL80211_STA_INFO_INACTIVE_TIME] = {.type = NLA_U32}, [NL80211_STA_INFO_RX_BYTES] = {.type = NLA_U32}, [NL80211_STA_INFO_TX_BYTES] = {.type = NLA_U32}, [NL80211_STA_INFO_RX_PACKETS] = {.type = NLA_U32}, [NL80211_STA_INFO_TX_PACKETS] = {.type = NLA_U32}, [NL80211_STA_INFO_SIGNAL] = {.type = NLA_U8}, [NL80211_STA_INFO_TX_BITRATE] = {.type = NLA_NESTED}, [NL80211_STA_INFO_LLID] = {.type = NLA_U16}, [NL80211_STA_INFO_PLID] = {.type = NLA_U16}, [NL80211_STA_INFO_PLINK_STATE] = {.type = NLA_U8}, [NL80211_STA_INFO_STA_FLAGS] = {.minlen = sizeof(struct nl80211_sta_flag_update)}, [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = {.type = NLA_U8}, }; static const struct nla_policy rate_policy[] = { [NL80211_RATE_INFO_BITRATE] = {.type = NLA_U16}, [NL80211_RATE_INFO_MCS] = {.type = NLA_U8}, [NL80211_RATE_INFO_40_MHZ_WIDTH] = {.type = NLA_FLAG}, [NL80211_RATE_INFO_SHORT_GI] = {.type = NLA_FLAG}, }; struct nlattr *rinfo[G_N_ELEMENTS(rate_policy)]; struct nlattr *sinfo[G_N_ELEMENTS(stats_policy)]; struct nl80211_station_info *info = arg; struct nlattr *tb[NL80211_ATTR_MAX + 1]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) return NL_SKIP; if (tb[NL80211_ATTR_MAC] == NULL) return NL_SKIP; if (tb[NL80211_ATTR_STA_INFO] == NULL) return NL_SKIP; if (nla_parse_nested_arr(sinfo, tb[NL80211_ATTR_STA_INFO], stats_policy)) return NL_SKIP; if (sinfo[NL80211_STA_INFO_STA_FLAGS] != NULL) { const struct nl80211_sta_flag_update *flags = nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]); if (flags->mask & ~flags->set & (1 << NL80211_STA_FLAG_ASSOCIATED)) return NL_SKIP; } memcpy(info->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN); info->valid = TRUE; if (sinfo[NL80211_STA_INFO_TX_BITRATE] != NULL && !nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy) && rinfo[NL80211_RATE_INFO_BITRATE] != NULL) { /* convert from nl80211's units of 100kbps to NM's kbps */ info->txrate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100; info->txrate_valid = TRUE; } if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) { info->signal = nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), 1); info->signal_valid = TRUE; } else if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG] != NULL) { /* Fall back to beacon signal strength */ info->signal = nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]), 1); info->signal_valid = TRUE; } return NL_SKIP; } static gboolean wifi_nl80211_get_station(NMWifiUtils *data, NMEtherAddr *out_bssid, int *out_quality, guint32 *out_rate) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_station_info sta_info = {}; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, NLM_F_DUMP); nl80211_send_and_recv(self, msg, nl80211_station_dump_handler, &sta_info); if (!sta_info.valid || (out_quality && !sta_info.signal_valid) || (out_rate && !sta_info.txrate_valid)) return FALSE; if (out_bssid) memcpy(out_bssid, sta_info.bssid, ETH_ALEN); if (out_quality) *out_quality = sta_info.signal; if (out_rate) *out_rate = sta_info.txrate; return TRUE; } static gboolean wifi_nl80211_indicate_addressing_running(NMWifiUtils *data, gboolean running) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg(self, running ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */ : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */, 0); /* Despite the DHCP name, we're using this for any type of IP addressing, * DHCPv4, DHCPv6, and IPv6 SLAAC. */ NLA_PUT_U16(msg, 179 /* NL80211_ATTR_CRIT_PROT_ID */, 1 /* NL80211_CRIT_PROTO_DHCP */); if (running) { /* Give DHCP 5 seconds to complete */ NLA_PUT_U16(msg, 180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */, 5000); } err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } struct nl80211_device_info { NMWifiUtilsNl80211 *self; int phy; Nl80211Freq *freqs; int num_freqs; int num_freqs_alloc; guint32 freq; guint32 caps; gboolean can_scan; gboolean can_scan_ssid; gboolean supported; gboolean success; gboolean can_wowlan; }; #define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00 #define WLAN_CIPHER_SUITE_WEP40 0x000FAC01 #define WLAN_CIPHER_SUITE_TKIP 0x000FAC02 #define WLAN_CIPHER_SUITE_CCMP 0x000FAC04 #define WLAN_CIPHER_SUITE_WEP104 0x000FAC05 #define WLAN_CIPHER_SUITE_AES_CMAC 0x000FAC06 #define WLAN_CIPHER_SUITE_GCMP 0x000FAC08 #define WLAN_CIPHER_SUITE_SMS4 0x00147201 static int nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg) { static const struct nla_policy freq_policy[] = { [NL80211_FREQUENCY_ATTR_FREQ] = {.type = NLA_U32}, [NL80211_FREQUENCY_ATTR_DISABLED] = {.type = NLA_FLAG}, #ifdef NL80211_FREQUENCY_ATTR_NO_IR [NL80211_FREQUENCY_ATTR_NO_IR] = {.type = NLA_FLAG}, #else [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = {.type = NLA_FLAG}, [NL80211_FREQUENCY_ATTR_NO_IBSS] = {.type = NLA_FLAG}, #endif [NL80211_FREQUENCY_ATTR_RADAR] = {.type = NLA_FLAG}, [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = {.type = NLA_U32}, }; struct nlattr *tb[NL80211_ATTR_MAX + 1]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nl80211_device_info *info = arg; NMWifiUtilsNl80211 *self = info->self; struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; struct nlattr *tb_freq[G_N_ELEMENTS(freq_policy)]; struct nlattr *nl_band; struct nlattr *nl_freq; int rem_freq; int rem_band; #ifdef NL80211_FREQUENCY_ATTR_NO_IR G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR); #else G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS); #endif if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) return NL_SKIP; if (!tb[NL80211_ATTR_WIPHY]) return NL_SKIP; info->phy = nla_get_u32(tb[NL80211_ATTR_WIPHY]); if (tb[NL80211_ATTR_WIPHY_FREQ]) info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) info->can_scan_ssid = nla_get_u8(tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0; if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) { struct nlattr *nl_cmd; int i; nla_for_each_nested (nl_cmd, tb[NL80211_ATTR_SUPPORTED_COMMANDS], i) { switch (nla_get_u32(nl_cmd)) { case NL80211_CMD_TRIGGER_SCAN: info->can_scan = TRUE; break; case NL80211_CMD_CONNECT: case NL80211_CMD_AUTHENTICATE: /* Only devices that support CONNECT or AUTH actually support * 802.11, unlike say ipw2x00 (up to at least kernel 3.4) which * has minimal info support, but no actual command support. * This check mirrors what wpa_supplicant does to determine * whether or not to use the nl80211 driver. */ info->supported = TRUE; break; default: break; } } } if (tb[NL80211_ATTR_WIPHY_BANDS]) { /* Read supported frequencies */ if (!info->freqs) { info->num_freqs = 0; info->num_freqs_alloc = 32; info->freqs = g_new(Nl80211Freq, info->num_freqs_alloc); } nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0) return NL_SKIP; if (!tb_band[NL80211_BAND_ATTR_FREQS]) continue; nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { Nl80211Freq *f; if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0) continue; if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) continue; if (info->num_freqs >= info->num_freqs_alloc) { info->num_freqs_alloc *= 2; info->freqs = g_renew(Nl80211Freq, info->freqs, info->num_freqs_alloc); } f = &info->freqs[info->num_freqs]; *f = (Nl80211Freq) { .freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]), .disabled = !!tb_freq[NL80211_FREQUENCY_ATTR_DISABLED], .no_ir = !!tb_freq[NL80211_FREQUENCY_ATTR_NO_IR], }; info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID; if (f->freq >= 2401 && f->freq <= 2495) info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ; if (f->freq >= 5150 && f->freq < 5950) info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ; if (f->freq >= 5950 && f->freq <= 7125) info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_6GHZ; info->num_freqs++; } } } /* Read security/encryption support */ if (tb[NL80211_ATTR_CIPHER_SUITES]) { guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]); guint i, num; num = nla_len(tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof(guint32); for (i = 0; i < num; i++) { switch (ciphers[i]) { case WLAN_CIPHER_SUITE_WEP40: info->caps |= _NM_WIFI_DEVICE_CAP_CIPHER_WEP40; break; case WLAN_CIPHER_SUITE_WEP104: info->caps |= _NM_WIFI_DEVICE_CAP_CIPHER_WEP104; break; case WLAN_CIPHER_SUITE_TKIP: info->caps |= (_NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_WPA); break; case WLAN_CIPHER_SUITE_CCMP: info->caps |= (_NM_WIFI_DEVICE_CAP_CIPHER_CCMP | _NM_WIFI_DEVICE_CAP_RSN); break; case WLAN_CIPHER_SUITE_AES_CMAC: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_SMS4: break; default: _LOGD("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.", ciphers[i]); break; } } } if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) { struct nlattr *nl_mode; int i; nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) { switch (nla_type(nl_mode)) { case NL80211_IFTYPE_AP: info->caps |= _NM_WIFI_DEVICE_CAP_AP; break; case NL80211_IFTYPE_ADHOC: info->caps |= _NM_WIFI_DEVICE_CAP_ADHOC; break; case NL80211_IFTYPE_MESH_POINT: info->caps |= _NM_WIFI_DEVICE_CAP_MESH; break; } } } if (tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]) info->can_wowlan = TRUE; if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN]) info->caps |= _NM_WIFI_DEVICE_CAP_IBSS_RSN; info->success = TRUE; return NL_SKIP; } static guint32 wifi_nl80211_get_mesh_channel(NMWifiUtils *data) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_device_info device_info = {.self = self}; int i; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0); if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { _LOGW("NL80211_CMD_GET_WIPHY request failed"); return 0; } for (i = 0; i < self->num_freqs; i++) { if (device_info.freq == self->freqs[i].freq) return i + 1; } return 0; } static gboolean wifi_nl80211_set_mesh_channel(NMWifiUtils *data, guint32 channel) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; int err; if (channel > self->num_freqs) return FALSE; msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WIPHY, 0); NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1].freq); err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } static gboolean wifi_nl80211_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) { NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); NLA_PUT(msg, NL80211_ATTR_MESH_ID, len, ssid); err = nl80211_send_and_recv(self, msg, NULL, NULL); return err >= 0; nla_put_failure: g_return_val_if_reached(FALSE); } static void nm_wifi_utils_nl80211_init(NMWifiUtilsNl80211 *self) {} static void nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass); object_class->dispose = dispose; wifi_utils_class->get_mode = wifi_nl80211_get_mode; wifi_utils_class->set_mode = wifi_nl80211_set_mode; wifi_utils_class->set_powersave = wifi_nl80211_set_powersave; wifi_utils_class->get_wake_on_wlan = wifi_nl80211_get_wake_on_wlan, wifi_utils_class->set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan, wifi_utils_class->get_freq = wifi_nl80211_get_freq; wifi_utils_class->find_freq = wifi_nl80211_find_freq; wifi_utils_class->get_station = wifi_nl80211_get_station; wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running; wifi_utils_class->get_mesh_channel = wifi_nl80211_get_mesh_channel; wifi_utils_class->set_mesh_channel = wifi_nl80211_set_mesh_channel; wifi_utils_class->set_mesh_ssid = wifi_nl80211_set_mesh_ssid; } NMWifiUtils * nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifindex) { gs_unref_object NMWifiUtilsNl80211 *self = NULL; nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_device_info device_info = {}; if (!genl) return NULL; if (genl_family_id == 0) return NULL; self = g_object_new(NM_TYPE_WIFI_UTILS_NL80211, NULL); self->parent.ifindex = ifindex; self->nl_sock = genl; self->genl_family_id = genl_family_id; self->phy = -1; msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, NLM_F_DUMP); NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP); device_info.can_scan_ssid = TRUE; device_info.self = self; if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { _LOGD("NL80211_CMD_GET_WIPHY request failed"); return NULL; } if (device_info.freqs) { device_info.freqs = g_renew(Nl80211Freq, device_info.freqs, device_info.num_freqs); } if (!device_info.success) { _LOGD("NL80211_CMD_GET_WIPHY request indicated failure"); return NULL; } if (!device_info.supported) { _LOGD("driver does not fully support nl80211, falling back to WEXT"); return NULL; } if (!device_info.can_scan_ssid) { _LOGE("driver does not support SSID scans"); return NULL; } if (device_info.num_freqs == 0 || device_info.freqs == NULL) { _LOGE("driver reports no supported frequencies"); return NULL; } if (device_info.caps == 0) { _LOGE("driver doesn't report support of any encryption"); return NULL; } self->phy = device_info.phy; self->freqs = device_info.freqs; self->num_freqs = device_info.num_freqs; self->parent.caps = device_info.caps; self->can_wowlan = device_info.can_wowlan; _LOGD("using nl80211 for Wi-Fi device control"); return (NMWifiUtils *) g_steal_pointer(&self); nla_put_failure: g_return_val_if_reached(NULL); }