You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
565 lines
15 KiB
565 lines
15 KiB
#include <net/if.h> |
|
#include <errno.h> |
|
#include <string.h> |
|
|
|
#include <netlink/genl/genl.h> |
|
#include <netlink/genl/family.h> |
|
#include <netlink/genl/ctrl.h> |
|
#include <netlink/msg.h> |
|
#include <netlink/attr.h> |
|
|
|
#include "nl80211.h" |
|
#include "iw.h" |
|
|
|
SECTION(station); |
|
|
|
enum plink_state { |
|
LISTEN, |
|
OPN_SNT, |
|
OPN_RCVD, |
|
CNF_RCVD, |
|
ESTAB, |
|
HOLDING, |
|
BLOCKED |
|
}; |
|
|
|
static void print_power_mode(struct nlattr *a) |
|
{ |
|
enum nl80211_mesh_power_mode pm = nla_get_u32(a); |
|
|
|
switch (pm) { |
|
case NL80211_MESH_POWER_ACTIVE: |
|
printf("ACTIVE"); |
|
break; |
|
case NL80211_MESH_POWER_LIGHT_SLEEP: |
|
printf("LIGHT SLEEP"); |
|
break; |
|
case NL80211_MESH_POWER_DEEP_SLEEP: |
|
printf("DEEP SLEEP"); |
|
break; |
|
default: |
|
printf("UNKNOWN"); |
|
break; |
|
} |
|
} |
|
|
|
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])); |
|
} |
|
|
|
static char *get_chain_signal(struct nlattr *attr_list) |
|
{ |
|
struct nlattr *attr; |
|
static char buf[64]; |
|
char *cur = buf; |
|
int i = 0, rem; |
|
const char *prefix; |
|
|
|
if (!attr_list) |
|
return ""; |
|
|
|
nla_for_each_nested(attr, attr_list, rem) { |
|
if (i++ > 0) |
|
prefix = ", "; |
|
else |
|
prefix = "["; |
|
|
|
cur += snprintf(cur, sizeof(buf) - (cur - buf), "%s%d", prefix, |
|
(int8_t) nla_get_u8(attr)); |
|
} |
|
|
|
if (i) |
|
snprintf(cur, sizeof(buf) - (cur - buf), "] "); |
|
|
|
return buf; |
|
} |
|
|
|
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]; |
|
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] = { |
|
[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_T_OFFSET] = { .type = NLA_U64 }, |
|
[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED }, |
|
[NL80211_STA_INFO_RX_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_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 }, |
|
}; |
|
char *chain; |
|
|
|
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
|
genlmsg_attrlen(gnlh, 0), NULL); |
|
|
|
/* |
|
* TODO: validate the interface and mac address! |
|
* Otherwise, there's a race condition as soon as |
|
* the kernel starts sending station notifications. |
|
*/ |
|
|
|
if (!tb[NL80211_ATTR_STA_INFO]) { |
|
fprintf(stderr, "sta stats missing!\n"); |
|
return NL_SKIP; |
|
} |
|
if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, |
|
tb[NL80211_ATTR_STA_INFO], |
|
stats_policy)) { |
|
fprintf(stderr, "failed to parse nested attributes!\n"); |
|
return NL_SKIP; |
|
} |
|
|
|
mac_addr_n2a(mac_addr, nla_data(tb[NL80211_ATTR_MAC])); |
|
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_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_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])); |
|
|
|
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_T_OFFSET]) |
|
printf("\n\tToffset:\t%lld 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_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_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])); |
|
if (sinfo[NL80211_STA_INFO_PLID]) |
|
printf("\n\tmesh plid:\t%d", |
|
nla_get_u16(sinfo[NL80211_STA_INFO_PLID])); |
|
if (sinfo[NL80211_STA_INFO_PLINK_STATE]) { |
|
switch (nla_get_u8(sinfo[NL80211_STA_INFO_PLINK_STATE])) { |
|
case LISTEN: |
|
strcpy(state_name, "LISTEN"); |
|
break; |
|
case OPN_SNT: |
|
strcpy(state_name, "OPN_SNT"); |
|
break; |
|
case OPN_RCVD: |
|
strcpy(state_name, "OPN_RCVD"); |
|
break; |
|
case CNF_RCVD: |
|
strcpy(state_name, "CNF_RCVD"); |
|
break; |
|
case ESTAB: |
|
strcpy(state_name, "ESTAB"); |
|
break; |
|
case HOLDING: |
|
strcpy(state_name, "HOLDING"); |
|
break; |
|
case BLOCKED: |
|
strcpy(state_name, "BLOCKED"); |
|
break; |
|
default: |
|
strcpy(state_name, "UNKNOWN"); |
|
break; |
|
} |
|
printf("\n\tmesh plink:\t%s", state_name); |
|
} |
|
if (sinfo[NL80211_STA_INFO_LOCAL_PM]) { |
|
printf("\n\tmesh local PS mode:\t"); |
|
print_power_mode(sinfo[NL80211_STA_INFO_LOCAL_PM]); |
|
} |
|
if (sinfo[NL80211_STA_INFO_PEER_PM]) { |
|
printf("\n\tmesh peer PS mode:\t"); |
|
print_power_mode(sinfo[NL80211_STA_INFO_PEER_PM]); |
|
} |
|
if (sinfo[NL80211_STA_INFO_NONPEER_PM]) { |
|
printf("\n\tmesh non-peer PS mode:\t"); |
|
print_power_mode(sinfo[NL80211_STA_INFO_NONPEER_PM]); |
|
} |
|
|
|
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_AUTHORIZED)) { |
|
printf("\n\tauthorized:\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_AUTHORIZED)) |
|
printf("yes"); |
|
else |
|
printf("no"); |
|
} |
|
|
|
if (sta_flags->mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) { |
|
printf("\n\tauthenticated:\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) |
|
printf("yes"); |
|
else |
|
printf("no"); |
|
} |
|
|
|
if (sta_flags->mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) { |
|
printf("\n\tpreamble:\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) |
|
printf("short"); |
|
else |
|
printf("long"); |
|
} |
|
|
|
if (sta_flags->mask & BIT(NL80211_STA_FLAG_WME)) { |
|
printf("\n\tWMM/WME:\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_WME)) |
|
printf("yes"); |
|
else |
|
printf("no"); |
|
} |
|
|
|
if (sta_flags->mask & BIT(NL80211_STA_FLAG_MFP)) { |
|
printf("\n\tMFP:\t\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_MFP)) |
|
printf("yes"); |
|
else |
|
printf("no"); |
|
} |
|
|
|
if (sta_flags->mask & BIT(NL80211_STA_FLAG_TDLS_PEER)) { |
|
printf("\n\tTDLS peer:\t"); |
|
if (sta_flags->set & BIT(NL80211_STA_FLAG_TDLS_PEER)) |
|
printf("yes"); |
|
else |
|
printf("no"); |
|
} |
|
} |
|
|
|
if (sinfo[NL80211_STA_INFO_CONNECTED_TIME]) |
|
printf("\n\tconnected time:\t%u seconds", |
|
nla_get_u32(sinfo[NL80211_STA_INFO_CONNECTED_TIME])); |
|
|
|
printf("\n"); |
|
return NL_SKIP; |
|
} |
|
|
|
static int handle_station_get(struct nl80211_state *state, |
|
struct nl_cb *cb, |
|
struct nl_msg *msg, |
|
int argc, char **argv, |
|
enum id_input id) |
|
{ |
|
unsigned char mac_addr[ETH_ALEN]; |
|
|
|
if (argc < 1) |
|
return 1; |
|
|
|
if (mac_addr_a2n(mac_addr, argv[0])) { |
|
fprintf(stderr, "invalid mac address\n"); |
|
return 2; |
|
} |
|
|
|
argc--; |
|
argv++; |
|
|
|
if (argc) |
|
return 1; |
|
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); |
|
|
|
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, print_sta_handler, NULL); |
|
|
|
return 0; |
|
nla_put_failure: |
|
return -ENOBUFS; |
|
} |
|
COMMAND(station, get, "<MAC address>", |
|
NL80211_CMD_GET_STATION, 0, CIB_NETDEV, handle_station_get, |
|
"Get information for a specific station."); |
|
COMMAND(station, del, "<MAC address>", |
|
NL80211_CMD_DEL_STATION, 0, CIB_NETDEV, handle_station_get, |
|
"Remove the given station entry (use with caution!)"); |
|
|
|
static const struct cmd *station_set_plink; |
|
static const struct cmd *station_set_vlan; |
|
static const struct cmd *station_set_mesh_power_mode; |
|
|
|
static const struct cmd *select_station_cmd(int argc, char **argv) |
|
{ |
|
if (argc < 2) |
|
return NULL; |
|
if (strcmp(argv[1], "plink_action") == 0) |
|
return station_set_plink; |
|
if (strcmp(argv[1], "vlan") == 0) |
|
return station_set_vlan; |
|
if (strcmp(argv[1], "mesh_power_mode") == 0) |
|
return station_set_mesh_power_mode; |
|
return NULL; |
|
} |
|
|
|
static int handle_station_set_plink(struct nl80211_state *state, |
|
struct nl_cb *cb, |
|
struct nl_msg *msg, |
|
int argc, char **argv, |
|
enum id_input id) |
|
{ |
|
unsigned char plink_action; |
|
unsigned char mac_addr[ETH_ALEN]; |
|
|
|
if (argc < 3) |
|
return 1; |
|
|
|
if (mac_addr_a2n(mac_addr, argv[0])) { |
|
fprintf(stderr, "invalid mac address\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (strcmp("plink_action", argv[0]) != 0) |
|
return 1; |
|
argc--; |
|
argv++; |
|
|
|
if (strcmp("open", argv[0]) == 0) |
|
plink_action = NL80211_PLINK_ACTION_OPEN; |
|
else if (strcmp("block", argv[0]) == 0) |
|
plink_action = NL80211_PLINK_ACTION_BLOCK; |
|
else { |
|
fprintf(stderr, "plink action not supported\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (argc) |
|
return 1; |
|
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); |
|
NLA_PUT_U8(msg, NL80211_ATTR_STA_PLINK_ACTION, plink_action); |
|
|
|
return 0; |
|
nla_put_failure: |
|
return -ENOBUFS; |
|
} |
|
COMMAND_ALIAS(station, set, "<MAC address> plink_action <open|block>", |
|
NL80211_CMD_SET_STATION, 0, CIB_NETDEV, handle_station_set_plink, |
|
"Set mesh peer link action for this station (peer).", |
|
select_station_cmd, station_set_plink); |
|
|
|
static int handle_station_set_vlan(struct nl80211_state *state, |
|
struct nl_cb *cb, |
|
struct nl_msg *msg, |
|
int argc, char **argv, |
|
enum id_input id) |
|
{ |
|
unsigned char mac_addr[ETH_ALEN]; |
|
unsigned long sta_vlan = 0; |
|
char *err = NULL; |
|
|
|
if (argc < 3) |
|
return 1; |
|
|
|
if (mac_addr_a2n(mac_addr, argv[0])) { |
|
fprintf(stderr, "invalid mac address\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (strcmp("vlan", argv[0]) != 0) |
|
return 1; |
|
argc--; |
|
argv++; |
|
|
|
sta_vlan = strtoul(argv[0], &err, 0); |
|
if (err && *err) { |
|
fprintf(stderr, "invalid vlan id\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (argc) |
|
return 1; |
|
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); |
|
NLA_PUT_U32(msg, NL80211_ATTR_STA_VLAN, sta_vlan); |
|
|
|
return 0; |
|
nla_put_failure: |
|
return -ENOBUFS; |
|
} |
|
COMMAND_ALIAS(station, set, "<MAC address> vlan <ifindex>", |
|
NL80211_CMD_SET_STATION, 0, CIB_NETDEV, handle_station_set_vlan, |
|
"Set an AP VLAN for this station.", |
|
select_station_cmd, station_set_vlan); |
|
|
|
static int handle_station_set_mesh_power_mode(struct nl80211_state *state, |
|
struct nl_cb *cb, |
|
struct nl_msg *msg, |
|
int argc, char **argv, |
|
enum id_input id) |
|
{ |
|
unsigned char mesh_power_mode; |
|
unsigned char mac_addr[ETH_ALEN]; |
|
|
|
if (argc < 3) |
|
return 1; |
|
|
|
if (mac_addr_a2n(mac_addr, argv[0])) { |
|
fprintf(stderr, "invalid mac address\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (strcmp("mesh_power_mode", argv[0]) != 0) |
|
return 1; |
|
argc--; |
|
argv++; |
|
|
|
if (strcmp("active", argv[0]) == 0) |
|
mesh_power_mode = NL80211_MESH_POWER_ACTIVE; |
|
else if (strcmp("light", argv[0]) == 0) |
|
mesh_power_mode = NL80211_MESH_POWER_LIGHT_SLEEP; |
|
else if (strcmp("deep", argv[0]) == 0) |
|
mesh_power_mode = NL80211_MESH_POWER_DEEP_SLEEP; |
|
else { |
|
fprintf(stderr, "unknown mesh power mode\n"); |
|
return 2; |
|
} |
|
argc--; |
|
argv++; |
|
|
|
if (argc) |
|
return 1; |
|
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); |
|
NLA_PUT_U32(msg, NL80211_ATTR_LOCAL_MESH_POWER_MODE, mesh_power_mode); |
|
|
|
return 0; |
|
nla_put_failure: |
|
return -ENOBUFS; |
|
} |
|
COMMAND_ALIAS(station, set, "<MAC address> mesh_power_mode " |
|
"<active|light|deep>", NL80211_CMD_SET_STATION, 0, CIB_NETDEV, |
|
handle_station_set_mesh_power_mode, |
|
"Set link-specific mesh power mode for this station", |
|
select_station_cmd, station_set_mesh_power_mode); |
|
|
|
static int handle_station_dump(struct nl80211_state *state, |
|
struct nl_cb *cb, |
|
struct nl_msg *msg, |
|
int argc, char **argv, |
|
enum id_input id) |
|
{ |
|
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, print_sta_handler, NULL); |
|
return 0; |
|
} |
|
COMMAND(station, dump, NULL, |
|
NL80211_CMD_GET_STATION, NLM_F_DUMP, CIB_NETDEV, handle_station_dump, |
|
"List all stations known, e.g. the AP on managed interfaces");
|
|
|