]> git.ipfire.org Git - thirdparty/iw.git/commitdiff
iw: add FTM initiator support
authorJohannes Berg <johannes.berg@intel.com>
Wed, 5 Dec 2018 10:36:31 +0000 (11:36 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 1 Feb 2019 22:01:08 +0000 (23:01 +0100)
Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Makefile
event.c
measurements.c [new file with mode: 0644]

index 02da9c36ff00456b0d22be766f610b6f2e2e6e95..73ee15731937eb671f14c1a8cf55aefcb31683b3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ OBJS = iw.o genl.o event.o info.o phy.o \
        mesh.o mpath.o mpp.o scan.o reg.o version.o \
        reason.o status.o connect.o link.o offch.o ps.o cqm.o \
        bitrate.o wowlan.o coalesce.o roc.o p2p.o vendor.o mgmt.o \
-       ap.o sha256.o nan.o bloom.o
+       ap.o sha256.o nan.o bloom.o measurements.o
 OBJS += sections.o
 
 OBJS-$(HWSIM) += hwsim.o
diff --git a/event.c b/event.c
index 6c9d745bf32cd2d1b73412310f3178bb3eaa2c9c..100f644d00d1a0ba41a30ea0c6376139ebf4a640 100644 (file)
--- a/event.c
+++ b/event.c
@@ -355,6 +355,205 @@ static void parse_nan_term(struct nlattr **attrs)
        }
 }
 
+static const char *ftm_fail_reason(unsigned int reason)
+{
+#define FTM_FAIL_REASON(x) case NL80211_PMSR_FTM_FAILURE_##x: return #x
+       switch (reason) {
+       FTM_FAIL_REASON(UNSPECIFIED);
+       FTM_FAIL_REASON(NO_RESPONSE);
+       FTM_FAIL_REASON(REJECTED);
+       FTM_FAIL_REASON(WRONG_CHANNEL);
+       FTM_FAIL_REASON(PEER_NOT_CAPABLE);
+       FTM_FAIL_REASON(INVALID_TIMESTAMP);
+       FTM_FAIL_REASON(PEER_BUSY);
+       FTM_FAIL_REASON(BAD_CHANGED_PARAMS);
+       default:
+               return "unknown";
+       }
+}
+
+static void parse_pmsr_ftm_data(struct nlattr *data)
+{
+       struct nlattr *ftm[NL80211_PMSR_FTM_RESP_ATTR_MAX + 1];
+
+       printf("    FTM");
+       nla_parse_nested(ftm, NL80211_PMSR_FTM_RESP_ATTR_MAX, data, NULL);
+
+       if (ftm[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]) {
+               printf(" failed: %s (%d)",
+                      ftm_fail_reason(nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON])),
+                      nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]));
+               if (ftm[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME])
+                       printf(" retry after %us",
+                              nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]));
+               printf("\n");
+               return;
+       }
+
+       printf("\n");
+
+#define PFTM(tp, attr, sign)                                                   \
+       do {                                                                    \
+               if (ftm[NL80211_PMSR_FTM_RESP_ATTR_##attr])                     \
+                       printf("      " #attr ": %lld\n",                       \
+                              (sign long long)nla_get_##tp(                    \
+                               ftm[NL80211_PMSR_FTM_RESP_ATTR_##attr]));       \
+       } while (0)
+
+       PFTM(u32, BURST_INDEX, unsigned);
+       PFTM(u32, NUM_FTMR_ATTEMPTS, unsigned);
+       PFTM(u32, NUM_FTMR_SUCCESSES, unsigned);
+       PFTM(u8, NUM_BURSTS_EXP, unsigned);
+       PFTM(u8, BURST_DURATION, unsigned);
+       PFTM(u8, FTMS_PER_BURST, unsigned);
+       PFTM(u32, RSSI_AVG, signed);
+       PFTM(u32, RSSI_SPREAD, unsigned);
+       PFTM(u64, RTT_AVG, signed);
+       PFTM(u64, RTT_VARIANCE, unsigned);
+       PFTM(u64, RTT_SPREAD, unsigned);
+       PFTM(u64, DIST_AVG, signed);
+       PFTM(u64, DIST_VARIANCE, unsigned);
+       PFTM(u64, DIST_SPREAD, unsigned);
+
+       if (ftm[NL80211_PMSR_FTM_RESP_ATTR_TX_RATE]) {
+               char buf[100];
+
+               parse_bitrate(ftm[NL80211_PMSR_FTM_RESP_ATTR_TX_RATE],
+                             buf, sizeof(buf));
+               printf("      TX bitrate: %s\n", buf);
+       }
+
+       if (ftm[NL80211_PMSR_FTM_RESP_ATTR_RX_RATE]) {
+               char buf[100];
+
+               parse_bitrate(ftm[NL80211_PMSR_FTM_RESP_ATTR_RX_RATE],
+                             buf, sizeof(buf));
+               printf("      RX bitrate: %s\n", buf);
+       }
+
+       if (ftm[NL80211_PMSR_FTM_RESP_ATTR_LCI])
+               iw_hexdump("      LCI",
+                          nla_data(ftm[NL80211_PMSR_FTM_RESP_ATTR_LCI]),
+                          nla_len(ftm[NL80211_PMSR_FTM_RESP_ATTR_LCI]));
+
+       if (ftm[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC])
+               iw_hexdump("      civic location",
+                          nla_data(ftm[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]),
+                          nla_len(ftm[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]));
+}
+
+static const char *pmsr_status(unsigned int status)
+{
+#define PMSR_STATUS(x) case NL80211_PMSR_STATUS_##x: return #x
+       switch (status) {
+       PMSR_STATUS(SUCCESS);
+       PMSR_STATUS(REFUSED);
+       PMSR_STATUS(TIMEOUT);
+       PMSR_STATUS(FAILURE);
+       default:
+               return "unknown";
+       }
+#undef PMSR_STATUS
+}
+
+static void parse_pmsr_peer(struct nlattr *peer)
+{
+       struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1];
+       struct nlattr *resp[NL80211_PMSR_RESP_ATTR_MAX + 1];
+       struct nlattr *data[NL80211_PMSR_TYPE_MAX + 1];
+       char macbuf[6*3];
+       int err;
+
+       err = nla_parse_nested(tb, NL80211_PMSR_PEER_ATTR_MAX, peer, NULL);
+       if (err) {
+               printf("  Peer: failed to parse!\n");
+               return;
+       }
+
+       if (!tb[NL80211_PMSR_PEER_ATTR_ADDR]) {
+               printf("  Peer: no MAC address\n");
+               return;
+       }
+
+       mac_addr_n2a(macbuf, nla_data(tb[NL80211_PMSR_PEER_ATTR_ADDR]));
+       printf("  Peer %s:", macbuf);
+
+       if (!tb[NL80211_PMSR_PEER_ATTR_RESP]) {
+               printf(" no response!\n");
+               return;
+       }
+
+       err = nla_parse_nested(resp, NL80211_PMSR_RESP_ATTR_MAX,
+                              tb[NL80211_PMSR_PEER_ATTR_RESP], NULL);
+       if (err) {
+               printf(" failed to parse response!\n");
+               return;
+       }
+
+       if (resp[NL80211_PMSR_RESP_ATTR_STATUS])
+               printf(" status=%d (%s)",
+                      nla_get_u32(resp[NL80211_PMSR_RESP_ATTR_STATUS]),
+                      pmsr_status(nla_get_u32(resp[NL80211_PMSR_RESP_ATTR_STATUS])));
+       if (resp[NL80211_PMSR_RESP_ATTR_HOST_TIME])
+               printf(" @%llu",
+                      (unsigned long long)nla_get_u64(resp[NL80211_PMSR_RESP_ATTR_HOST_TIME]));
+       if (resp[NL80211_PMSR_RESP_ATTR_AP_TSF])
+               printf(" tsf=%llu",
+                      (unsigned long long)nla_get_u64(resp[NL80211_PMSR_RESP_ATTR_AP_TSF]));
+       if (resp[NL80211_PMSR_RESP_ATTR_FINAL])
+               printf(" (final)");
+
+       if (!resp[NL80211_PMSR_RESP_ATTR_DATA]) {
+               printf(" - no data!\n");
+               return;
+       }
+
+       printf("\n");
+
+       nla_parse_nested(data, NL80211_PMSR_TYPE_MAX,
+                        resp[NL80211_PMSR_RESP_ATTR_DATA], NULL);
+
+       if (data[NL80211_PMSR_TYPE_FTM])
+               parse_pmsr_ftm_data(data[NL80211_PMSR_TYPE_FTM]);
+}
+
+static void parse_pmsr_result(struct nlattr **tb,
+                             struct print_event_args *pargs)
+{
+       struct nlattr *pmsr[NL80211_PMSR_ATTR_MAX + 1];
+       struct nlattr *peer;
+       unsigned long long cookie;
+       int err, i;
+
+       if (!tb[NL80211_ATTR_COOKIE]) {
+               printf("Peer measurements: no cookie!\n");
+               return;
+       }
+       cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]);
+
+       if (!tb[NL80211_ATTR_PEER_MEASUREMENTS]) {
+               printf("Peer measurements: no measurement data!\n");
+               return;
+       }
+
+       err = nla_parse_nested(pmsr, NL80211_PMSR_ATTR_MAX,
+                              tb[NL80211_ATTR_PEER_MEASUREMENTS], NULL);
+       if (err) {
+               printf("Peer measurements: failed to parse measurement data!\n");
+               return;
+       }
+
+       if (!pmsr[NL80211_PMSR_ATTR_PEERS]) {
+               printf("Peer measurements: no peer data!\n");
+               return;
+       }
+
+       printf("Peer measurements (cookie %llu):\n", cookie);
+
+       nla_for_each_nested(peer, pmsr[NL80211_PMSR_ATTR_PEERS], i)
+               parse_pmsr_peer(peer);
+}
+
 static void parse_nan_match(struct nlattr **attrs)
 {
        char macbuf[6*3];
@@ -777,6 +976,12 @@ static int print_event(struct nl_msg *msg, void *arg)
        case NL80211_CMD_DEL_WIPHY:
                printf("delete wiphy\n");
                break;
+       case NL80211_CMD_PEER_MEASUREMENT_RESULT:
+               parse_pmsr_result(tb, args);
+               break;
+       case NL80211_CMD_PEER_MEASUREMENT_COMPLETE:
+               printf("peer measurement complete\n");
+               break;
        case NL80211_CMD_DEL_NAN_FUNCTION:
                parse_nan_term(tb);
                break;
diff --git a/measurements.c b/measurements.c
new file mode 100644 (file)
index 0000000..385143f
--- /dev/null
@@ -0,0 +1,320 @@
+#include <errno.h>
+
+#include "nl80211.h"
+#include "iw.h"
+#include <unistd.h>
+
+SECTION(measurement);
+
+static int put_preamble(struct nl_msg *msg, char *s)
+{
+       static const struct {
+               const char *name;
+               unsigned int val;
+       } preamble_map[] = {
+               { .name = "legacy", .val = NL80211_PREAMBLE_LEGACY, },
+               { .name = "ht", .val = NL80211_PREAMBLE_HT, },
+               { .name = "vht", .val = NL80211_PREAMBLE_VHT, },
+               { .name = "dmg", .val = NL80211_PREAMBLE_DMG, },
+       };
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(preamble_map); i++) {
+               if (strcasecmp(preamble_map[i].name, s) == 0) {
+                       NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE,
+                                   preamble_map[i].val);
+                       return 0;
+               }
+       }
+
+nla_put_failure:
+       return -1;
+}
+
+static int parse_ftm_target(struct nl_msg *msg, char *str, int peer_index)
+{
+       unsigned char addr[ETH_ALEN];
+       int res, consumed;
+       char *bw = NULL, *pos, *tmp, *save_ptr, *delims = " \t\n";
+       struct nlattr *peer, *req, *reqdata, *ftm, *chan;
+       bool report_ap_tsf = false, preamble = false;
+       unsigned int freq = 0, cf1 = 0, cf2 = 0;
+
+       res = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx%n",
+                    &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5],
+                    &consumed);
+
+       if (res != ETH_ALEN) {
+               printf("Invalid MAC address\n");
+               return HANDLER_RET_USAGE;
+       }
+
+       peer = nla_nest_start(msg, peer_index);
+
+       NLA_PUT(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, addr);
+
+       req = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_REQ);
+       if (!req)
+               goto nla_put_failure;
+       reqdata = nla_nest_start(msg, NL80211_PMSR_REQ_ATTR_DATA);
+       if (!reqdata)
+               goto nla_put_failure;
+       ftm = nla_nest_start(msg, NL80211_PMSR_TYPE_FTM);
+       if (!ftm)
+               goto nla_put_failure;
+
+       str += consumed;
+       pos = strtok_r(str, delims, &save_ptr);
+
+       while (pos) {
+               if (strncmp(pos, "cf=", 3) == 0) {
+                       freq = strtol(pos + 3, &tmp, 0);
+                       if (*tmp) {
+                               printf("Invalid cf value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "bw=", 3) == 0) {
+                       bw = pos + 3;
+               } else if (strncmp(pos, "cf1=", 4) == 0) {
+                       cf1 = strtol(pos + 4, &tmp, 0);
+                       if (*tmp) {
+                               printf("Invalid cf1 value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "cf2=", 4) == 0) {
+                       cf2 = strtol(pos + 4, &tmp, 0);
+                       if (*tmp) {
+                               printf("Invalid cf2 value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "bursts_exp=", 11) == 0) {
+                       NLA_PUT_U8(msg,
+                                  NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP,
+                                  strtol(pos + 11, &tmp, 0));
+                       if (*tmp) {
+                               printf("Invalid bursts_exp value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "burst_period=", 13) == 0) {
+                       NLA_PUT_U16(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD,
+                                   strtol(pos + 13, &tmp, 0));
+                       if (*tmp) {
+                               printf("Invalid burst_period value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "retries=", 8) == 0) {
+                       NLA_PUT_U8(msg,
+                                  NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES,
+                                  strtol(pos + 8, &tmp, 0));
+                       if (*tmp) {
+                               printf("Invalid retries value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "burst_duration=", 15) == 0) {
+                       NLA_PUT_U8(msg,
+                                  NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION,
+                                  strtol(pos + 15, &tmp, 0));
+                       if (*tmp) {
+                               printf("Invalid burst_duration value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strncmp(pos, "ftms_per_burst=", 15) == 0) {
+                       NLA_PUT_U8(msg,
+                                  NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST,
+                                  strtol(pos + 15, &tmp, 0));
+                       if (*tmp) {
+                               printf("Invalid ftms_per_burst value!\n");
+                               return HANDLER_RET_USAGE;
+                       }
+               } else if (strcmp(pos, "asap") == 0) {
+                       NLA_PUT_FLAG(msg, NL80211_PMSR_FTM_REQ_ATTR_ASAP);
+               } else if (strcmp(pos, "ap-tsf") == 0) {
+                       report_ap_tsf = true;
+               } else if (strcmp(pos, "civic") == 0) {
+                       NLA_PUT_FLAG(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC);
+               } else if (strcmp(pos, "lci") == 0) {
+                       NLA_PUT_FLAG(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI);
+               } else if (strncmp(pos, "preamble=", 9) == 0) {
+                       if (put_preamble(msg, pos + 9)) {
+                               printf("Invalid preamble %s\n", pos + 9);
+                               return HANDLER_RET_USAGE;
+                       }
+                       preamble = true;
+               } else {
+                       printf("Unknown parameter %s\n", pos);
+                       return HANDLER_RET_USAGE;
+               }
+
+               pos = strtok_r(NULL, delims, &save_ptr);
+       }
+
+       if (!preamble) {
+               int preamble = -1;
+
+               switch (str_to_bw(bw)) {
+               case NL80211_CHAN_WIDTH_20_NOHT:
+               case NL80211_CHAN_WIDTH_5:
+               case NL80211_CHAN_WIDTH_10:
+                       preamble = NL80211_PREAMBLE_LEGACY;
+                       break;
+               case NL80211_CHAN_WIDTH_20:
+               case NL80211_CHAN_WIDTH_40:
+                       preamble = NL80211_PREAMBLE_HT;
+                       break;
+               case NL80211_CHAN_WIDTH_80:
+               case NL80211_CHAN_WIDTH_80P80:
+               case NL80211_CHAN_WIDTH_160:
+                       preamble = NL80211_PREAMBLE_VHT;
+                       break;
+               }
+
+               NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, preamble);
+       }
+
+       nla_nest_end(msg, ftm);
+       if (report_ap_tsf)
+               NLA_PUT_FLAG(msg, NL80211_PMSR_REQ_ATTR_GET_AP_TSF);
+       nla_nest_end(msg, reqdata);
+       nla_nest_end(msg, req);
+
+       /* set the channel */
+       chan = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_CHAN);
+       if (!chan)
+               goto nla_put_failure;
+       if (freq)
+               NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq);
+       if (cf1)
+               NLA_PUT_U32(msg, NL80211_ATTR_CENTER_FREQ1, cf1);
+       if (cf2)
+               NLA_PUT_U32(msg, NL80211_ATTR_CENTER_FREQ2, cf2);
+       if (bw)
+               NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH,
+                           str_to_bw(bw));
+       nla_nest_end(msg, chan);
+
+       nla_nest_end(msg, peer);
+       return 0;
+nla_put_failure:
+       return -ENOBUFS;
+}
+
+static int parse_ftm_config(struct nl_msg *msg, const char *file)
+{
+       FILE *input;
+       char line[256];
+       int line_num;
+
+       input = fopen(file, "r");
+       if (!input) {
+               int err = errno;
+
+               printf("Failed to open file: %s\n", strerror(err));
+               return -err;
+       }
+
+       for (line_num = 1; fgets(line, sizeof(line), input); line_num++) {
+               if (line[0] == '#')
+                       continue;
+
+               if (parse_ftm_target(msg, line, line_num)) {
+                       printf("Invalid FTM configuration at line %d!\n",
+                              line_num);
+                       return HANDLER_RET_USAGE;
+               }
+       }
+
+       return 0;
+}
+
+static int handle_ftm_req(struct nl80211_state *state, struct nl_msg *msg,
+                         int argc, char **argv, enum id_input id)
+{
+       int err, i;
+       static char **req_argv;
+       static const __u32 wait[] = {
+               NL80211_CMD_PEER_MEASUREMENT_COMPLETE,
+       };
+       static const __u32 print[] = {
+               NL80211_CMD_PEER_MEASUREMENT_RESULT,
+               NL80211_CMD_PEER_MEASUREMENT_COMPLETE,
+       };
+       struct print_event_args printargs = { };
+
+       req_argv = calloc(argc + 1, sizeof(req_argv[0]));
+       req_argv[0] = argv[0];
+       req_argv[1] = "measurement";
+       req_argv[2] = "ftm_request_send";
+       for (i = 3; i < argc; i++)
+               req_argv[i] = argv[i];
+
+       err = handle_cmd(state, id, argc, req_argv);
+
+       free(req_argv);
+
+       if (err)
+               return err;
+
+       __do_listen_events(state,
+                          ARRAY_SIZE(wait), wait,
+                          ARRAY_SIZE(print), print,
+                          &printargs);
+       return 0;
+}
+
+static int handle_ftm_req_send(struct nl80211_state *state, struct nl_msg *msg,
+                              int argc, char **argv, enum id_input id)
+{
+       struct nlattr *pmsr, *peers;
+       const char *file;
+       int err;
+
+       file = argv[0];
+       argc--;
+       argv++;
+       while (argc) {
+               if (strncmp(argv[0], "randomise", 9) == 0 ||
+                   strncmp(argv[0], "randomize", 9) == 0) {
+                       err = parse_random_mac_addr(msg, argv[0] + 9);
+                       if (err)
+                               return err;
+               } else if (strncmp(argv[0], "timeout=", 8) == 0) {
+                       char *end;
+
+                       NLA_PUT_U32(msg, NL80211_ATTR_TIMEOUT,
+                                   strtoul(argv[0] + 8, &end, 0));
+                       if (*end)
+                               return HANDLER_RET_USAGE;
+               } else {
+                       return HANDLER_RET_USAGE;
+               }
+
+               argc--;
+               argv++;
+       }
+
+       pmsr = nla_nest_start(msg, NL80211_ATTR_PEER_MEASUREMENTS);
+       if (!pmsr)
+               goto nla_put_failure;
+       peers = nla_nest_start(msg, NL80211_PMSR_ATTR_PEERS);
+       if (!peers)
+               goto nla_put_failure;
+
+       err = parse_ftm_config(msg, file);
+       if (err)
+               return err;
+
+       nla_nest_end(msg, peers);
+       nla_nest_end(msg, pmsr);
+
+       return 0;
+
+nla_put_failure:
+       return -ENOBUFS;
+}
+COMMAND(measurement, ftm_request, "<config-file> [timeout=<seconds>] [randomise[=<addr>/<mask>]]", 0, 0,
+       CIB_NETDEV, handle_ftm_req,
+       "Send an FTM request to the targets supplied in the config file.\n"
+       "Each line in the file represents a target, with the following format:\n"
+       "<addr> bw=<[20|40|80|80+80|160]> cf=<center_freq> [cf1=<center_freq1>] [cf2=<center_freq2>] [ftms_per_burst=<samples per burst>] [ap-tsf] [asap] [bursts_exp=<num of bursts exponent>] [burst_period=<burst period>] [retries=<num of retries>] [burst_duration=<burst duration>] [preamble=<legacy,ht,vht,dmg>] [lci] [civic]");
+HIDDEN(measurement, ftm_request_send, "", NL80211_CMD_PEER_MEASUREMENT_START,
+       0, CIB_NETDEV, handle_ftm_req_send);