Currently, the iw station dump displays the overall statistics of associated MLO (multi-link operation) station. While it provides a overall summary view, it does not reflect the individual link-level statistics. Extend iw to parse and display nested per-link attributes for MLO station, incorporating recent kernel support for reporting per-link statistics. This allows users to view detailed statistics for each link, along with overall statistics which improving visibility and debugging capabilities for MLO stations. Sample output: For MLO client: iw dev wlan0 station dump -v Station xx:xx:xx:xx:xx:xx (on wlan0) authorized: yes authenticated: yes associated: yes preamble: long WMM/WME: yes MFP: yes TDLS peer: no inactive time: 328 ms rx bytes: 11319 rx packets: 98 tx bytes: 1496 tx packets: 9 tx retries: 3 tx failed: 0 rx drop misc: 0 signal: -33 dBm signal avg: -28 dBm tx bitrate: 6.0 MBit/s tx duration: 3989 us rx bitrate: 576.4 MBit/s 80MHz EHT-MCS 5 EHT-NSS 2 EHT-GI 0 rx duration: 0 us last ack signal:-35 dBm avg ack signal: -33 dBm MSDU: TID rx tx tx retries tx failed 0 88 3 0 0 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 0 0 0 0 5 0 0 0 0 6 2 0 0 0 7 0 2 0 0 8 0 0 0 0 9 0 0 0 0 10 0 0 0 0 11 0 0 0 0 12 0 0 0 0 13 0 0 0 0 14 0 0 0 0 15 0 0 0 0 16 0 4 0 0 TXQs: TID qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytes tx-packets 0 0 0 3 0 0 0 0 186 3 1 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 7 0 0 2 0 0 0 0 580 2 8 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 11 0 0 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0 0 0 13 0 0 0 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0 0 15 0 0 0 0 0 0 0 0 0 DTIM period: 2 beacon interval:100 connected time: 188 seconds associated at [boottime]: 659.536s associated at: 659536 ms current time: 847264 ms Link 0: address: xx:xx:xx:xx:xx:xx inactive time: 5324 ms rx bytes: 7838 rx packets: 69 tx bytes: 0 tx packets: 0 tx retries: 0 tx failed: 0 rx drop misc: 0 signal: -33 dBm signal avg: -28 dBm tx bitrate: 6.0 MBit/s tx duration: 1264 us rx bitrate: 576.4 MBit/s 80MHz EHT-MCS 5 EHT-NSS 2 EHT-GI 0 rx duration: 0 us MSDU: TID rx tx tx retries tx failed 0 65 0 0 0 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 0 0 0 0 5 0 0 0 0 6 0 0 0 0 7 0 0 0 0 8 0 0 0 0 9 0 0 0 0 10 0 0 0 0 11 0 0 0 0 12 0 0 0 0 13 0 0 0 0 14 0 0 0 0 15 0 0 0 0 16 0 0 0 0 DTIM period: 2 beacon interval:100 Link 1: address: xx:xx:xx:xx:xx:xx inactive time: 328 ms rx bytes: 3481 rx packets: 29 tx bytes: 1496 tx packets: 9 tx retries: 3 tx failed: 0 rx drop misc: 0 signal: -33 dBm signal avg: -32 dBm tx bitrate: 6.0 MBit/s tx duration: 2725 us rx bitrate: 864.8 MBit/s 80MHz EHT-MCS 8 EHT-NSS 2 EHT-GI 0 rx duration: 0 us last ack signal:-35 dBm avg ack signal: -33 dBm MSDU: TID rx tx tx retries tx failed 0 23 3 0 0 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 0 0 0 0 5 0 0 0 0 6 2 0 0 0 7 0 2 0 0 8 0 0 0 0 9 0 0 0 0 10 0 0 0 0 11 0 0 0 0 12 0 0 0 0 13 0 0 0 0 14 0 0 0 0 15 0 0 0 0 16 0 4 0 0 DTIM period: 2 beacon interval:100 For non-ML client: iw dev wlan0 station dump Station xx:xx:xx:xx:xx:xx (on wlan0) authorized: yes authenticated: yes associated: yes preamble: long WMM/WME: yes MFP: yes TDLS peer: no inactive time: 540 ms rx bytes: 2944 rx packets: 27 tx bytes: 2156 tx packets: 18 tx retries: 34 tx failed: 0 rx drop misc: 0 signal: -95 dBm tx bitrate: 344.1 MBit/s HE-MCS 7 HE-NSS 4 HE-GI 0 HE-DCM tx duration: 3695 us rx bitrate: 1441.3 MBit/s 80MHz HE-MCS 7 HE-NSS 4 HE-GI 0 HE-DCM 0 rx duration: 0 us DTIM period: 2 beacon interval:100 connected time: 128 seconds associated at [boottime]: 10850.471s associated at: 10850467 ms current time: 10978261 ms Signed-off-by: Sarika Sharma --- station.c | 316 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 181 insertions(+), 135 deletions(-) diff --git a/station.c b/station.c index bf7c0f5..a07ead6 100644 --- a/station.c +++ b/station.c @@ -101,7 +101,7 @@ int parse_txq_stats(char *buf, int buflen, struct nlattr *tid_stats_attr, int he } -static void parse_tid_stats(struct nlattr *tid_stats_attr) +static void parse_tid_stats(struct nlattr *tid_stats_attr, const char *indent) { struct nlattr *stats_info[NL80211_TID_STATS_MAX + 1], *tidattr, *info; static struct nla_policy stats_policy[NL80211_TID_STATS_MAX + 1] = { @@ -115,14 +115,14 @@ static void parse_tid_stats(struct nlattr *tid_stats_attr) char txqbuf[2000] = {}, *pos = txqbuf; int buflen = sizeof(txqbuf), foundtxq = 0; - printf("\n\tMSDU:\n\t\tTID\trx\ttx\ttx retries\ttx failed"); + printf("%sMSDU:%sTID\trx\ttx\ttx retries\ttx failed", indent, indent); nla_for_each_nested(tidattr, tid_stats_attr, rem) { if (nla_parse_nested(stats_info, NL80211_TID_STATS_MAX, tidattr, stats_policy)) { printf("failed to parse nested stats attributes!"); return; } - printf("\n\t\t%d", i); + printf("%s%d", indent, i); info = stats_info[NL80211_TID_STATS_RX_MSDU]; if (info) printf("\t%llu", (unsigned long long)nla_get_u64(info)); @@ -145,10 +145,10 @@ static void parse_tid_stats(struct nlattr *tid_stats_attr) } if (foundtxq) - printf("\n\tTXQs:%s", txqbuf); + printf("%sTXQs:%s", indent, txqbuf); } -static void parse_bss_param(struct nlattr *bss_param_attr) +static void parse_bss_param(struct nlattr *bss_param_attr, const char *indent) { struct nlattr *bss_param_info[NL80211_STA_BSS_PARAM_MAX + 1], *info; static struct nla_policy bss_poilcy[NL80211_STA_BSS_PARAM_MAX + 1] = { @@ -166,13 +166,13 @@ static void parse_bss_param(struct nlattr *bss_param_attr) info = bss_param_info[NL80211_STA_BSS_PARAM_DTIM_PERIOD]; if (info) - printf("\n\tDTIM period:\t%u", nla_get_u8(info)); + printf("%sDTIM period:\t%u", indent, nla_get_u8(info)); info = bss_param_info[NL80211_STA_BSS_PARAM_BEACON_INTERVAL]; if (info) - printf("\n\tbeacon interval:%u", nla_get_u16(info)); + printf("%sbeacon interval:%u", indent, nla_get_u16(info)); info = bss_param_info[NL80211_STA_BSS_PARAM_CTS_PROT]; if (info) { - printf("\n\tCTS protection:"); + printf("%sCTS protection:", indent); if (nla_get_u16(info)) printf("\tyes"); else @@ -180,7 +180,7 @@ static void parse_bss_param(struct nlattr *bss_param_attr) } info = bss_param_info[NL80211_STA_BSS_PARAM_SHORT_PREAMBLE]; if (info) { - printf("\n\tshort preamble:"); + printf("%sshort preamble:", indent); if (nla_get_u16(info)) printf("\tyes"); else @@ -188,7 +188,7 @@ static void parse_bss_param(struct nlattr *bss_param_attr) } info = bss_param_info[NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME]; if (info) { - printf("\n\tshort slot time:"); + printf("%sshort slot time:", indent); if (nla_get_u16(info)) printf("yes"); else @@ -312,11 +312,152 @@ static char *get_chain_signal(struct nlattr *attr_list) return buf; } +static void print_nested_sta_handler(struct nlattr *link_sinfo[NL80211_STA_INFO_MAX + 1], + void *arg, const char *indent) +{ + char *chain; + struct timeval now; + unsigned long long now_ms; + + gettimeofday(&now, NULL); + now_ms = now.tv_sec * 1000ULL; + now_ms += (now.tv_usec / 1000); + + if (link_sinfo[NL80211_STA_INFO_INACTIVE_TIME]) + printf("%sinactive time:\t%u ms", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_INACTIVE_TIME])); + if (link_sinfo[NL80211_STA_INFO_RX_BYTES64]) + printf("%srx bytes:\t%llu", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_RX_BYTES64])); + else if (link_sinfo[NL80211_STA_INFO_RX_BYTES]) + printf("%srx bytes:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_RX_BYTES])); + if (link_sinfo[NL80211_STA_INFO_RX_PACKETS]) + printf("%srx packets:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_RX_PACKETS])); + if (link_sinfo[NL80211_STA_INFO_TX_BYTES64]) + printf("%stx bytes:\t%llu", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_TX_BYTES64])); + else if (link_sinfo[NL80211_STA_INFO_TX_BYTES]) + printf("%stx bytes:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_TX_BYTES])); + if (link_sinfo[NL80211_STA_INFO_TX_PACKETS]) + printf("%stx packets:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_TX_PACKETS])); + if (link_sinfo[NL80211_STA_INFO_TX_RETRIES]) + printf("%stx retries:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_TX_RETRIES])); + if (link_sinfo[NL80211_STA_INFO_TX_FAILED]) + printf("%stx failed:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_TX_FAILED])); + if (link_sinfo[NL80211_STA_INFO_BEACON_LOSS]) + printf("%sbeacon loss:\t%u", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_BEACON_LOSS])); + if (link_sinfo[NL80211_STA_INFO_BEACON_RX]) + printf("\n\t\tbeacon rx:\t%llu", + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_BEACON_RX])); + if (link_sinfo[NL80211_STA_INFO_RX_DROP_MISC]) + printf("%srx drop misc:\t%llu", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_RX_DROP_MISC])); + + chain = get_chain_signal(link_sinfo[NL80211_STA_INFO_CHAIN_SIGNAL]); + if (link_sinfo[NL80211_STA_INFO_SIGNAL]) + printf("%ssignal: \t%d %sdBm", indent, + (int8_t)nla_get_u8(link_sinfo[NL80211_STA_INFO_SIGNAL]), + chain); + + chain = get_chain_signal(link_sinfo[NL80211_STA_INFO_CHAIN_SIGNAL_AVG]); + if (link_sinfo[NL80211_STA_INFO_SIGNAL_AVG]) + printf("%ssignal avg:\t%d %sdBm", indent, + (int8_t)nla_get_u8(link_sinfo[NL80211_STA_INFO_SIGNAL_AVG]), + chain); + + if (link_sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]) + printf("%sbeacon signal avg:\t%d dBm", indent, + (int8_t)nla_get_u8(link_sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG])); + if (link_sinfo[NL80211_STA_INFO_T_OFFSET]) + printf("%sToffset:\t%llu us", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_T_OFFSET])); + + if (link_sinfo[NL80211_STA_INFO_TX_BITRATE]) { + char buf[100]; + + parse_bitrate(link_sinfo[NL80211_STA_INFO_TX_BITRATE], buf, sizeof(buf)); + printf("%stx bitrate:\t%s", indent, buf); + } + + if (link_sinfo[NL80211_STA_INFO_TX_DURATION]) + printf("%stx duration:\t%lld us", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_TX_DURATION])); + + if (link_sinfo[NL80211_STA_INFO_RX_BITRATE]) { + char buf[100]; + + parse_bitrate(link_sinfo[NL80211_STA_INFO_RX_BITRATE], buf, sizeof(buf)); + printf("%srx bitrate:\t%s", indent, buf); + } + + if (link_sinfo[NL80211_STA_INFO_RX_DURATION]) + printf("%srx duration:\t%lld us", indent, + (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_RX_DURATION])); + + if (link_sinfo[NL80211_STA_INFO_ACK_SIGNAL]) + printf("%slast ack signal:%d dBm", indent, + (int8_t)nla_get_u8(link_sinfo[NL80211_STA_INFO_ACK_SIGNAL])); + + if (link_sinfo[NL80211_STA_INFO_ACK_SIGNAL_AVG]) + printf("%savg ack signal:\t%d dBm", indent, + (int8_t)nla_get_u8(link_sinfo[NL80211_STA_INFO_ACK_SIGNAL_AVG])); + + if (link_sinfo[NL80211_STA_INFO_AIRTIME_WEIGHT]) { + printf("%sairtime weight: %d", indent, + nla_get_u16(link_sinfo[NL80211_STA_INFO_AIRTIME_WEIGHT])); + } + + if (link_sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) { + uint32_t thr; + + thr = nla_get_u32(link_sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]); + /* convert in Mbps but scale by 1000 to save kbps units */ + thr = thr * 1000 / 1024; + + printf("%sexpected throughput:\t%u.%uMbps", indent, + thr / 1000, thr % 1000); + } + + if (link_sinfo[NL80211_STA_INFO_TID_STATS] && arg != NULL && + !strcmp((char *)arg, "-v")) + parse_tid_stats(link_sinfo[NL80211_STA_INFO_TID_STATS], indent); + if (link_sinfo[NL80211_STA_INFO_BSS_PARAM]) + parse_bss_param(link_sinfo[NL80211_STA_INFO_BSS_PARAM], indent); + if (link_sinfo[NL80211_STA_INFO_CONNECTED_TIME]) + printf("%sconnected time:\t%u seconds", indent, + nla_get_u32(link_sinfo[NL80211_STA_INFO_CONNECTED_TIME])); + if (link_sinfo[NL80211_STA_INFO_ASSOC_AT_BOOTTIME]) { + unsigned long long bt; + struct timespec now_ts; + unsigned long long boot_ns; + unsigned long long assoc_at_ms; + + clock_gettime(CLOCK_BOOTTIME, &now_ts); + boot_ns = now_ts.tv_sec * 1000000000ULL; + boot_ns += now_ts.tv_nsec; + + bt = (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_ASSOC_AT_BOOTTIME]); + printf("%sassociated at [boottime]:\t%llu.%.3llus", + indent, bt/1000000000, (bt%1000000000)/1000000); + assoc_at_ms = now_ms - ((boot_ns - bt) / 1000000); + printf("%sassociated at:\t%llu ms", indent, assoc_at_ms); + } +} + static int print_sta_handler(struct nl_msg *msg, void *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 *link_sinfo[NL80211_STA_INFO_MAX + 1]; + struct nlattr *attrs, *link[NL80211_ATTR_MAX + 1]; char mac_addr[20], state_name[10], dev[20]; struct nl80211_sta_flag_update *sta_flags; static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = { @@ -356,7 +497,6 @@ static int print_sta_handler(struct nl_msg *msg, void *arg) [NL80211_STA_INFO_CONNECTED_TO_AS] = { .type = NLA_U8 }, [NL80211_STA_INFO_CONNECTED_TO_GATE] = { .type = NLA_U8 }, }; - char *chain; struct timeval now; unsigned long long now_ms; @@ -388,107 +528,6 @@ static int print_sta_handler(struct nl_msg *msg, void *arg) if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), dev); printf("Station %s (on %s)", mac_addr, dev); - if (sinfo[NL80211_STA_INFO_INACTIVE_TIME]) - printf("\n\tinactive time:\t%u ms", - nla_get_u32(sinfo[NL80211_STA_INFO_INACTIVE_TIME])); - if (sinfo[NL80211_STA_INFO_RX_BYTES64]) - printf("\n\trx bytes:\t%llu", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_RX_BYTES64])); - else if (sinfo[NL80211_STA_INFO_RX_BYTES]) - printf("\n\trx bytes:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_RX_BYTES])); - if (sinfo[NL80211_STA_INFO_RX_PACKETS]) - printf("\n\trx packets:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_RX_PACKETS])); - if (sinfo[NL80211_STA_INFO_TX_BYTES64]) - printf("\n\ttx bytes:\t%llu", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_TX_BYTES64])); - else if (sinfo[NL80211_STA_INFO_TX_BYTES]) - printf("\n\ttx bytes:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_TX_BYTES])); - if (sinfo[NL80211_STA_INFO_TX_PACKETS]) - printf("\n\ttx packets:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_TX_PACKETS])); - if (sinfo[NL80211_STA_INFO_TX_RETRIES]) - printf("\n\ttx retries:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_TX_RETRIES])); - if (sinfo[NL80211_STA_INFO_TX_FAILED]) - printf("\n\ttx failed:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_TX_FAILED])); - if (sinfo[NL80211_STA_INFO_BEACON_LOSS]) - printf("\n\tbeacon loss:\t%u", - nla_get_u32(sinfo[NL80211_STA_INFO_BEACON_LOSS])); - if (sinfo[NL80211_STA_INFO_BEACON_RX]) - printf("\n\tbeacon rx:\t%llu", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_BEACON_RX])); - if (sinfo[NL80211_STA_INFO_RX_DROP_MISC]) - printf("\n\trx drop misc:\t%llu", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_RX_DROP_MISC])); - - chain = get_chain_signal(sinfo[NL80211_STA_INFO_CHAIN_SIGNAL]); - if (sinfo[NL80211_STA_INFO_SIGNAL]) - printf("\n\tsignal: \t%d %sdBm", - (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), - chain); - - chain = get_chain_signal(sinfo[NL80211_STA_INFO_CHAIN_SIGNAL_AVG]); - if (sinfo[NL80211_STA_INFO_SIGNAL_AVG]) - printf("\n\tsignal avg:\t%d %sdBm", - (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL_AVG]), - chain); - - if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]) - printf("\n\tbeacon signal avg:\t%d dBm", - (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG])); - if (sinfo[NL80211_STA_INFO_T_OFFSET]) - printf("\n\tToffset:\t%llu us", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_T_OFFSET])); - - if (sinfo[NL80211_STA_INFO_TX_BITRATE]) { - char buf[100]; - - parse_bitrate(sinfo[NL80211_STA_INFO_TX_BITRATE], buf, sizeof(buf)); - printf("\n\ttx bitrate:\t%s", buf); - } - - if (sinfo[NL80211_STA_INFO_TX_DURATION]) - printf("\n\ttx duration:\t%lld us", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_TX_DURATION])); - - if (sinfo[NL80211_STA_INFO_RX_BITRATE]) { - char buf[100]; - - parse_bitrate(sinfo[NL80211_STA_INFO_RX_BITRATE], buf, sizeof(buf)); - printf("\n\trx bitrate:\t%s", buf); - } - - if (sinfo[NL80211_STA_INFO_RX_DURATION]) - printf("\n\trx duration:\t%lld us", - (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_RX_DURATION])); - - if (sinfo[NL80211_STA_INFO_ACK_SIGNAL]) - printf("\n\tlast ack signal:%d dBm", - (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_ACK_SIGNAL])); - - if (sinfo[NL80211_STA_INFO_ACK_SIGNAL_AVG]) - printf("\n\tavg ack signal:\t%d dBm", - (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_ACK_SIGNAL_AVG])); - - if (sinfo[NL80211_STA_INFO_AIRTIME_WEIGHT]) { - printf("\n\tairtime weight: %d", nla_get_u16(sinfo[NL80211_STA_INFO_AIRTIME_WEIGHT])); - } - - if (sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) { - uint32_t thr; - - thr = nla_get_u32(sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]); - /* convert in Mbps but scale by 1000 to save kbps units */ - thr = thr * 1000 / 1024; - - printf("\n\texpected throughput:\t%u.%uMbps", - thr / 1000, thr % 1000); - } - if (sinfo[NL80211_STA_INFO_LLID]) printf("\n\tmesh llid:\t%d", nla_get_u16(sinfo[NL80211_STA_INFO_LLID])); @@ -610,32 +649,39 @@ static int print_sta_handler(struct nl_msg *msg, void *arg) } } - if (sinfo[NL80211_STA_INFO_TID_STATS] && arg != NULL && - !strcmp((char *)arg, "-v")) - parse_tid_stats(sinfo[NL80211_STA_INFO_TID_STATS]); - if (sinfo[NL80211_STA_INFO_BSS_PARAM]) - parse_bss_param(sinfo[NL80211_STA_INFO_BSS_PARAM]); - if (sinfo[NL80211_STA_INFO_CONNECTED_TIME]) - printf("\n\tconnected time:\t%u seconds", - nla_get_u32(sinfo[NL80211_STA_INFO_CONNECTED_TIME])); - if (sinfo[NL80211_STA_INFO_ASSOC_AT_BOOTTIME]) { - unsigned long long bt; - struct timespec now_ts; - unsigned long long boot_ns; - unsigned long long assoc_at_ms; + /* print non-MLO/MLO specific fields */ + print_nested_sta_handler(sinfo, arg, "\n\t"); - clock_gettime(CLOCK_BOOTTIME, &now_ts); - boot_ns = now_ts.tv_sec * 1000000000ULL; - boot_ns += now_ts.tv_nsec; + printf("\n\tcurrent time:\t%llu ms\n", now_ms); - bt = (unsigned long long)nla_get_u64(sinfo[NL80211_STA_INFO_ASSOC_AT_BOOTTIME]); - printf("\n\tassociated at [boottime]:\t%llu.%.3llus", - bt/1000000000, (bt%1000000000)/1000000); - assoc_at_ms = now_ms - ((boot_ns - bt) / 1000000); - printf("\n\tassociated at:\t%llu ms", assoc_at_ms); + /* print link specific fields */ + if (tb[NL80211_ATTR_MLO_LINKS]) { + int ret = 0; + + nla_for_each_nested(attrs, tb[NL80211_ATTR_MLO_LINKS], ret) { + nla_parse_nested(link, NL80211_ATTR_MAX, attrs, NULL); + if (link[NL80211_ATTR_MLO_LINK_ID]) { + printf("\n\tLink %u:", nla_get_u8(link[NL80211_ATTR_MLO_LINK_ID])); + if (link[NL80211_ATTR_MAC]) { + mac_addr_n2a(mac_addr, nla_data(link[NL80211_ATTR_MAC])); + printf("\n\t\taddress: %s", mac_addr); + } + if (!link[NL80211_ATTR_STA_INFO]) { + fprintf(stderr, "link sta stats missing!\n"); + return NL_SKIP; + } + + if (nla_parse_nested(link_sinfo, NL80211_STA_INFO_MAX, + link[NL80211_ATTR_STA_INFO], + stats_policy)) { + fprintf(stderr, "failed to parse nested attributes!\n"); + return NL_SKIP; + } + print_nested_sta_handler(link_sinfo, arg, "\n\t\t"); + } + } } - - printf("\n\tcurrent time:\t%llu ms\n", now_ms); + printf("\n"); return NL_SKIP; } base-commit: 8d52fb4ccc5398a89dd99eba132a7faa3136e1ce -- 2.34.1