mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
wifi: cfg80211: Fragment per-link station stats in nl80211_dump_station()
In MLO scenarios, stations may have multiple links, each with distinct statistics. When userspace tools like iw or hostapd request station dumps, attempting to pack all per-link stats into a single netlink message can easily exceed the default 4KB buffer limit, especially when more than two links are active. This results in -EMSGSIZE errors and incomplete data delivery. To address this, fragment per-link station statistics across multiple netlink messages to ensure reliable delivery of complete MLO station information. Extend the stateful context with a two-phase dump mechanism: phase 0 (AGGREGATED) sends combined MLO-level statistics and phase 1 (PER_LINK) sends individual per-link statistics for each active link. The dump loop is structured to produce exactly one netlink message per iteration, with a common header (ifindex, wdev, mac, generation) built once and phase-specific payload added via a switch statement. This keeps header construction in one place and makes the EMSGSIZE bail-out uniform. Add a new request flag attribute, NL80211_ATTR_STA_DUMP_LINK_STATS (NLA_FLAG), for NL80211_CMD_GET_STATION dump. Userspace can set this flag to request per-link station statistics for MLO stations. Extract this flag during the first dump invocation by passing an attrbuf to nl80211_prepare_wdev_dump(); use __free(kfree) to avoid scattered manual kfree() calls. Cache the boolean in the dump context to avoid repeated parsing on subsequent invocations. Per-link messages carry a single NL80211_ATTR_MLO_LINKS nest with the link ID, link-specific MAC, and per-link NL80211_ATTR_STA_INFO payload. The link-specific validity (is_valid_ether_addr) and null pointer guard are checked in nl80211_put_link_station_payload() before any message construction begins. Also fix all nla_nest_start_noflag() calls in nl80211_fill_link_station() for nested attribute types (STA_INFO, BSS_PARAM, TID_STATS, per-tid) to use nla_nest_start() so the NLA_F_NESTED flag is set correctly. Propagate the actual return value from nl80211_put_sta_info_common() in the AGGREGATED phase rather than returning skb->len. Returning skb->len signals netlink to re-invoke the dump with the same sta_idx, causing an infinite loop when the aggregated payload is too large to fit; returning the real error code (-EMSGSIZE or otherwise) terminates the dump cleanly. Backward compatibility is seamlessly preserved for non-MLO stations. Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com> Link: https://patch.msgid.link/20260614051739.3979947-5-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
727afb05ef
commit
dd21d1844a
|
|
@ -3168,6 +3168,23 @@ enum nl80211_commands {
|
|||
* @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32)
|
||||
* @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32)
|
||||
*
|
||||
* @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION
|
||||
* (dump mode only). When set on an MLD station, the dump produces two
|
||||
* %NL80211_CMD_NEW_STATION messages per station per dump call:
|
||||
*
|
||||
* 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO
|
||||
* contains MLO-combined statistics (same content as a dump without
|
||||
* this flag).
|
||||
*
|
||||
* 2. For each active link, a per-link message containing
|
||||
* %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds
|
||||
* %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC,
|
||||
* and %NL80211_ATTR_STA_INFO with per-link statistics (see
|
||||
* &enum nl80211_sta_info).
|
||||
*
|
||||
* The aggregated message always precedes the per-link messages for the
|
||||
* same station within a dump sequence.
|
||||
*
|
||||
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
|
||||
* @NL80211_ATTR_MAX: highest attribute number currently defined
|
||||
* @__NL80211_ATTR_AFTER_LAST: internal use
|
||||
|
|
@ -3766,6 +3783,8 @@ enum nl80211_attrs {
|
|||
NL80211_ATTR_NPCA_PRIMARY_FREQ,
|
||||
NL80211_ATTR_NPCA_PUNCT_BITMAP,
|
||||
|
||||
NL80211_ATTR_STA_DUMP_LINK_STATS,
|
||||
|
||||
/* add attributes here, update the policy in nl80211.c */
|
||||
|
||||
__NL80211_ATTR_AFTER_LAST,
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
|
|||
[NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 },
|
||||
[NL80211_ATTR_NPCA_PUNCT_BITMAP] =
|
||||
NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
|
||||
[NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG },
|
||||
};
|
||||
|
||||
/* policy for the key attributes */
|
||||
|
|
@ -7870,7 +7871,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
|
|||
goto nla_put_failure; \
|
||||
} while (0)
|
||||
|
||||
link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO);
|
||||
link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO);
|
||||
if (!link_sinfoattr)
|
||||
goto nla_put_failure;
|
||||
|
||||
|
|
@ -7936,8 +7937,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
|
|||
PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32);
|
||||
|
||||
if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) {
|
||||
bss_param = nla_nest_start_noflag(msg,
|
||||
NL80211_STA_INFO_BSS_PARAM);
|
||||
bss_param = nla_nest_start(msg,
|
||||
NL80211_STA_INFO_BSS_PARAM);
|
||||
if (!bss_param)
|
||||
goto nla_put_failure;
|
||||
|
||||
|
|
@ -7979,8 +7980,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
|
|||
struct nlattr *tidsattr;
|
||||
int tid;
|
||||
|
||||
tidsattr = nla_nest_start_noflag(msg,
|
||||
NL80211_STA_INFO_TID_STATS);
|
||||
tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS);
|
||||
if (!tidsattr)
|
||||
goto nla_put_failure;
|
||||
|
||||
|
|
@ -7993,7 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
|
|||
if (!tidstats->filled)
|
||||
continue;
|
||||
|
||||
tidattr = nla_nest_start_noflag(msg, tid + 1);
|
||||
tidattr = nla_nest_start(msg, tid + 1);
|
||||
if (!tidattr)
|
||||
goto nla_put_failure;
|
||||
|
||||
|
|
@ -8464,21 +8464,74 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo)
|
|||
sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
|
||||
}
|
||||
|
||||
enum nl80211_dump_station_phase {
|
||||
NL80211_DUMP_STA_PHASE_AGGREGATED = 0,
|
||||
NL80211_DUMP_STA_PHASE_PER_LINK = 1,
|
||||
};
|
||||
|
||||
struct nl80211_dump_station_ctx {
|
||||
int sta_idx;
|
||||
int link_idx;
|
||||
enum nl80211_dump_station_phase phase;
|
||||
bool dump_link_stats;
|
||||
u8 mac_addr[ETH_ALEN];
|
||||
struct station_info sinfo;
|
||||
};
|
||||
|
||||
static int nl80211_put_link_station_payload(struct sk_buff *msg,
|
||||
struct cfg80211_registered_device *rdev,
|
||||
struct station_info *sinfo,
|
||||
int link_idx)
|
||||
{
|
||||
struct link_station_info *link_sinfo = sinfo->links[link_idx];
|
||||
struct nlattr *links, *link;
|
||||
|
||||
if (WARN_ON_ONCE(!link_sinfo))
|
||||
return -ENOENT;
|
||||
|
||||
if (!is_valid_ether_addr(link_sinfo->addr))
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS);
|
||||
if (!links)
|
||||
return -EMSGSIZE;
|
||||
|
||||
link = nla_nest_start(msg, link_idx + 1);
|
||||
if (!link)
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) ||
|
||||
nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr))
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nl80211_fill_link_station(msg, rdev, link_sinfo))
|
||||
goto nla_put_failure;
|
||||
|
||||
nla_nest_end(msg, link);
|
||||
nla_nest_end(msg, links);
|
||||
return 0;
|
||||
|
||||
nla_put_failure:
|
||||
nla_nest_cancel(msg, links);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
static int nl80211_dump_station(struct sk_buff *skb,
|
||||
struct netlink_callback *cb)
|
||||
{
|
||||
struct cfg80211_registered_device *rdev;
|
||||
struct wireless_dev *wdev;
|
||||
struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
|
||||
struct nlattr **attrbuf __free(kfree) = NULL;
|
||||
int err;
|
||||
|
||||
err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL);
|
||||
if (!ctx) {
|
||||
attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR);
|
||||
if (!attrbuf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf);
|
||||
if (err)
|
||||
return err;
|
||||
/* nl80211_prepare_wdev_dump acquired it in the successful case */
|
||||
|
|
@ -8490,6 +8543,9 @@ static int nl80211_dump_station(struct sk_buff *skb,
|
|||
err = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED;
|
||||
ctx->dump_link_stats =
|
||||
!!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS];
|
||||
cb->args[2] = (long)ctx;
|
||||
}
|
||||
|
||||
|
|
@ -8505,34 +8561,53 @@ static int nl80211_dump_station(struct sk_buff *skb,
|
|||
|
||||
while (true) {
|
||||
void *hdr;
|
||||
int ret;
|
||||
|
||||
memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
|
||||
for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
|
||||
ctx->sinfo.links[i] =
|
||||
kzalloc_obj(*ctx->sinfo.links[0]);
|
||||
if (!ctx->sinfo.links[i]) {
|
||||
err = -ENOMEM;
|
||||
/* AGGREGATED phase: fetch sinfo from driver once per station */
|
||||
if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) {
|
||||
memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
|
||||
for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
|
||||
ctx->sinfo.links[i] =
|
||||
kzalloc_obj(*ctx->sinfo.links[0]);
|
||||
if (!ctx->sinfo.links[i]) {
|
||||
err = -ENOMEM;
|
||||
goto out_err_release;
|
||||
}
|
||||
}
|
||||
|
||||
err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
|
||||
ctx->mac_addr, &ctx->sinfo);
|
||||
if (err == -ENOENT) {
|
||||
err = skb->len;
|
||||
goto out_err_release;
|
||||
}
|
||||
if (err)
|
||||
goto out_err_release;
|
||||
|
||||
if (ctx->sinfo.valid_links)
|
||||
cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
|
||||
} else {
|
||||
/* PER_LINK phase: advance to next valid link */
|
||||
while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS &&
|
||||
!(ctx->sinfo.valid_links & BIT(ctx->link_idx)))
|
||||
ctx->link_idx++;
|
||||
|
||||
if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) {
|
||||
cfg80211_sinfo_release_content(&ctx->sinfo);
|
||||
ctx->sta_idx++;
|
||||
ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
|
||||
ctx->mac_addr, &ctx->sinfo);
|
||||
if (err == -ENOENT) {
|
||||
err = skb->len;
|
||||
goto out_err_release;
|
||||
}
|
||||
if (err)
|
||||
goto out_err_release;
|
||||
|
||||
if (ctx->sinfo.valid_links)
|
||||
cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
|
||||
|
||||
/* Build common header for both phases */
|
||||
hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid,
|
||||
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
||||
NL80211_CMD_NEW_STATION);
|
||||
if (!hdr) {
|
||||
err = skb->len;
|
||||
if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK)
|
||||
goto out_err;
|
||||
goto out_err_release;
|
||||
}
|
||||
|
||||
|
|
@ -8546,18 +8621,49 @@ static int nl80211_dump_station(struct sk_buff *skb,
|
|||
ctx->sinfo.generation)) {
|
||||
genlmsg_cancel(skb, hdr);
|
||||
err = skb->len;
|
||||
if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK)
|
||||
goto out_err;
|
||||
goto out_err_release;
|
||||
}
|
||||
|
||||
if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) {
|
||||
genlmsg_cancel(skb, hdr);
|
||||
err = skb->len;
|
||||
goto out_err_release;
|
||||
}
|
||||
switch (ctx->phase) {
|
||||
case NL80211_DUMP_STA_PHASE_AGGREGATED:
|
||||
ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo);
|
||||
if (ret) {
|
||||
genlmsg_cancel(skb, hdr);
|
||||
err = ret;
|
||||
goto out_err_release;
|
||||
}
|
||||
genlmsg_end(skb, hdr);
|
||||
|
||||
genlmsg_end(skb, hdr);
|
||||
cfg80211_sinfo_release_content(&ctx->sinfo);
|
||||
ctx->sta_idx++;
|
||||
if (ctx->dump_link_stats && ctx->sinfo.valid_links) {
|
||||
ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK;
|
||||
ctx->link_idx = 0;
|
||||
} else {
|
||||
cfg80211_sinfo_release_content(&ctx->sinfo);
|
||||
ctx->sta_idx++;
|
||||
}
|
||||
break;
|
||||
|
||||
case NL80211_DUMP_STA_PHASE_PER_LINK:
|
||||
ret = nl80211_put_link_station_payload(skb, rdev,
|
||||
&ctx->sinfo,
|
||||
ctx->link_idx);
|
||||
if (ret == -EMSGSIZE) {
|
||||
genlmsg_cancel(skb, hdr);
|
||||
err = skb->len;
|
||||
goto out_err;
|
||||
}
|
||||
if (ret) {
|
||||
/* skip invalid link, do not abort the dump */
|
||||
genlmsg_cancel(skb, hdr);
|
||||
ctx->link_idx++;
|
||||
continue;
|
||||
}
|
||||
genlmsg_end(skb, hdr);
|
||||
ctx->link_idx++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out_err_release:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user