diff options
Diffstat (limited to '')
-rw-r--r-- | iw_nl80211.c | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/iw_nl80211.c b/iw_nl80211.c new file mode 100644 index 0000000..5b9c562 --- /dev/null +++ b/iw_nl80211.c @@ -0,0 +1,730 @@ +/* + * FIXME: + * PROTOTYPE: add nl80211 calls to iw_if. Mostly copied/stolen from iw + */ +#include "wavemon.h" +#include <net/if.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "iw_nl80211.h" + +/** + * handle_cmd: process @cmd + * Returns 0 if ok, -errno < 0 on failure + * stolen/modified from iw:iw.c + */ +int handle_cmd(struct cmd *cmd) +{ + struct nl_cb *cb; + struct nl_msg *msg; + static int nl80211_id = -1; + int ret; + uint32_t ifindex, idx; + + /* + * Initialization of static components: + * - per-cmd socket + * - global nl80211 ID + * - per-cmd interface index (in case conf_ifname() changes) + */ + if (!cmd->sk) { + cmd->sk = nl_socket_alloc(); + if (!cmd->sk) + err_sys("failed to allocate netlink socket"); + + /* NB: not setting sk buffer size, using default 32Kb */ + if (genl_connect(cmd->sk)) + err_sys("failed to connect to GeNetlink"); + } + + if (nl80211_id < 0) { + nl80211_id = genl_ctrl_resolve(cmd->sk, "nl80211"); + if (nl80211_id < 0) + err_sys("nl80211 not found"); + } + + ifindex = if_nametoindex(conf_ifname()); + if (ifindex == 0 && errno) + err_sys("failed to look up interface %s", conf_ifname()); + + /* + * Message Preparation + */ + msg = nlmsg_alloc(); + if (!msg) + err_sys("failed to allocate netlink message"); + + cb = nl_cb_alloc(IW_NL_CB_DEBUG ? NL_CB_DEBUG : NL_CB_DEFAULT); + if (!cb) + err_sys("failed to allocate netlink callback"); + + genlmsg_put(msg, 0, 0, nl80211_id, 0, cmd->flags, cmd->cmd, 0); + + /* netdev identifier: interface index */ + NLA_PUT(msg, NL80211_ATTR_IFINDEX, sizeof(ifindex), &ifindex); + + /* Additional attributes */ + if (cmd->msg_args) { + for (idx = 0; idx < cmd->msg_args_len; idx++) + NLA_PUT(msg, cmd->msg_args[idx].type, + cmd->msg_args[idx].len, + cmd->msg_args[idx].data); + } + + ret = nl_send_auto_complete(cmd->sk, msg); + if (ret < 0) + err_sys("failed to send netlink message"); + + /*------------------------------------------------------------------------- + * Receive loop + *-------------------------------------------------------------------------*/ + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + if (cmd->handler) + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cmd->handler, cmd->handler_arg); + + while (ret > 0) + nl_recvmsgs(cmd->sk, cb); + + nl_cb_put(cb); + nlmsg_free(msg); + goto out; + +nla_put_failure: + err_quit("failed to add attribute to netlink message"); +out: + return ret; +} + +/* + * STATION COMMANDS + */ +/* stolen from iw:station.c */ +void parse_bitrate(struct nlattr *bitrate_attr, char *buf, int buflen) +{ + int rate = 0; + char *pos = buf; + struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1]; + static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = { + [NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 }, + [NL80211_RATE_INFO_BITRATE32] = { .type = NLA_U32 }, + [NL80211_RATE_INFO_MCS] = { .type = NLA_U8 }, + [NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG }, + [NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG }, + }; + + if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX, + bitrate_attr, rate_policy)) { + snprintf(buf, buflen, "failed to parse nested rate attributes!"); + return; + } + + if (rinfo[NL80211_RATE_INFO_BITRATE32]) + rate = nla_get_u32(rinfo[NL80211_RATE_INFO_BITRATE32]); + else if (rinfo[NL80211_RATE_INFO_BITRATE]) + rate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]); + if (rate > 0) + pos += snprintf(pos, buflen - (pos - buf), + "%d.%d MBit/s", rate / 10, rate % 10); + + if (rinfo[NL80211_RATE_INFO_MCS]) + pos += snprintf(pos, buflen - (pos - buf), + " MCS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_MCS])); + if (rinfo[NL80211_RATE_INFO_VHT_MCS]) + pos += snprintf(pos, buflen - (pos - buf), + " VHT-MCS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_VHT_MCS])); + if (rinfo[NL80211_RATE_INFO_40_MHZ_WIDTH]) + pos += snprintf(pos, buflen - (pos - buf), " 40MHz"); + if (rinfo[NL80211_RATE_INFO_80_MHZ_WIDTH]) + pos += snprintf(pos, buflen - (pos - buf), " 80MHz"); + if (rinfo[NL80211_RATE_INFO_80P80_MHZ_WIDTH]) + pos += snprintf(pos, buflen - (pos - buf), " 80P80MHz"); + if (rinfo[NL80211_RATE_INFO_160_MHZ_WIDTH]) + pos += snprintf(pos, buflen - (pos - buf), " 160MHz"); + if (rinfo[NL80211_RATE_INFO_SHORT_GI]) + pos += snprintf(pos, buflen - (pos - buf), " short GI"); + if (rinfo[NL80211_RATE_INFO_VHT_NSS]) + pos += snprintf(pos, buflen - (pos - buf), + " VHT-NSS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_VHT_NSS])); +} + +/* + * INTERFACE COMMANDS + */ +void print_ssid_escaped(char *buf, const size_t buflen, + const uint8_t *data, const size_t datalen) +{ + int i, l; + + memset(buf, '\0', buflen); + /* Treat zeroed-out SSIDs separately */ + for (i = 0; i < datalen && data[i] == '\0'; i++) + ; + if (i == datalen) + return; + + for (i = l= 0; i < datalen; i++) { + if (l + 4 >= buflen) + return; + else if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\') + l += sprintf(buf + l, "%c", data[i]); + else if (data[i] == ' ' && i != 0 && i != datalen -1) + l += sprintf(buf + l, " "); + else + l += sprintf(buf + l, "\\x%.2x", data[i]); + } +} + +/* stolen from iw:interface.c */ +static int iface_handler(struct nl_msg *msg, void *arg) +{ + struct iw_nl80211_ifstat *ifs = (struct iw_nl80211_ifstat *)arg; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + + assert(ifs != NULL); + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (tb_msg[NL80211_ATTR_WIPHY]) + ifs->phy = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]); + + if (tb_msg[NL80211_ATTR_IFINDEX]) + ifs->ifindex = nla_get_u32(tb_msg[NL80211_ATTR_IFINDEX]); + + if (tb_msg[NL80211_ATTR_WDEV]) + ifs->wdev = nla_get_u64(tb_msg[NL80211_ATTR_WDEV]); + if (tb_msg[NL80211_ATTR_SSID]) + print_ssid_escaped(ifs->ssid, sizeof(ifs->ssid), + nla_data(tb_msg[NL80211_ATTR_SSID]), + nla_len(tb_msg[NL80211_ATTR_SSID])); + + if (tb_msg[NL80211_ATTR_IFTYPE]) + ifs->iftype = nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]); + + ifs->chan_width = -1; + ifs->chan_type = -1; + if (tb_msg[NL80211_ATTR_WIPHY_FREQ]) { + ifs->freq = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FREQ]); + + if (tb_msg[NL80211_ATTR_CHANNEL_WIDTH]) { + ifs->chan_width = nla_get_u32(tb_msg[NL80211_ATTR_CHANNEL_WIDTH]); + + if (tb_msg[NL80211_ATTR_CENTER_FREQ1]) + ifs->freq_ctr1 = nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ1]); + if (tb_msg[NL80211_ATTR_CENTER_FREQ2]) + ifs->freq_ctr2 = nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ2]); + + } + if (tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) + ifs->chan_type = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE]); + } + + return NL_SKIP; +} + +/** + * survey_handler - channel survey data + * This handler will be called multiple times, for each channel. + * stolen from iw:survey.c + */ +static int survey_handler(struct nl_msg *msg, void *arg) +{ + struct iw_nl80211_survey *sd = (struct iw_nl80211_survey *)arg; + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1]; + + static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = { + [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 }, + [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 }, + [NL80211_SURVEY_INFO_IN_USE] = { .type = NLA_FLAG }, + [NL80211_SURVEY_INFO_TIME] = { .type = NLA_U64 }, + [NL80211_SURVEY_INFO_TIME_BUSY] = { .type = NLA_U64 }, + [NL80211_SURVEY_INFO_TIME_EXT_BUSY] = { .type = NLA_U64 }, + [NL80211_SURVEY_INFO_TIME_RX] = { .type = NLA_U64 }, + [NL80211_SURVEY_INFO_TIME_TX] = { .type = NLA_U64 }, + [NL80211_SURVEY_INFO_TIME_SCAN] = { .type = NLA_U64 }, + }; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_SURVEY_INFO]) + return NL_SKIP; + + if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX, + tb[NL80211_ATTR_SURVEY_INFO], survey_policy)) + return NL_SKIP; + + /* The frequency is needed to match up with the associated station */ + if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY]) + return NL_SKIP; + + /* We are only interested in the data of the operating channel */ + if (!sinfo[NL80211_SURVEY_INFO_IN_USE]) + return NL_SKIP; + + sd->freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]); + + if (sinfo[NL80211_SURVEY_INFO_NOISE]) + sd->noise = (int8_t)nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]); + + if (sinfo[NL80211_SURVEY_INFO_TIME]) + sd->time.active = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME]); + + if (sinfo[NL80211_SURVEY_INFO_TIME_BUSY]) + sd->time.busy = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_BUSY]); + + if (sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY]) + sd->time.ext_busy = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY]); + + if (sinfo[NL80211_SURVEY_INFO_TIME_RX]) + sd->time.rx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_RX]); + + if (sinfo[NL80211_SURVEY_INFO_TIME_TX]) + sd->time.tx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_TX]); + + if (sinfo[NL80211_SURVEY_INFO_TIME_SCAN]) + sd->time.scan = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_SCAN]); + + return NL_SKIP; +} + +/* Regulatory domain, stolen from iw:reg.c */ +static int reg_handler(struct nl_msg *msg, void *arg) +{ + struct iw_nl80211_reg *ir = (struct iw_nl80211_reg *)arg; + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + char *alpha2; + + ir->region = -1; + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb_msg[NL80211_ATTR_REG_ALPHA2]) + return NL_SKIP; + + if (!tb_msg[NL80211_ATTR_REG_RULES]) + return NL_SKIP; + + if (tb_msg[NL80211_ATTR_DFS_REGION]) + ir->region = nla_get_u8(tb_msg[NL80211_ATTR_DFS_REGION]); + else + ir->region = NL80211_DFS_UNSET; + + alpha2 = nla_data(tb_msg[NL80211_ATTR_REG_ALPHA2]); + ir->country[0] = alpha2[0]; + ir->country[1] = alpha2[1]; + + return NL_SKIP; +} + +static int link_handler(struct nl_msg *msg, void *arg) +{ + struct iw_nl80211_linkstat *ls = arg; + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *bss[NL80211_BSS_MAX + 1]; + static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { + [NL80211_BSS_TSF] = { .type = NLA_U64 }, + [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, + [NL80211_BSS_BSSID] = { }, + [NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 }, + [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, + [NL80211_BSS_INFORMATION_ELEMENTS] = { }, + [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, + [NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 }, + [NL80211_BSS_STATUS] = { .type = NLA_U32 }, + }; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_BSS]) + return NL_SKIP; + + if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy)) + return NL_SKIP; + + if (!bss[NL80211_BSS_BSSID]) + return NL_SKIP; + + if (!bss[NL80211_BSS_STATUS]) + return NL_SKIP; + + if (bss[NL80211_BSS_SIGNAL_UNSPEC]) + ls->bss_signal_qual = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); + + if (bss[NL80211_BSS_SIGNAL_MBM]) { + int s = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]); + ls->bss_signal = s / 100; + } + + ls->status = nla_get_u32(bss[NL80211_BSS_STATUS]); + switch (ls->status) { + case NL80211_BSS_STATUS_ASSOCIATED: /* apparently no longer used */ + case NL80211_BSS_STATUS_AUTHENTICATED: + case NL80211_BSS_STATUS_IBSS_JOINED: + memcpy(&ls->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN); + } + + return NL_SKIP; +} + +static int link_sta_handler(struct nl_msg *msg, void *arg) +{ + struct iw_nl80211_linkstat *ls = arg; + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; + struct nlattr *binfo[NL80211_STA_BSS_PARAM_MAX + 1]; + struct nl80211_sta_flag_update *sta_flags; + static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = { + [NL80211_STA_INFO_CONNECTED_TIME] = { .type = NLA_U32 }, + [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_SIGNAL_AVG] = { .type = NLA_U8 }, + [NL80211_STA_INFO_T_OFFSET] = { .type = NLA_U64 }, + [NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED }, + [NL80211_STA_INFO_RX_BITRATE] = { .type = NLA_NESTED }, + [NL80211_STA_INFO_RX_DROP_MISC] = { .type = NLA_U64 }, + [NL80211_STA_INFO_BEACON_RX] = { .type = NLA_U64 }, + [NL80211_STA_INFO_BEACON_LOSS] = { .type = NLA_U32 }, + [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = { .type = NLA_U8 }, + [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_TX_RETRIES] = { .type = NLA_U32 }, + [NL80211_STA_INFO_TX_FAILED] = { .type = NLA_U32 }, + [NL80211_STA_INFO_STA_FLAGS] = + { .minlen = sizeof(struct nl80211_sta_flag_update) }, + [NL80211_STA_INFO_LOCAL_PM] = { .type = NLA_U32}, + [NL80211_STA_INFO_PEER_PM] = { .type = NLA_U32}, + [NL80211_STA_INFO_NONPEER_PM] = { .type = NLA_U32}, + [NL80211_STA_INFO_CHAIN_SIGNAL] = { .type = NLA_NESTED }, + [NL80211_STA_INFO_CHAIN_SIGNAL_AVG] = { .type = NLA_NESTED }, + }; + static struct nla_policy bss_policy[NL80211_STA_BSS_PARAM_MAX + 1] = { + [NL80211_STA_BSS_PARAM_CTS_PROT] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_SHORT_PREAMBLE] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_DTIM_PERIOD] = { .type = NLA_U8 }, + [NL80211_STA_BSS_PARAM_BEACON_INTERVAL] = { .type = NLA_U16 }, + }; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_STA_INFO]) + return NL_SKIP; + + if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, + tb[NL80211_ATTR_STA_INFO], + stats_policy)) + return NL_SKIP; + + if (sinfo[NL80211_STA_INFO_TX_RETRIES]) + ls->tx_retries = nla_get_u32(sinfo[NL80211_STA_INFO_TX_RETRIES]); + if (sinfo[NL80211_STA_INFO_TX_FAILED]) + ls->tx_failed = nla_get_u32(sinfo[NL80211_STA_INFO_TX_FAILED]); + + + if (sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) { + ls->expected_thru = nla_get_u32(sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]); + /* convert in Mbps but scale by 1000 to save kbps units */ + ls->expected_thru = ls->expected_thru * 1000 / 1024; + } + if (sinfo[NL80211_STA_INFO_INACTIVE_TIME]) + ls->inactive_time = nla_get_u32(sinfo[NL80211_STA_INFO_INACTIVE_TIME]); + if (sinfo[NL80211_STA_INFO_CONNECTED_TIME]) + ls->connected_time = nla_get_u32(sinfo[NL80211_STA_INFO_CONNECTED_TIME]); + + if (sinfo[NL80211_STA_INFO_RX_BYTES]) + ls->rx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_RX_BYTES]); + if (sinfo[NL80211_STA_INFO_RX_PACKETS]) + ls->rx_packets = nla_get_u32(sinfo[NL80211_STA_INFO_RX_PACKETS]); + if (sinfo[NL80211_STA_INFO_RX_DROP_MISC]) + ls->rx_drop_misc = nla_get_u64(sinfo[NL80211_STA_INFO_RX_DROP_MISC]); + + if (sinfo[NL80211_STA_INFO_TX_BYTES]) + ls->tx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_TX_BYTES]); + if (sinfo[NL80211_STA_INFO_TX_PACKETS]) + ls->tx_packets = nla_get_u32(sinfo[NL80211_STA_INFO_TX_PACKETS]); + + if (sinfo[NL80211_STA_INFO_SIGNAL]) + ls->signal = (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]); + if (sinfo[NL80211_STA_INFO_SIGNAL_AVG]) + ls->signal_avg = (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL_AVG]); + + + if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]) + ls->beacon_avg_sig = nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]); + if (sinfo[NL80211_STA_INFO_BEACON_RX]) + ls->beacons = nla_get_u64(sinfo[NL80211_STA_INFO_BEACON_RX]); + if (sinfo[NL80211_STA_INFO_BEACON_LOSS]) + ls->beacon_loss = nla_get_u32(sinfo[NL80211_STA_INFO_BEACON_LOSS]); + + if (sinfo[NL80211_STA_INFO_TX_BITRATE]) + parse_bitrate(sinfo[NL80211_STA_INFO_TX_BITRATE], ls->tx_bitrate, sizeof(ls->tx_bitrate)); + + if (sinfo[NL80211_STA_INFO_RX_BITRATE]) + parse_bitrate(sinfo[NL80211_STA_INFO_RX_BITRATE], ls->rx_bitrate, sizeof(ls->rx_bitrate)); + + if (sinfo[NL80211_STA_INFO_STA_FLAGS]) { + sta_flags = (struct nl80211_sta_flag_update *) + nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]); + + if (sta_flags->mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) && + sta_flags->set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) + ls->long_preamble = true; + + if (sta_flags->mask & BIT(NL80211_STA_FLAG_WME) && + sta_flags->set & BIT(NL80211_STA_FLAG_WME)) + ls->wme = true; + + if (sta_flags->mask & BIT(NL80211_STA_FLAG_MFP) && + sta_flags->set & BIT(NL80211_STA_FLAG_MFP)) + ls->mfp = true; + + if (sta_flags->mask & BIT(NL80211_STA_FLAG_TDLS_PEER) && + sta_flags->set & BIT(NL80211_STA_FLAG_TDLS_PEER)) + ls->tdls = true; + } + + /* BSS Flags */ + if (sinfo[NL80211_STA_INFO_BSS_PARAM]) { + if (nla_parse_nested(binfo, NL80211_STA_BSS_PARAM_MAX, + sinfo[NL80211_STA_INFO_BSS_PARAM], + bss_policy) == 0) { + if (binfo[NL80211_STA_BSS_PARAM_CTS_PROT]) { + ls->cts_protection = true; + } + if (binfo[NL80211_STA_BSS_PARAM_SHORT_PREAMBLE]) + ls->long_preamble = false; + if (binfo[NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME]) + ls->short_slot_time = true; + + ls->beacon_int = nla_get_u16(binfo[NL80211_STA_BSS_PARAM_BEACON_INTERVAL]); + ls->dtim_period = nla_get_u8(binfo[NL80211_STA_BSS_PARAM_DTIM_PERIOD]); + } + } + + return NL_SKIP; +} + +/* + * COMMAND HANDLERS + */ +void iw_nl80211_get_linkstat(struct iw_nl80211_linkstat *ls) +{ + static struct cmd cmd_linkstat = { + .cmd = NL80211_CMD_GET_SCAN, + .flags = NLM_F_DUMP, + .handler = link_handler + }; + static struct cmd cmd_getstation = { + .cmd = NL80211_CMD_GET_STATION, + .flags = 0, + .handler = link_sta_handler + }; + + struct msg_attribute station_addr = { + .type = NL80211_ATTR_MAC, + .len = sizeof(ls->bssid), + .data = &ls->bssid + }; + + cmd_linkstat.handler_arg = ls; + memset(ls, 0, sizeof(*ls)); + handle_cmd(&cmd_linkstat); + + /* If not associated to another station, the bssid is zeroed out */ + if (ether_addr_is_zero(&ls->bssid)) + return; + /* + * Details of the associated station + */ + cmd_getstation.handler_arg = ls; + cmd_getstation.msg_args = &station_addr; + cmd_getstation.msg_args_len = 1; + + handle_cmd(&cmd_getstation); + + /* Channel survey data */ + iw_nl80211_get_survey(&ls->survey); +} + +void iw_nl80211_getreg(struct iw_nl80211_reg *ir) +{ + static struct cmd cmd_reg = { + .cmd = NL80211_CMD_GET_REG, + .flags = 0, + .handler = reg_handler + }; + + cmd_reg.handler_arg = ir; + memset(ir, 0, sizeof(*ir)); + handle_cmd(&cmd_reg); +} + +void iw_nl80211_getifstat(struct iw_nl80211_ifstat *ifs) +{ + static struct cmd cmd_ifstat = { + .cmd = NL80211_CMD_GET_INTERFACE, + .flags = 0, + .handler = iface_handler + }; + + cmd_ifstat.handler_arg = ifs; + memset(ifs, 0, sizeof(*ifs)); + handle_cmd(&cmd_ifstat); +} + +void iw_nl80211_get_survey(struct iw_nl80211_survey *sd) +{ + static struct cmd cmd_survey = { + .cmd = NL80211_CMD_GET_SURVEY, + .flags = NLM_F_DUMP, + .handler = survey_handler + }; + + cmd_survey.handler_arg = sd; + memset(sd, 0, sizeof(*sd)); + handle_cmd(&cmd_survey); +} + +/* + * Multicast Handling + */ +/** + * struct handler_args - arguments to resolve multicast group + * @group: group name to resolve + * @id: ID it resolves into + */ +struct handler_args { + const char *group; + int id; +}; + +/* stolen from iw:genl.c */ +static int family_handler(struct nl_msg *msg, void *arg) +{ + struct handler_args *grp = arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *mcgrp; + int rem_mcgrp; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return NL_SKIP; + + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, + nla_data(mcgrp), nla_len(mcgrp), NULL); + + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), + grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + break; + } + + return NL_SKIP; +} + +/* stolen from iw:genl.c */ +int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int ret, ctrlid; + struct handler_args grp = { + .group = group, + .id = -ENOENT, + }; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + ret = -ENOMEM; + goto out_fail_cb; + } + + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, + 0, CTRL_CMD_GETFAMILY, 0); + + ret = -ENOBUFS; + NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); + + ret = nl_send_auto_complete(sock, msg); + if (ret < 0) + goto out; + + ret = 1; + + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp); + + while (ret > 0) + nl_recvmsgs(sock, cb); + + if (ret == 0) + ret = grp.id; +nla_put_failure: +out: + nl_cb_put(cb); +out_fail_cb: + nlmsg_free(msg); + return ret; +} + +/** + * Allocate a GeNetlink socket ready to listen for nl80211 multicast group @grp + * @grp: identifier of an nl80211 multicast group (e.g. "scan") + */ +struct nl_sock *alloc_nl_mcast_sk(const char *grp) +{ + int mcid, ret; + struct nl_sock *sk = nl_socket_alloc(); + + if (!sk) + err_sys("failed to allocate netlink multicast socket"); + + if (genl_connect(sk)) + err_sys("failed to connect multicast socket to GeNetlink"); + + mcid = nl_get_multicast_id(sk, "nl80211", grp); + if (mcid < 0) + err_quit("failed to resolve nl80211 '%s' multicast group", grp); + + ret = nl_socket_add_membership(sk, mcid); + if (ret) + err_sys("failed to join nl80211 multicast group %s", grp); + + return sk; +} |