diff options
Diffstat (limited to 'wifi_scan.c')
-rw-r--r-- | wifi_scan.c | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/wifi_scan.c b/wifi_scan.c new file mode 100644 index 0000000..6201474 --- /dev/null +++ b/wifi_scan.c @@ -0,0 +1,897 @@ +/* + * wifi-scan library implementation + * + * Copyright 2016-2018 (C) Bartosz Meglicki <meglickib@gmail.com> + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + + /* + * Library Overview + * + * This library uses netlink nl80211 user-space interface to retrieve wireless device information from kernel-space. + * For netlink communication libmnl is used (minimalistic user-space netlink library). + * + * First concept you need to understand is that netlink uses sockets to communicate between user-space and kernel-space. + * + * There are 2 netlink communication channels (sockets/buffers) + * - for notifications (about triggers, ready scan results) + * - for commands (commanding triggers, retrieving scan results, station information) + * + * wifi_scan_init initializes 2 channels, gets nl80211 id using generic netlink (genetlink), gets id of + * multicast group scan and subscribes to this group notifcations using notifications channel. + * + * wifi_scan_station gets the last (not necessarilly fresh) scan results that are available from the device, + * checks which station we are associated with and retrieves information about this station (using commands channel) + * + * wifi_scan_all reads up any pending notifications, commands a trigger if necessary, waits for the device to gather + * results and finally reads scan results with get_scan function (those are fresh results) + * + * wifi_scan_close frees up resources of two channels and any other resoureces that library uses. + * + * prepare_nl_messsage/send_nl_message/receive_nl_message are helper functions to simplify common tasks when issuing commands + * + * validate function simplifies common tasks (validates each attribute against table specifying what is valid) + * + */ + +#include "wifi_scan.h" + +#include <libmnl/libmnl.h> //netlink libmnl +#include <linux/nl80211.h> //nl80211 netlink +#include <linux/genetlink.h> //generic netlink + +#include <net/if.h> +#include <string.h> +#include <stdio.h> +#include <malloc.h> +#include <stdlib.h> +#include <fcntl.h> //fntnl (set descriptor options) +#include <errno.h> //errno + +// everything needed for sending/receiving with netlink +struct netlink_channel +{ + struct mnl_socket *nl; //netlink socket + char *buf; //buffer for messages (in and out) + uint16_t nl80211_id; //generic netlink nl80211 id + uint32_t ifindex; //the wireless interface number (e.g. interface number for wlan0) + uint32_t sequence; //the sequence number of netlink message + void *context; //additional data to be stored/used when processing concrete message +}; + +// internal library data passed around by user +struct wifi_scan +{ + struct netlink_channel notification_channel; + struct netlink_channel command_channel; +}; + +// DECLARATIONS AND TOP-DOWN LIBRARY OVERVIEW + +// INITIALIZATION + +// data needed from CTRL_CMD_GETFAMILY for nl80211, nl80211 id is stored in the channel rather then here +struct context_CTRL_CMD_GETFAMILY +{ + uint32_t id_NL80211_MULTICAST_GROUP_SCAN; //the id of group scan which we need to subscribe to +}; + +// public interface - initialize the library for wireless interface (e.g. wlan0) +struct wifi_scan *wifi_scan_init(const char *interface); + +// allocate memory, set initial values, etc. +static void init_netlink_channel(struct netlink_channel *channel, const char *interface); +// create netlink sockets for generic netlink +static void init_netlink_socket(struct netlink_channel *channel); + +// execute command to get nl80211 family and process the results +static int get_family_and_scan_ids(struct netlink_channel *channel); +// this processes kernel reply for get family request, stores family id +static int handle_CTRL_CMD_GETFAMILY(const struct nlmsghdr *nlh, void *data); +// parses multicast groups to get scan multicast group id +static void parse_CTRL_ATTR_MCAST_GROUPS(struct nlattr *nested, struct netlink_channel *channel); + +// subscribes channel to multicast group scan using scan group id +static void subscribe_NL80211_MULTICAST_GROUP_SCAN(struct netlink_channel *channel, uint32_t scan_group_id); + +// CLEANUP + +// public interface - cleans up after library +void wifi_scan_close(struct wifi_scan *wifi); +// cleans up after single channel +static void close_netlink_channel(struct netlink_channel *channel); + +// SCANNING + +// public interface - trigger scan if necessary, retrieve information about all known BSSes +int wifi_scan_all(struct wifi_scan *wifi, struct bss_info *bss_infos, int bss_infos_length); + +// SCANNING - notification related + +// the data needed from notifications +struct context_NL80211_MULTICAST_GROUP_SCAN +{ + int new_scan_results; //are new scan results waiting for us? + int scan_triggered; //was scan was already triggered by somebody else? +}; + +// read but do not block +static void read_past_notifications(struct netlink_channel *notifications); +// go non-blocking +static void set_channel_non_blocking(struct netlink_channel *channel); +// go back blocking +static void set_channel_blocking(struct netlink_channel *channel); +// this handles notifications +static int handle_NL80211_MULTICAST_GROUP_SCAN(const struct nlmsghdr *nlh, void *data); +// triggers scan if no results are waiting yet and if it was not already triggered +static int trigger_scan_if_necessary(struct netlink_channel *commands, struct context_NL80211_MULTICAST_GROUP_SCAN *scanning); +// triggers the scan +static int trigger_scan(struct netlink_channel *channel); +// wait for the notification that scan finished +static void wait_for_new_scan_results(struct netlink_channel *notifications); + +// SCANNING - scan related + +// the data needed from new scan results +struct context_NL80211_CMD_NEW_SCAN_RESULTS +{ + struct bss_info *bss_infos; + int bss_infos_length; + int scanned; +}; + +// get scan results cached by the driver +static int get_scan(struct netlink_channel *channel); +// process the new scan results +static int handle_NL80211_CMD_NEW_SCAN_RESULTS(const struct nlmsghdr *nlh, void *data); +// get the information about bss (nested attribute) +static void parse_NL80211_ATTR_BSS(struct nlattr *nested, struct netlink_channel *channel); +// get the information from IE (non-netlink binary data here!) +static void parse_NL80211_BSS_INFORMATION_ELEMENTS(struct nlattr *attr, char SSID_OUT[33]); +// get BSSID (mac address) +static void parse_NL80211_BSS_BSSID(struct nlattr *attr, uint8_t bssid_out[BSSID_LENGTH]); + +// STATION + +// data needed from command new station +struct context_NL80211_CMD_NEW_STATION +{ + struct station_info *station; +}; + +// public interface - get information about station we are associated with +int wifi_scan_station(struct wifi_scan *wifi,struct station_info *station); +// get information about station with BSSID +static int get_station(struct netlink_channel *channel, uint8_t bssid[BSSID_LENGTH]); +// process command new station +static int handle_NL80211_CMD_NEW_STATION(const struct nlmsghdr *nlh, void *data); +// process station info (nested attribute) +static void parse_NL80211_ATTR_STA_INFO(struct nlattr *nested, struct netlink_channel *channel); + +// NETLINK HELPERS + +// NETLINK HELPERS - message construction/sending/receiving + +// create the message with specified parameters for the channel +// fill the message with additional attributes as needed with: +// mnl_attr_put_[|u8|u16|u32|u64|str|strz] and mnl_attr_nest_[start|end] +static struct nlmsghdr *prepare_nl_message(uint32_t type, uint16_t flags, uint8_t genl_cmd, struct netlink_channel *channel); +// send the above message +static void send_nl_message(struct nlmsghdr *nlh, struct netlink_channel *channel); +// receive the results and process them using callback function +static int receive_nl_message(struct netlink_channel *channel, mnl_cb_t callback); + +// NETLINK HELPERS - validation + +// formal requirements for attribute +struct attribute_validation +{ + int attr; // attribute constant from nl80211.h + enum mnl_attr_data_type type; // MNL_TYPE_[UNSPEC|U8|U16|U32|U64|STRING|FLAG|MSECS|NESTED|NESTED_COMPAT|NUL_STRING|BINARY] + size_t len; // length in bytes, can be ommitted for attibutes of known size (e.g. U16), can be 0 if unspeciffied +}; + +// all information needed to validate attributes +struct validation_data +{ + struct nlattr **attribute_table; //validated attributes are returned here + int attribute_length; //at most that many, distinct constants from nl80211.h go here + const struct attribute_validation *validation; //vavildate against that table + int validation_length; +}; + +// data of type struct validation_data*, validate attr against data, this is called for each attribute +static int validate(const struct nlattr *attr, void *data); + +// GENNERAL PURPOSE + +// if anything goes wrong... +static void die(const char *s); +// as above but scream errno +static void die_errno(const char *s); + +// ##################################################################### +// IMPLEMENTATION + +// validate only what we are going to use, note that +// this lists all the attributes used by the library + +const struct attribute_validation NL80211_VALIDATION[]={ + {CTRL_ATTR_FAMILY_ID, MNL_TYPE_U16}, + {CTRL_ATTR_MCAST_GROUPS, MNL_TYPE_NESTED} }; + +const struct attribute_validation NL80211_MCAST_GROUPS_VALIDATION[]={ + {CTRL_ATTR_MCAST_GRP_ID, MNL_TYPE_U32}, + {CTRL_ATTR_MCAST_GRP_NAME, MNL_TYPE_STRING} }; + +const struct attribute_validation NL80211_BSS_VALIDATION[]={ + {NL80211_BSS_BSSID, MNL_TYPE_BINARY, 6}, + {NL80211_BSS_FREQUENCY, MNL_TYPE_U32}, + {NL80211_BSS_INFORMATION_ELEMENTS, MNL_TYPE_BINARY}, + {NL80211_BSS_STATUS, MNL_TYPE_U32}, + {NL80211_BSS_SIGNAL_MBM, MNL_TYPE_U32}, + {NL80211_BSS_SEEN_MS_AGO, MNL_TYPE_U32} }; + +const struct attribute_validation NL80211_NEW_SCAN_RESULTS_VALIDATION[]={ + {NL80211_ATTR_IFINDEX, MNL_TYPE_U32}, + {NL80211_ATTR_SCAN_SSIDS, MNL_TYPE_NESTED}, + {NL80211_ATTR_BSS, MNL_TYPE_NESTED} }; + +const struct attribute_validation NL80211_CMD_NEW_STATION_VALIDATION[]={ + {NL80211_ATTR_STA_INFO, MNL_TYPE_NESTED}, +}; + +const struct attribute_validation NL80211_STA_INFO_VALIDATION[]={ + {NL80211_STA_INFO_SIGNAL, MNL_TYPE_U8}, + {NL80211_STA_INFO_SIGNAL_AVG, MNL_TYPE_U8}, + {NL80211_STA_INFO_RX_PACKETS, MNL_TYPE_U32}, + {NL80211_STA_INFO_TX_PACKETS, MNL_TYPE_U32} +}; + +const int NL80211_VALIDATION_LENGTH=sizeof(NL80211_VALIDATION)/sizeof(struct attribute_validation); +const int NL80211_MCAST_GROUPS_VALIDATION_LENGTH=sizeof(NL80211_MCAST_GROUPS_VALIDATION)/sizeof(struct attribute_validation); +const int NL80211_BSS_VALIDATION_LENGTH=sizeof(NL80211_BSS_VALIDATION)/sizeof(struct attribute_validation); +const int NL80211_NEW_SCAN_RESULTS_VALIDATION_LENGTH=sizeof(NL80211_NEW_SCAN_RESULTS_VALIDATION)/sizeof(struct attribute_validation); +const int NL80211_CMD_NEW_STATION_VALIDATION_LENGTH=sizeof(NL80211_CMD_NEW_STATION_VALIDATION)/sizeof(struct attribute_validation); +const int NL80211_STA_INFO_VALIDATION_LENGTH=sizeof(NL80211_STA_INFO_VALIDATION)/sizeof(struct attribute_validation); + +// INITIALIZATION + +// public interface - pass wireless interface like wlan0 +struct wifi_scan *wifi_scan_init(const char *interface) +{ + struct wifi_scan *wifi=malloc(sizeof(struct wifi_scan)); + if(wifi==NULL) + die("Insufficient memory - malloc(sizeof(struct wifi_data)"); + + init_netlink_channel(&wifi->notification_channel, interface); + + struct context_CTRL_CMD_GETFAMILY family_context ={0}; + wifi->notification_channel.context=&family_context; + + if(get_family_and_scan_ids(&wifi->notification_channel) == -1) + die_errno("GetFamilyAndScanId failed"); + + if(family_context.id_NL80211_MULTICAST_GROUP_SCAN == 0) + die("No scan multicast group in generic netlink nl80211\n"); + + init_netlink_channel(&wifi->command_channel, interface); + wifi->command_channel.nl80211_id = wifi->notification_channel.nl80211_id; + + subscribe_NL80211_MULTICAST_GROUP_SCAN(&wifi->notification_channel, family_context.id_NL80211_MULTICAST_GROUP_SCAN); + + return wifi; +} + +// prerequisities: +// - proper interface, e.g. wlan0, wlan1 +static void init_netlink_channel(struct netlink_channel *channel, const char *interface) +{ + channel->sequence=1; + channel->buf=(char*) malloc(MNL_SOCKET_BUFFER_SIZE); + + if(channel->buf == NULL) + die("Insufficent memory for netlink socket buffer"); + + channel->ifindex=if_nametoindex(interface); + + if(channel->ifindex==0) + die_errno("Incorrect network interface"); + + channel->context=NULL; + + init_netlink_socket(channel); +} + +static void init_netlink_socket(struct netlink_channel *channel) +{ + channel->nl = mnl_socket_open(NETLINK_GENERIC); + + if (channel->nl == NULL) + die_errno("mnl_socket_open"); + + if (mnl_socket_bind(channel->nl, 0, MNL_SOCKET_AUTOPID) < 0) + die_errno("mnl_socket_bind"); +} + +// prerequisities: +// - channel initialized with init_netlink_channel +// - channel context of type context_CTRL_CMD_GETFAMILY +static int get_family_and_scan_ids(struct netlink_channel *channel) +{ + struct nlmsghdr *nlh=prepare_nl_message(GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, channel); + mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL); + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, NL80211_GENL_NAME); + + send_nl_message(nlh, channel); + + return receive_nl_message(channel, handle_CTRL_CMD_GETFAMILY); +} + +// prerequisities: +// - netlink_channel passed as data +// - data->context of type struct context_CTRL_CMD_GETFAMILY +static int handle_CTRL_CMD_GETFAMILY(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[CTRL_ATTR_MAX+1] = {}; + struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh); + struct netlink_channel *channel = (struct netlink_channel*)data; + struct validation_data vd={tb, CTRL_ATTR_MAX, NL80211_VALIDATION, NL80211_VALIDATION_LENGTH}; + + mnl_attr_parse(nlh, sizeof(*genl), validate, &vd); + + if (!tb[CTRL_ATTR_FAMILY_ID]) + die("No family id attribute"); + + channel->nl80211_id=mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + + if (tb[CTRL_ATTR_MCAST_GROUPS]) + parse_CTRL_ATTR_MCAST_GROUPS(tb[CTRL_ATTR_MCAST_GROUPS], channel); + + return MNL_CB_OK; +} + +// prerequisities: +// - data->context of type struct context_CTRL_CMD_GETFAMILY +static void parse_CTRL_ATTR_MCAST_GROUPS(struct nlattr *nested, struct netlink_channel *channel) +{ + struct nlattr *pos; + + mnl_attr_for_each_nested(pos, nested) + { + struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX+1] = {}; + struct validation_data vd={tb, CTRL_ATTR_MCAST_GRP_MAX, NL80211_MCAST_GROUPS_VALIDATION, NL80211_MCAST_GROUPS_VALIDATION_LENGTH}; + + mnl_attr_parse_nested(pos, validate, &vd); + + if ( tb[CTRL_ATTR_MCAST_GRP_NAME]) + { + const char *name=mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]); + + if( strcmp(name, "scan") == 0 ) + { + if (tb[CTRL_ATTR_MCAST_GRP_ID]) + { + struct context_CTRL_CMD_GETFAMILY *context=channel->context; + context->id_NL80211_MULTICAST_GROUP_SCAN= mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]); + } + else + die("Missing id attribute for scan multicast group"); + } + } + } +} + +// prerequisities: +// - channel initialized with init_netlink_channel +static void subscribe_NL80211_MULTICAST_GROUP_SCAN(struct netlink_channel *channel, uint32_t scan_group_id) +{ + if (mnl_socket_setsockopt(channel->nl, NETLINK_ADD_MEMBERSHIP, &scan_group_id, sizeof(int)) < 0) + die_errno("mnl_socket_set_sockopt"); +} + +// CLEANUP + +// prerequisities: +// - wifi initialized with wifi_scan_init +void wifi_scan_close(struct wifi_scan *wifi) +{ + close_netlink_channel(&wifi->notification_channel); + close_netlink_channel(&wifi->command_channel); + free(wifi); +} + +// prerequisities: +// - channel initalized with init_netlink-channel +static void close_netlink_channel(struct netlink_channel *channel) +{ + free(channel->buf); + mnl_socket_close(channel->nl); +} + + +// SCANNING + +// handle also trigger abort +// public interface +// +// prerequisities: +// - wifi initialized with wifi_scan_init +// - bss_info table of sized bss_info_length passed +int wifi_scan_all(struct wifi_scan *wifi, struct bss_info *bss_infos, int bss_infos_length) +{ + struct netlink_channel *notifications=&wifi->notification_channel; + struct context_NL80211_MULTICAST_GROUP_SCAN scanning={0,0}; + notifications->context=&scanning; + + struct netlink_channel *commands=&wifi->command_channel; + struct context_NL80211_CMD_NEW_SCAN_RESULTS scan_results = {bss_infos, bss_infos_length, 0}; + commands->context=&scan_results; + + //somebody else might have triggered scanning or even the results can be already waiting + read_past_notifications(notifications); + + //if no results yet or scan not triggered then trigger it. + //the device can be busy - we have to take it into account + if( trigger_scan_if_necessary(commands, &scanning) == -1) + return -1; //most likely with errno set to EBUSY + + //now just wait for trigger/new_scan_results + wait_for_new_scan_results(notifications); + + //finally read the scan + get_scan(commands); + + return scan_results.scanned; +} + +// SCANNING - notification related + +// prerequisities +// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN +// - context_NL80211_MULTICAST_GROUP_SCAN set for notifications +static void read_past_notifications(struct netlink_channel *notifications) +{ + set_channel_non_blocking(notifications); + int ret, run_ret; + + while( (ret = mnl_socket_recvfrom(notifications->nl, notifications->buf, MNL_SOCKET_BUFFER_SIZE) ) >= 0) + { + //the line below fills context about past scans/triggers + run_ret = mnl_cb_run(notifications->buf, ret, 0, 0, handle_NL80211_MULTICAST_GROUP_SCAN, notifications); + if(run_ret <= 0) + die_errno("ReadPastNotificationsNonBlocking mnl_cb_run failed"); + } + + if(ret == -1) + if( !(errno == EINPROGRESS || errno == EWOULDBLOCK) ) + die_errno("ReadPastNotificationsNonBlocking mnl_socket_recv failed"); + //no more notifications waiting + set_channel_blocking(notifications); +} + +// prerequisities +// - channel initialized with init_netlink_channel +static void set_channel_non_blocking(struct netlink_channel *channel) +{ + int fd = mnl_socket_get_fd(channel->nl); + int flags = fcntl(fd, F_GETFL, 0); + if(flags == -1) + die_errno("SetChannelNonBlocking F_GETFL"); + if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + die_errno("SetChannelNonBlocking F_SETFL"); +} + +// prerequisities +// - channel initialized with init_netlink_channel +static void set_channel_blocking(struct netlink_channel *channel) +{ + int fd = mnl_socket_get_fd(channel->nl); + int flags = fcntl(fd, F_GETFL, 0); + if(flags == -1) + die_errno("SetChannelNonBlocking F_GETFL"); + if( fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) + die_errno("SetChannelNonBlocking F_SETFL"); +} + +// prerequisities: +// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN +// - netlink_channel passed as data +// - data->context of type struct context_NL80211_MULTICAST_GROUP_SCAN +static int handle_NL80211_MULTICAST_GROUP_SCAN(const struct nlmsghdr *nlh, void *data) +{ + struct netlink_channel *channel=data; + struct context_NL80211_MULTICAST_GROUP_SCAN *context = channel->context; + + struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh); + +// printf("Got message type %d seq %d pid %d genl cmd %d \n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + if(genl->cmd == NL80211_CMD_TRIGGER_SCAN) + { + context->scan_triggered=1; +// printf("TRIGGER type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + return MNL_CB_OK; //do nothing for now + } + else if(genl->cmd == NL80211_CMD_NEW_SCAN_RESULTS) + { +// printf("NEW SCAN RESULTS type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + if(nlh->nlmsg_pid==0 && nlh->nlmsg_seq==0) + context->new_scan_results = 1; + return MNL_CB_OK; //do nothing for now + } + else + { + fprintf(stderr, "Ignoring generic netlink command type %u seq %u pid %u genl cmd %u\n",nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + return MNL_CB_OK; + } +} + + +// prerequisities: +// - commands initialized with init_netlink_channel +// - scanning updated with read_past_notifications +static int trigger_scan_if_necessary(struct netlink_channel *commands, struct context_NL80211_MULTICAST_GROUP_SCAN *scanning) +{ + if(!scanning->new_scan_results && !scanning->scan_triggered) + if(trigger_scan(commands) == -1) + return -1; //most likely errno set to EBUSY which means hardware is doing something else, try again later + return 0; +} + +// prerequisities: +// - channel initialized with init_netlink_channel +static int trigger_scan(struct netlink_channel *channel) +{ + struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_ACK, NL80211_CMD_TRIGGER_SCAN, channel); + mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex); + send_nl_message(nlh, channel); + return receive_nl_message(channel, handle_NL80211_CMD_NEW_SCAN_RESULTS); +} + +// prerequisities +// - channel initalized with init_netlink_channel +// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN +// - context_NL80211_MULTICAST_GROUP_SCAN set for notifications +static void wait_for_new_scan_results(struct netlink_channel *notifications) +{ + struct context_NL80211_MULTICAST_GROUP_SCAN *scanning=notifications->context; + int ret; + + while(!scanning->new_scan_results) + { + if ( (ret = mnl_socket_recvfrom(notifications->nl, notifications->buf, MNL_SOCKET_BUFFER_SIZE)) <=0 ) + die_errno("Waiting for new scan results failed - mnl_socket_recvfrom"); + + if ( (ret=mnl_cb_run(notifications->buf, ret, 0, 0, handle_NL80211_MULTICAST_GROUP_SCAN, notifications)) <=0 ) + die_errno("Processing notificatoins failed - mnl_cb_run"); + } +} + +// SCANNING - scan related + +// prerequisities: +// - channel initalized with init_netlink_channel +// - channel context of type context_NL80211_CMD_NEW_SCAN_RESULTS +static int get_scan(struct netlink_channel *channel) +{ + struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, NL80211_CMD_GET_SCAN, channel); + mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex); + + send_nl_message(nlh, channel); + return receive_nl_message(channel, handle_NL80211_CMD_NEW_SCAN_RESULTS); +} + +// prerequisities: +// - netlink_channel passed as data +// - data->context of type context_NL80211_CMD_NEW_SCAN_RESULTS +static int handle_NL80211_CMD_NEW_SCAN_RESULTS(const struct nlmsghdr *nlh, void *data) +{ + struct netlink_channel *channel=data; + struct nlattr *tb[NL80211_ATTR_MAX+1] = {}; + struct validation_data vd={tb, NL80211_ATTR_MAX, NL80211_NEW_SCAN_RESULTS_VALIDATION, NL80211_NEW_SCAN_RESULTS_VALIDATION_LENGTH}; + struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh); + +// printf("NSR type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + + if(genl->cmd != NL80211_CMD_NEW_SCAN_RESULTS) + { + fprintf(stderr, "Ignoring generic netlink command %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + return MNL_CB_OK; + } + + //seq 0 - notification from kernel, then pid should also 0, if it is result of our scan we have sequence and our pid +// int new_scan_results= nlh->nlmsg_seq != 0 && nlh->nlmsg_pid != 0; + + mnl_attr_parse(nlh, sizeof(*genl), validate, &vd); + + if(tb[NL80211_ATTR_IFINDEX]) + { + // uint32_t ifindex=mnl_attr_get_u32(tb[NL80211_ATTR_IFINDEX]); +// printf("ifindex %u\n", ifindex); + } + if (!tb[NL80211_ATTR_BSS]) + return MNL_CB_OK; + + parse_NL80211_ATTR_BSS(tb[NL80211_ATTR_BSS], channel); + + return MNL_CB_OK; +} + +// prerequisities: +// - channel context of type context_NL80211_CMD_NEW_SCAN_RESULTS +static void parse_NL80211_ATTR_BSS(struct nlattr *nested, struct netlink_channel *channel) +{ + struct nlattr *tb[NL80211_BSS_MAX+1] = {}; + struct validation_data vd={tb, NL80211_BSS_MAX, NL80211_BSS_VALIDATION, NL80211_BSS_VALIDATION_LENGTH}; + struct context_NL80211_CMD_NEW_SCAN_RESULTS *scan_results = channel->context; + struct bss_info *bss = scan_results->bss_infos + scan_results->scanned; + + mnl_attr_parse_nested(nested, validate, &vd); + + enum nl80211_bss_status status=BSS_NONE; + + if(tb[NL80211_BSS_STATUS]) + status=mnl_attr_get_u32(tb[NL80211_BSS_STATUS]); + + //if we have found associated station store first as last and associated as first + if(status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_IBSS_JOINED) + { + if(scan_results->scanned>0 && scan_results->scanned < scan_results->bss_infos_length) + memcpy(bss, scan_results->bss_infos, sizeof(struct bss_info)); + bss=scan_results->bss_infos; + } + + //check bounds, make exception if we have found associated station and replace previous data + if(scan_results->bss_infos_length == 0 || ( scan_results->scanned >= scan_results->bss_infos_length && bss != scan_results->bss_infos ) ) + { + ++scan_results->scanned; + return; + } + + if ( tb[NL80211_BSS_BSSID]) + parse_NL80211_BSS_BSSID(tb[NL80211_BSS_BSSID], bss->bssid); + + if ( tb[NL80211_BSS_FREQUENCY]) + bss->frequency = mnl_attr_get_u32(tb[NL80211_BSS_FREQUENCY]); + + if ( tb[NL80211_BSS_INFORMATION_ELEMENTS]) + parse_NL80211_BSS_INFORMATION_ELEMENTS(tb[NL80211_BSS_INFORMATION_ELEMENTS], bss->ssid); + + if ( tb[NL80211_BSS_SIGNAL_MBM]) + bss->signal_mbm=mnl_attr_get_u32(tb[NL80211_BSS_SIGNAL_MBM]); + + if ( tb[NL80211_BSS_SEEN_MS_AGO]) + bss->seen_ms_ago = mnl_attr_get_u32(tb[NL80211_BSS_SEEN_MS_AGO]); + + bss->status=status; + + ++scan_results->scanned; +} + +//This is guesswork! Read up on that!!! I don't think it's netlink in this attribute, some lower beacon layer +static void parse_NL80211_BSS_INFORMATION_ELEMENTS(struct nlattr *attr, char SSID_OUT[SSID_MAX_LENGTH_WITH_NULL]) +{ + const char *payload=mnl_attr_get_payload(attr); + int len=mnl_attr_get_payload_len(attr); + if(len==0 || payload[0]!=0 || payload[1] >= SSID_MAX_LENGTH_WITH_NULL || payload[1] > len-2) + { + fprintf(stderr, "SSID len 0 or payload not starting from 0 or payload length > 32 or payload length > length-2!\n"); + SSID_OUT[0]='\0'; + return; + } + int ssid_len=payload[1]; + strncpy(SSID_OUT, payload+2, ssid_len); + SSID_OUT[ssid_len]='\0'; +} + +static void parse_NL80211_BSS_BSSID(struct nlattr *attr, uint8_t bssid_out[BSSID_LENGTH]) +{ + const char *payload=mnl_attr_get_payload(attr); + int len=mnl_attr_get_payload_len(attr); + + if(len != BSSID_LENGTH) + { + fprintf(stderr, "BSSID length != %d, ignoring", BSSID_LENGTH); + memset(bssid_out, 0, BSSID_LENGTH); + return; + } + + memcpy(bssid_out, payload, BSSID_LENGTH); +} + +// STATION + +// public interface +// +// prerequisities: +// - wifi initialized with wifi_scan_init +int wifi_scan_station(struct wifi_scan *wifi,struct station_info *station) +{ + struct netlink_channel *commands=&wifi->command_channel; + struct bss_info bss; + + struct context_NL80211_CMD_NEW_SCAN_RESULTS scan_results = {&bss, 1, 0}; + commands->context=&scan_results; + get_scan(commands); + + if(scan_results.scanned==0) + return 0; + + struct context_NL80211_CMD_NEW_STATION station_results = {station}; + commands->context=&station_results; + get_station(commands, bss.bssid); + + memcpy(station->bssid, bss.bssid, BSSID_LENGTH); + memcpy(station->ssid, bss.ssid, SSID_MAX_LENGTH_WITH_NULL); + station->status=bss.status; + + return 1; +} + +// prerequisites: +// - channel initalized with init_netlink_channel +// - context_NL80211_CMD_NEW_STATION set for channel +static int get_station(struct netlink_channel *channel, uint8_t bssid[BSSID_LENGTH]) +{ + struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_ACK, NL80211_CMD_GET_STATION, channel); + mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex); + mnl_attr_put(nlh, NL80211_ATTR_MAC, BSSID_LENGTH, bssid); + send_nl_message(nlh, channel); + return receive_nl_message(channel, handle_NL80211_CMD_NEW_STATION); +} + +// prerequisities: +// - netlink_channel passed as data +// - data->context of type context_NL80211_CMD_NEW_STATION +static int handle_NL80211_CMD_NEW_STATION(const struct nlmsghdr *nlh, void *data) +{ + struct netlink_channel *channel=data; + struct nlattr *tb[NL80211_ATTR_MAX+1] = {}; + struct validation_data vd={tb, NL80211_ATTR_MAX, NL80211_CMD_NEW_STATION_VALIDATION, NL80211_CMD_NEW_STATION_VALIDATION_LENGTH}; + struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh); + + if(genl->cmd != NL80211_CMD_NEW_STATION) + { + fprintf(stderr, "Ignoring generic netlink command %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd); + return MNL_CB_OK; + } + + mnl_attr_parse(nlh, sizeof(*genl), validate, &vd); + + if(!tb[NL80211_ATTR_STA_INFO]) //or error, no statoin + return MNL_CB_OK; + + parse_NL80211_ATTR_STA_INFO(tb[NL80211_ATTR_STA_INFO], channel); + + return MNL_CB_OK; +} + +// prerequisities: +// - channel context of type context_NL80211_CMD_NEW_STATION +static void parse_NL80211_ATTR_STA_INFO(struct nlattr *nested, struct netlink_channel *channel) +{ + struct nlattr *tb[NL80211_STA_INFO_MAX+1] = {}; + struct validation_data vd={tb, NL80211_STA_INFO_MAX, NL80211_STA_INFO_VALIDATION, NL80211_STA_INFO_VALIDATION_LENGTH}; + struct context_NL80211_CMD_NEW_STATION *station_results = channel->context; + struct station_info *station= station_results->station; + + mnl_attr_parse_nested(nested, validate, &vd); + + if ( tb[NL80211_STA_INFO_SIGNAL]) + station->signal_dbm=(int8_t)mnl_attr_get_u8(tb[NL80211_STA_INFO_SIGNAL]); + if ( tb[NL80211_STA_INFO_SIGNAL_AVG]) + station->signal_avg_dbm=(int8_t)mnl_attr_get_u8(tb[NL80211_STA_INFO_SIGNAL_AVG]); + if (tb[NL80211_STA_INFO_RX_PACKETS]) + station->rx_packets=mnl_attr_get_u32(tb[NL80211_STA_INFO_RX_PACKETS]); + if (tb[NL80211_STA_INFO_TX_PACKETS]) + station->tx_packets=mnl_attr_get_u32(tb[NL80211_STA_INFO_TX_PACKETS]); +} + + +// NETLINK HELPERS + +// NETLINK HELPERS - message construction/sending/receiving + +// prerequisities: +// - channel initialized with init_netlink_channel +static struct nlmsghdr *prepare_nl_message(uint32_t type, uint16_t flags, uint8_t genl_cmd, struct netlink_channel *channel) +{ + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + + nlh = mnl_nlmsg_put_header(channel->buf); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = flags; + nlh->nlmsg_seq = channel->sequence; + + genl = (struct genlmsghdr*)mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd = genl_cmd; + genl->version = 1; + return nlh; +} + +// prerequisities: +// - prepare_nl_message called first +// - mnl_attr_put_xxx used if additional attributes needed +static void send_nl_message(struct nlmsghdr *nlh, struct netlink_channel *channel) +{ + if (mnl_socket_sendto(channel->nl, nlh, nlh->nlmsg_len) < 0) + die_errno("mnl_socket_sendto"); +} + +// prerequisities: +// - send_nl_message called first +// - prerequisities for callback matched +static int receive_nl_message(struct netlink_channel *channel, mnl_cb_t callback) +{ + int ret; + unsigned int portid = mnl_socket_get_portid(channel->nl); + + ret = mnl_socket_recvfrom(channel->nl, channel->buf, MNL_SOCKET_BUFFER_SIZE); + + while (ret > 0) + { + ret = mnl_cb_run(channel->buf, ret, channel->sequence, portid, callback, channel); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(channel->nl, channel->buf, MNL_SOCKET_BUFFER_SIZE); + } + + ++channel->sequence; + + return ret; +} + +// NETLINK HELPERS - validation + +// prerequisities: +// - data of type validation_data +static int validate(const struct nlattr *attr, void *data) +{ + struct validation_data *vd=data; + const struct nlattr **tb = (const struct nlattr**) vd->attribute_table; + int type = mnl_attr_get_type(attr) ,i; + +// printf("%d\n", type); + + if (mnl_attr_type_valid(attr, vd->attribute_length) < 0) + return MNL_CB_OK; + + for(i=0; i < vd->validation_length;++i) + if(type == vd->validation[i].attr) + { + int len=vd->validation[i].len; + if(len==0 && mnl_attr_validate(attr, vd->validation[i].type) < 0) + { + perror("mnl_attr_validate error"); + return MNL_CB_ERROR; + } + if(len != 0 && mnl_attr_validate2(attr, vd->validation[i].type, len) < 0) + { + perror("mnl_attr_validate error"); + return MNL_CB_ERROR; + } + } + + tb[type] = attr; + return MNL_CB_OK; +} + +// GENNERAL PURPOSE + +static void die(const char *s) +{ + fprintf(stderr, "%s", s); + fprintf(stderr, "\n"); + exit(1); +} + +static void die_errno(const char *s) +{ + perror(s); + exit(1); +} |