diff options
author | Paul Oliver <contact@pauloliver.dev> | 2025-05-06 18:30:39 +0200 |
---|---|---|
committer | Paul Oliver <contact@pauloliver.dev> | 2025-05-06 21:29:05 +0200 |
commit | 965bc45937fdfe2a20bc284c3ccc590d01eee389 (patch) | |
tree | e471c8a5da2f09b069dd77f09515b32efbf52f3d |
Initial
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rwxr-xr-x | build.sh | 11 | ||||
-rw-r--r-- | miniwifi.c | 134 | ||||
-rw-r--r-- | wifi_scan.c | 897 | ||||
-rw-r--r-- | wifi_scan.h | 119 |
6 files changed, 1173 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12fbb72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +miniwifi diff --git a/README.md b/README.md new file mode 100644 index 0000000..466f7dd --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Miniwifi + +Tiny wireless access status monitoring app, outputs assigned IP and SSID +attached to wlan0 each second. Output color changes depending on whether an +internet connection is available. + +`wifi-scan` library was copied from +[here](https://github.com/bmegli/wifi-scan/tree/master). + +Meant to be used with status bar apps like i3-blocks. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c14c447 --- /dev/null +++ b/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +if [ "${1}" = "DEBUG" ] ; then + gcc -c -ggdb wifi_scan.c -o wifi_scan.o + gcc -c -ggdb -DDEBUG -W -Wextra miniwifi.c -o miniwifi.o +else + gcc -c -O3 wifi_scan.c -o wifi_scan.o + gcc -c -O3 -W -Wextra miniwifi.c -o miniwifi.o +fi + +gcc wifi_scan.o miniwifi.o -lmnl -o miniwifi diff --git a/miniwifi.c b/miniwifi.c new file mode 100644 index 0000000..b85c38a --- /dev/null +++ b/miniwifi.c @@ -0,0 +1,134 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <sys/ioctl.h> + +#include "wifi_scan.h" + +#define COLOR_CONNECTED "#859900" +#define COLOR_DISCONNECTED "#dc322f" +#define ICON_CONNECTED "f05a9" +#define ICON_DISCONNECTED "f16bc" + +int main() { + const char *ip_addr = NULL; + const char *ssid = NULL; + const char *color = NULL; + const char *icon = NULL; + const char *fmt = "<span color=\"%s\"><span font=\"Symbols Nerd Font\">&#x%s;</span> %s %s</span>\n"; + +#ifdef DEBUG + for (int i = 0; true; ++i) { +#else + for (;;) { +#endif + +#ifdef DEBUG + printf("\nloop #%d:\n", ++i); +#endif + + color = COLOR_DISCONNECTED; + icon = ICON_DISCONNECTED; + + // check if wlan0 has an IP address + struct ifreq ifr; + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ - 1); + + int station_fd = socket(AF_INET, SOCK_DGRAM, 0); + int ioctl_res = ioctl(station_fd, SIOCGIFADDR, &ifr); + +#ifdef DEBUG + if (ioctl_res < 0) { + perror("ioctl()"); + } +#endif + + close(station_fd); + ip_addr = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); + +#ifdef DEBUG + printf("ip_addr set to: %s\n", ip_addr); +#endif + + if (ioctl_res != 0 || strcmp(ip_addr, "0.0.0.0") == 0) { + ssid = "-"; + ip_addr = "-"; + +#ifdef DEBUG + printf("ioctl() failed or ip set to 0:0:0:0, ssid set to: %s, ip_addr set to: %s\n", ssid, ip_addr); +#endif + } else { + // get ssid in case we have an IP + struct station_info station; + wifi_scan_station(wifi_scan_init("wlan0"), &station); + ssid = station.ssid; + +#ifdef DEBUG + printf("ssid set to: %s\n", ssid); +#endif + + // lastly, check if we can ping google + struct sockaddr_in sock_in; + sock_in.sin_family = AF_INET; + sock_in.sin_port = htons(443); + inet_pton(AF_INET, "8.8.8.8", &sock_in.sin_addr); + + int remote_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + int connect_res = connect(remote_fd, (struct sockaddr *)&sock_in, sizeof(sock_in)); + +#ifdef DEBUG + if (connect_res < 0) { + perror("connect()"); + } +#endif + + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(remote_fd, &fdset); + + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + int select_res = select(remote_fd + 1, NULL, &fdset, NULL, &tv); + +#ifdef DEBUG + if (select_res < 0) { + perror("select()"); + } +#endif + + if (select_res == 1) { +#ifdef DEBUG + printf("select() returned 1\n"); +#endif + + int so_error; + socklen_t len = sizeof so_error; + getsockopt(remote_fd, SOL_SOCKET, SO_ERROR, &so_error, &len); + + // we have a good connection + if (so_error == 0) { +#ifdef DEBUG + printf("so_error set to 0, we must have internet\n"); +#endif + + color = COLOR_CONNECTED; + icon = ICON_CONNECTED; + } + } + + close(remote_fd); + } + + // print results +#ifndef DEBUG + printf(fmt, color, icon, ssid, ip_addr); +#endif + fflush(stdout); + sleep(1); + } + + return 0; +} 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); +} diff --git a/wifi_scan.h b/wifi_scan.h new file mode 100644 index 0000000..95a5550 --- /dev/null +++ b/wifi_scan.h @@ -0,0 +1,119 @@ +/* + * wifi-scan library header + * + * 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/. + * + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +// some constants - mac address length, mac adress string length, max length of wireless network id with null character +enum wifi_constants {BSSID_LENGTH=6, BSSID_STRING_LENGTH=18, SSID_MAX_LENGTH_WITH_NULL=33}; +// anything >=0 should mean that your are associated with the station +enum bss_status{BSS_NONE=-1, BSS_AUTHENTHICATED=0, BSS_ASSOCIATED=1, BSS_IBSS_JOINED=2}; + +// internal data used by the functions +struct wifi_scan; + +// a single wireless network can have multiple BSSes working as network under one SSID +struct bss_info +{ + uint8_t bssid[BSSID_LENGTH]; //this is hardware mac address of your AP + uint32_t frequency; // this is AP frequency in mHz + char ssid[SSID_MAX_LENGTH_WITH_NULL]; //this is the name of your AP as you see it when connecting + enum bss_status status; //anything >=0 means that your are connected to this station/network + int32_t signal_mbm; //signal strength in mBm, divide it by 100 to get signal in dBm + int32_t seen_ms_ago; //when the above information was collected +}; + +// like above +struct station_info +{ + uint8_t bssid[BSSID_LENGTH]; //this is hardware mac address of your AP + char ssid[SSID_MAX_LENGTH_WITH_NULL]; //this is the name of your AP as you see it when connecting + enum bss_status status; //anything >=0 means that your are connected to this station/network + int8_t signal_dbm; //signal strength in dBm from last received PPDU + int8_t signal_avg_dbm; //signal strength average in dBm + uint32_t rx_packets; //the number of received packets + uint32_t tx_packets; //the number of transmitted packets +}; + +/* Initializes the library + * + * If this functions fails the library will die with error message explaining why + * + * parameters: + * interface - wireless interface, e.g. wlan0, wlan1 + * + * returns: + * struct wifi_scan * - pass it to all the functions in the library + * + */ +struct wifi_scan *wifi_scan_init(const char *interface); + +/* Frees the resources used by library + * + * parameters: + * wifi - library data initialized with wifi_scan_init + * + * preconditions: + * wifi initialized with wifi_scan_init + */ +void wifi_scan_close(struct wifi_scan *wifi); + +/* Retrieve information about station you are associated to + * + * Retrieves information only about single station. + * This function can be called repeateadly fast. + * + * parameters: + * wifi - library data initialized with wifi_scan_init + * station - to be filled with information + * + * returns: + * -1 on error (errno is set), 0 if not associated to any station, 1 if data was retrieved + * + * preconditions: + * wifi initialized with wifi_scan_init + * + */ +int wifi_scan_station(struct wifi_scan *wifi, struct station_info *station); + +/* Make a passive scan of all networks around. + * + * This function triggers passive scan if necessery, waits for completion and returns the data. + * If some other scan was triggered in the meanwhile the library will collect it's results. + * Triggering a scan requires permissions, for testing you may use sudo. + * + * Scanning may take some time (it can be order of second). + * While scanning the link may be unusable for other programs! + * + * parameters: + * wifi - library data initialized with wifi_scan_init + * bss_infos - array of bss_info of size bss_infos_length + * bss_infos_length - the length of passed array + * + * returns: + * -1 on error (errno is set) or the number of found BSSes, the number may be greater then bss_infos_length + * + * Some devices may fail with -1 and errno=EBUSY if triggering scan when another scan is in progress. You may wait and retry in that case + * + * preconditions: + * wifi initialized with wifi_scan_init + * + */ +int wifi_scan_all(struct wifi_scan *wifi, struct bss_info *bss_infos, int bss_infos_length); + +#ifdef __cplusplus +} +#endif |