# Wifimngr

[wifimngr](https://dev.iopsys.eu/iopsys/wifimngr)

## Introduction

Wifimngr is a Linux userspace daemon that provides **Radio** and **Interface** objects for status and management of Wi-Fi.
In OpenWrt based systems, these Wi-Fi objects are available over UBUS.

Wifimngr reads a "wireless" configuration file (in UCI format [https://openwrt.org/docs/guide-user/network/wifi/basic]) and creates the appropriate objects.
The "wireless" configuration file should be OpenWrt compatible. This means that the "wifi-device" and "wifi-iface" sections in the configuration file represent the Wi-Fi Radios and Wi-Fi interfaces respectively.

The wifimngr objects can be used for fetching status and statistics of the Wi-Fi radio and interfaces and perform actions like sending Wi-Fi management frames, start/stop WPS, DPP etc.

Wifimngr is also responsible for passing the received Netlink events from the "libwifi" layer to the system's bus (f.e. UBUS in the Openwrt based systems).

The radio objects are named as **"wifi.radio.<radioname>"**, while the interface objects are **"wifi.ap.<ifname>"** or **"wifi.bsta.<ifanme>"** respectively for the AP and STA mode interfaces.

With the Example-1 `"wireless"` config below, wifimngr creates a `"wifi.radio.radio0"` object for the `"wifi-device"` section.

Example-1
---------

````bash
config wifi-device "radio0"
	option band 'a'
	option channel 'auto'
	:

````

For Example-2 config below, wifimngr creates `"wifi.ap.wlan0"` and `"wifi.bsta.wlan0-1"` objects for the `"wifi-iface"` 'ap' and 'sta' sections respectively.

Example-2:
---------

````bash
config wifi-iface
	option device 'radio0'
	option ifname 'wlan0'
	option mode 'ap'
	:

cofnig wifi-iface
	option device 'radio0'
	option ifname 'wlan0-1'
	option mode 'sta'
	:

````

NOTE: With 802.11be and the introduction of Multi-Link Device (MLD) interfaces, the `"wireless"` config may additionally contain one or more `"wifi-mld"` sections.
These MLD sections represent the Wi-Fi MLD interfaces.

Example-3:
---------

````bash
config wifi-iface
	option device 'radio0'
	option ifname 'wlan0'
	option mode 'ap'
	option mld 'mld1'
	:

cofnig wifi-iface
	option device 'radio1'
	option ifname 'wlan1'
	option mode 'ap'
	option mld 'mld1'
	:

config wifi-mld 'mld1'
	option ifname 'wlan-01'
	:

````

For the config in Example-3 above, wifimngr creates a `"wifi.apmld.wlan-01"` object for the Wi-Fi AP MLD interface "wlan-01".
Note the Wi-Fi MLD interface 'wlan-01' has two Affiliated AP interfaces - "wlan0" and "wlan1" from radios "radio0" and "radio1" respectively.
For MLD interface of type STA, wifimngr creates `"wifi.bstamld.<mld-ifname>"` object.

## Overview


### Configuration

The "wireless" configuration file should be in UCI format. See  <URL: Openwrt Wi-Fi UCI Config>
For more info on the UCI wireless configuration see [link](./docs/api/uci.wireless.md)

### Objects and APIs

The Wi-Fi radio, interface (and mld) objects alongwith the APIs supported by each is given below -

````bash
'wifi' @5dffe10f
        "status":{}
        "debug":{"level":"Integer"}

'wifi.radio.radio0\_band1' @b9359f1b
        "status":{}
        "stats":{}
        "get":{"param":"String"}
        "scan":{"ssid":"String","bssid":"String","channel":"Integer","opclass":"Integer"}
        "scan_ex":{"ssid":"Array","bssid":"String","opclass":"Array","channel":"Array"}
        "scanresults":{"bssid":"String","cache":"Boolean"}
        "autochannel":{"exclude_dfs":"Boolean","exclude_6ghz_non_psc":"Boolean","exclude_opclass":"Array","exclude_channels":"Array","exclude_bandwidth":"Array","channels":"Array","bandwidth":"Array"}
        "start_cac":{"channel":"Integer","bandwidth":"Integer","method":"Integer"}
        "stop_cac":{"channel":"Integer","bandwidth":"Integer","method":"Integer"}
        "add_iface":{"args":"Table","config":"Boolean"}
        "del_iface":{"ifname":"String","config":"Boolean"}
        "channels_info":{}
        "channels":{"bandwidth":"Integer"}
        "opclass_preferences":{}
        "simulate_radar":{"channel":"Integer","bandwidth":"Integer","type":"Integer","subband_mask":"Integer"}

'wifi.ap.wlan01-1' @86d7fe82
        "status":{}
        "stats":{}
        "assoclist":{}
        "stations":{"sta":"String"}
        "disconnect":{"sta":"String","reason":"Integer"}
        "probe_sta":{"sta":"String"}
        "monitor_add":{"sta":"String"}
        "monitor_del":{"sta":"String"}
        "monitor_get":{"sta":"String"}
        "subscribe_frame":{"type":"Integer","stype":"Integer"}
        "unsubscribe_frame":{"type":"Integer","stype":"Integer"}
        "add_neighbor":{"bssid":"String","channel":"Integer","bssid_info":"String","reg":"Integer","phy":"Integer"}
        "del_neighbor":{"bssid":"String"}
        "list_neighbor":{"ssid":"String","client":"String"}
        "request_neighbor":{"client":"String","opclass":"Integer","channel":"Integer","duration":"Integer","mode":"String","bssid":"String","reporting_detail":"Integer","ssid":"String","channel_report":"Array","request_element":"Array"}
        "request_btm": {"sta":"String","target_ap":"Array","mode":"Integer","disassoc_tmo":"Integer","validity_int":"Integer","dialog_token":"Integer","bssterm_dur":"Integer","mbo_reason":"Integer","mbo_cell_pref":"Integer","mbo_reassoc_delay":"Integer"}
        "request_transition":{"client":"String","bssid":"Array","timeout":"Integer"}
        "assoc_control":{"client":"Array","enable":"Integer"}
        "add_vendor_ie":{"mgmt":"Integer","oui":"String","data":"String"}
        "del_vendor_ie":{"mgmt":"Integer","oui":"String","data":"String"}
        "dump_beacon":{}
        "chan_switch":{"count":"Integer","channel":"Integer","freq":"Integer","sec_chan_offset":"Integer","cf1":"Integer","cf2":"Integer","bw":"Integer","blocktx":"Boolean","ht":"Boolean","vht":"Boolean","he":"Boolean","eht":"Boolean","auto-ht":"Boolean"}
        "measure_link":{"sta":"String"}
        "mbo_disallow_assoc":{"reason":"Integer"}
        "up":{}
        "down":{}
        "send_action":{"dst":"String","wait":"Integer","freq":"Integer","frame":"String","src":"String"}
        "set_qos_map":{"set":"Array"}
        "send_qos_map_conf":{"sta":"String"}

'wifi.apmld.wlan1' @eb47f8ed
        "status":{}
        "stats":{}
        "assoclist":{}
        "stations":{"sta":"String"}
        "disconnect":{"sta":"String","reason":"Integer"}

'wifi.wps' @4eb8cbd8
	"start":{"ifname":"String","mode":"String","role":"String","pin":"String"}
	"stop":{}
	"status":{"ifname":"String"}
	"generate_pin":{}
	"validate_pin":{"pin":"String"}
	"showpin":{"ifname":"String"}
	"setpin":{"ifname":"String","pin":"String"}
````

For more info on the Ubus API see [function specification](./docs/functionspec.md) or go directly to the generated [docs](./docs/ubus.splash.md).

**Table 1. WiFi events over UBUS**

Event | Event Data
------|-----------
wifi.radio |_{"ifname":"wlan0","event":"ap-disabled", "data": {"bssid":"00:20:07:11:22:33}}_|
wifi.radio |_{"ifname":"wlan0","event":"ap-enabled", "data": {"bssid":"00:20:07:11:22:33"}}_|
wifi.radio |_{"ifname":"wlan1","event":"cac-start","data":{"channel":"52","bandwidth":80,"cac_time":60}}_|
wifi.radio |_{"ifname":"wlan1","event":"cac-end","data":{"channel":"52","bandwidth":80,"success":1}}_|
wifi.radio |_{"ifname":"wlan1","event":"ap-csa-finished","data":{"freq":"2412", "bandwidth":80}}_|
wifi.radio |_{"ifname":"wlan1","event":"radar","data":{"channel":"52","bandwidth":80}}_|
wifi.radio |_{"ifname":"wlan1","event":"nop-end","data":{"channel":"52", "bandwidth":80}}_|
wifi.radio |_{"ifname":"wlan1","event":"acs-start"}_|
wifi.radio |_{"ifname":"wlan1","event":"acs-end","data":{"channel":"149", "bandwidth":80}}_|
wifi.radio |_{"ifname":"wlan1","event":"acs-failed"}_|
||
||
wifi.iface |_{"ifname":"wlan1","event":"up"}_|
wifi.iface |_{"ifname":"wlan1","event":"down"}_|
wifi.iface |_{"ifname":"wlan1","event":"created", "data":{"type":"sta|ap|monitor", "macaddr":"00:10:20:33:44:55"}}_|
wifi.iface |_{"ifname":"wlan1","event":"deleted" }_|
wifi.iface |_{"ifname":"wlan1","event":"wps-start", "data":{"method":"pbc"}}_|
wifi.iface |_{"ifname":"wlan1","event":"wps-stop", "data":{"method":"pbc"}}_|
wifi.iface |_{"ifname":"wlan1","event":"wps-success"}_|
wifi.iface |_{"ifname":"wlan1","event":"wps-success", "data": {"credentials": {"..."}}}_|
wifi.iface |_{"ifname":"wlan1","event":"wps-timeout"}_|
wifi.iface |_{"ifname":"wlan1","event":"wps-overlap"}_|
wifi.iface |_{"ifname":"wlan0","event":"frame-rx","data":{"hexstring":"00003a01...0100"}}_|
||
||
wifi.sta |_{"ifname":"wlan1","event":"connected","data":{"macaddr":"44:d4:37:42:47:bf", "bssid":"00:20:07:11:22:33"}}_|
wifi.sta |_{"ifname":"wlan1","event":"disconnected","data":{"macaddr":"44:d4:37:42:47:bf", "bssid":"00:20:07:11:22:33"}}_|
wifi.sta |_{"ifname":"wlan1","event":"wds-station-added","data":{"ifname":"wlan1.sta1","macaddr":"44:d4:37:42:47:bf"}}_|
wifi.sta |_{"ifname":"wlan1","event":"wds-station-removed","data":{"ifname":"wlan1.sta1","macaddr":"44:d4:37:42:47:bf"}}_|
wifi.sta |_{"ifname":"wlan1","event":"btm-resp","data":{"macaddr":"30:10:b3:6d:8d:ba","status":"0","target_bssid":"44:d4:37:42:1f:16"}}_|
wifi.sta |_{"ifname":"wlan0","event":"btm-resp","data":{"macaddr":"30:10:b3:6d:8d:ba","status":"1"}}_|
wifi.sta |_{"ifname":"wlan0","event":"probe-req","data":{"macaddr":"44:d4:37:42:47:be","rssi":-35}}_|
wifi.sta |_{"ifname":"wlan0","event":"bcn-resp","data":{"macaddr":"44:d4:37:42:47:be","status":1}}_|
||
wifi.sta |_{"ifname":"wlan0","event":"bcn-report","data":{"macaddr":"30:10:b3:6d:8d:ba","token":"1","mode":"00","nbr":[{"bssid":"44:d4:37:42:1f:16","channel":"11","op_class":"81","phy_type":"7","rcpi":"148","rsni":"255"}]}}_|
---------------


### Wifi Statistics

The `wifi` object publishes gathered radio and interface statistics.

```
'wifi' @f2f72310
	"status":{}
```

For info on the `wifi` API see [link](./docs/api/wifi.md#wifi)

### Access Point

An object will be published on ubus for each interface operating as an access point, managing operations and data for wifi clients, neighbor nodes and interface statistics.

To parse access point interfaces, wifimngr reads the wireless configuration file (`/etc/config/wireless`), looking for `wifi-iface` sections, with the option `mode` specified as `ap`.

```
config wifi-iface
	option device 'test5'
	option ifname 'test5'
	option mode 'ap'
```

Wifimngr will query libwifi to find available API calls for the driver, in order to dynamically publish available methods to ubus.

```
root@eagle:~# ubus -v list wifi.ap.test5
'wifi.ap.test5' @a939a75c
	"status":{}
	"stats":{}
	"assoclist":{}
	"stations":{"sta":"String"}
	"disconnect":{"sta":"String","reason":"Integer"}
	"probe_sta":{"sta":"String"}
	"monitor_add":{"sta":"String"}
	"monitor_del":{"sta":"String"}
	"monitor_get":{"sta":"String"}
	"subscribe_frame":{"type":"Integer","stype":"Integer"}
	"unsubscribe_frame":{"type":"Integer","stype":"Integer"}
	"add_neighbor":{"bssid":"String","channel":"Integer","bssid_info":"String","reg":"Integer","phy":"Integer"}
	"del_neighbor":{"bssid":"String"}
	"list_neighbor":{"ssid":"String","client":"String"}
	"request_neighbor":{"client":"String","opclass":"Integer","channel":"Integer","duration":"Integer","mode":"String","bssid":"String","reporting_detail":"Integer","ssid":"String","channel_report":"Array","request_element":"Array"}
	"request_transition":{"client":"String","bssid":"Array","timeout":"Integer"}
	"assoc_control":{"client":"Array","enable":"Integer"}
	"add_vendor_ie":{"mgmt":"Integer","oui":"String","data":"String"}
	"del_vendor_ie":{"mgmt":"Integer","oui":"String","data":"String"}
	"dump_beacon":{}
	"chan_switch":{"count":"Integer","freq":"Integer","sec_chan_offset":"Integer","cf1":"Integer","cf2":"Integer","bw":"Integer","blocktx":"Boolean","ht":"Boolean","vht":"Boolean"}
	"measure_link":{"sta":"String"}
	"mbo_disallow_assoc":{"reason":"Integer"}
	"up":{}
	"down":{}
```

For info on the access point API see [link](./docs/api/wifi.ap.md#wifiapname)

### Radio

Similarily to access point interfaces, wifimngr will publish one object per available radio, parsed from the wireless configuration, by `wifi-device` sections. The methods are added dynamically by quering for supported API calls from libwifi.

```
config wifi-device 'test5'
	option channel 'auto'
	option hwmode 'auto'
	option country 'DE'
	option band 'a'
	option bandwidth '80'
```

The radio object manages radio data and statistics, channel selection and wifi scan operations.

```
root@:/opt/work# ubus -v list wifi.radio.test5
'wifi.radio.test5' @fa7d185d
	"status":{}
	"stats":{}
	"get":{"param":"String"}
	"scan":{"ssid":"String","bssid":"String","channel":"Integer"}
	"scanresults":{"bssid":"String"}
	"autochannel":{"interval":"Integer","algo":"Integer","scans":"Integer"}
```
For info on the radio API see [link](./docs/api/wifi.radio.md#wifiradioname)

### Wifi Protected Setup

Lastly, wifimngr exposes WPS functionality to ubus.

The configuration is read from the `/etc/config/wireless`. To enable WPS the `wps` option has to be set to `1`.

```
config wifi-iface
	option wps '1'
```

To see more configuration options see [link](./docs/api/uci.wireless.md), under the `wifi-iface` section.


```
root@:/opt/work# ubus -v list wifi.wps
'wifi.wps' @78c52f3b
	"start":{"ifname":"String","mode":"String","role":"String","pin":"String"}
	"stop":{}
	"status":{"ifname":"String"}
	"generate_pin":{}
	"validate_pin":{"pin":"String"}
	"showpin":{"ifname":"String"}
	"setpin":{"ifname":"String","pin":"String"}
```

For info on the WPS API see [link](./docs/api/wifi.wps.md#wifiwps)


## Tests

This section will give a brief overview of the tests for wifimngr, for a more detailed report see [test specification](./docs/testspec.md)

To test wifimngr, the scope of the tests has to be clearly defined, as wifimngr is heavily dependent on libwifi:

1. Verify linkage between wifimngr and libwifi APIs
2. Verify that the wifi structures are correctly prepared and return data is used correctly by wifimngr
3. Verify that API calls successfully reach libwifi and if passed input is of correct format

As the test environment runs in a ubuntu 16.04 docker environment, with little possibility to prepare wifi drivers and kernel version, the easy-soc-libs had to be extended to support APIs to run on a test platform, returning dummy data for getters, and for setters, logging that the call made with timestamp and arguments.

To ensure full coverage, the getters are tested by the ubus-api-validation tool, using json-schemas to validate accurate output. The setters are tested by cmocka tests, creating a libwifimngr.so shared library, invoking its ubus API functions directly, verifying that the call went through, and that the arguments were passed correctly, by parsing the test log file found at `/tmp/test.log`.

Additionally, the cmocka tests verify that netlink event callbacks are
registered and invoked.

## Dependencies ##

To successfully build wifimngr, the following libraries are needed:

| Dependency  		| Link                                       						| License        |
| ----------------- | ---------------------------------------------------------------- 	| -------------- |
| libuci      		| https://git.openwrt.org/project/uci.git     					 	| LGPL 2.1       |
| libubox     		| https://git.openwrt.org/project/libubox.git 					 	| BSD            |
| libubus     		| https://git.openwrt.org/project/ubus.git    					 	| LGPL 2.1       |
| libjson-c   		| https://s3.amazonaws.com/json-c_releases    					 	| MIT            |
| libwifi	  		| https://dev.iopsys.eu/iopsys/easy-soc-libs/tree/devel/libwifi    	| GNU GPL2       |
| libnl3	  		| 											 					 	| 			     |
| libblobmsg_json	| 											 					 	| 			     |
| libnl-genl  		|                                             					 	|                |

Additionally, in order to build with the tests, the following libraries are needed:

| Dependency  				| Link                                       				| License		|
| ------------------------- | --------------------------------------------------------- | ------------- |
| libjson-schema-validator 	| https://github.com/pboettch/json-schema-validator     	| LGPL 2.1		|
| libjson-validator			| https://dev.iopsys.eu/iopsys/json-schema-validator		| 				|
| libjson-editor			| https://dev.iopsys.eu/iopsys/json-editor   				| 				|

