diff --git a/repos/dde_linux/include/wifi/ctrl.h b/repos/dde_linux/include/wifi/ctrl.h index 5cc748aa47..df95f66cb5 100644 --- a/repos/dde_linux/include/wifi/ctrl.h +++ b/repos/dde_linux/include/wifi/ctrl.h @@ -14,33 +14,90 @@ #ifndef _WIFI__CTRL_H_ #define _WIFI__CTRL_H_ -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ +#include -#define WPA_CTRL_FD 51 +namespace Wifi { -struct Msg_buffer + /* + * FD used to poll CTRL state from the supplicant. + */ + enum { CTRL_FD = 51, }; + + struct Msg_buffer; + + struct Notify_interface : Genode::Interface + { + virtual void submit_response() = 0; + virtual void submit_event() = 0; + virtual void block_for_processing() = 0; + }; + + void ctrl_init(Msg_buffer &); + +} /* namespace Wifi */ + + +struct Wifi::Msg_buffer { - unsigned char recv[4096*8]; - unsigned char send[4096]; - unsigned recv_id; + char send[4096]; unsigned send_id; - unsigned char event[1024]; + + char recv[4096*8]; + unsigned recv_id; + unsigned last_recv_id; + + char event[1024]; unsigned event_id; -} __attribute__((packed)); + unsigned last_event_id; + + Notify_interface &_notify; + + Msg_buffer(Notify_interface ¬ify) + : _notify { notify } { } + + /* + * Member functions below are called by the + * CTRL interface. + */ + + void notify_response() const { + _notify.submit_response(); } + + void notify_event() const { + _notify.submit_event(); } + + void block_for_processing() { + _notify.block_for_processing(); } + + /* + * Member functions below are called by the + * Manager. + */ + + void with_new_reply(auto const &fn) + { + char const *msg = reinterpret_cast(recv); + /* return early */ + if (last_recv_id == recv_id) + return; + + last_recv_id = recv_id; + fn(msg); + } + + void with_new_event(auto const &fn) + { + char const *msg = reinterpret_cast(event); + + if (last_event_id == event_id) + return; + + last_event_id = event_id; + fn(msg); + } +}; void wpa_ctrl_set_fd(void); -void *wifi_get_buffer(void); -void wifi_notify_cmd_result(void); -void wifi_block_for_processing(void); -void wifi_notify_event(void); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - #endif /* _WIFI__CTRL_H_ */ diff --git a/repos/dde_linux/include/wifi/rfkill.h b/repos/dde_linux/include/wifi/rfkill.h index 641b19d8be..ceb1824046 100644 --- a/repos/dde_linux/include/wifi/rfkill.h +++ b/repos/dde_linux/include/wifi/rfkill.h @@ -36,12 +36,20 @@ namespace Wifi { bool rfkill_blocked(); /** - * Set RFKILL state from the frontend + * Set RFKILL state from the manager * * May be only called from an EP context. */ void set_rfkill(bool); + /** + * Trigger RFKILL notification signal + * + * Used by the supplicants RFKILL driver to notify + * the management-layer. + */ + void rfkill_notify(); + } /* namespace Wifi */ #endif /* _WIFI__RFKILL_H_ */ diff --git a/repos/dde_linux/lib/mk/wpa_supplicant.mk b/repos/dde_linux/lib/mk/wpa_supplicant.mk index a7352ccf73..8d7f0eb40e 100644 --- a/repos/dde_linux/lib/mk/wpa_supplicant.mk +++ b/repos/dde_linux/lib/mk/wpa_supplicant.mk @@ -10,7 +10,8 @@ CC_OPT += -Wno-unused-function CC_CXX_OPT += -fpermissive -SRC_C += main.c ctrl_iface_genode.c +SRC_C += main.c +SRC_CC += ctrl_iface_genode.cc INC_DIR += $(REP_DIR)/include diff --git a/repos/dde_linux/lib/symbols/wifi b/repos/dde_linux/lib/symbols/wifi index f76556ecbb..acde1609b6 100644 --- a/repos/dde_linux/lib/symbols/wifi +++ b/repos/dde_linux/lib/symbols/wifi @@ -22,6 +22,7 @@ wifi_ifname T _ZN4Wifi20firmware_get_requestEv T _ZN4Wifi26firmware_establish_handlerERNS_24Firmware_request_handlerE T _ZN4Wifi10set_rfkillEb T +_ZN4Wifi13rfkill_notifyEv T _ZN4Wifi14rfkill_blockedEv T _ZN4Wifi24rfkill_establish_handlerERNS_27Rfkill_notification_handlerE T convert_errno_from_linux T diff --git a/repos/dde_linux/patches/wpa_supplicant.patch b/repos/dde_linux/patches/wpa_supplicant.patch index 814ee9a879..06c0651934 100644 --- a/repos/dde_linux/patches/wpa_supplicant.patch +++ b/repos/dde_linux/patches/wpa_supplicant.patch @@ -93,7 +93,7 @@ The nl80211 driver patch contains the following changes: goto err; - global->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0); -+ global->ioctl_sock = 42; ++ global->ioctl_sock = 12345; /* arbitrarily chosen number b/c it won't be used anyway */ if (global->ioctl_sock < 0) { wpa_printf(MSG_ERROR, "nl80211: socket(PF_INET,SOCK_DGRAM) failed: %s", strerror(errno)); diff --git a/repos/dde_linux/ports/wpa_supplicant.hash b/repos/dde_linux/ports/wpa_supplicant.hash index 6c08e34884..bd65b206b1 100644 --- a/repos/dde_linux/ports/wpa_supplicant.hash +++ b/repos/dde_linux/ports/wpa_supplicant.hash @@ -1 +1 @@ -aa7af61abdac82e5a783d8371be9cb8d1a41fceb +15974e67af9112d919f1625a5c2ed2d996cdbdfb diff --git a/repos/dde_linux/run/nic_router_uplinks.run b/repos/dde_linux/run/nic_router_uplinks.run index 77fe902d95..a17f051731 100644 --- a/repos/dde_linux/run/nic_router_uplinks.run +++ b/repos/dde_linux/run/nic_router_uplinks.run @@ -133,8 +133,8 @@ install_config { - - + + diff --git a/repos/dde_linux/src/driver/wifi/README b/repos/dde_linux/src/driver/wifi/README index d964b409b9..1527e0c724 100644 --- a/repos/dde_linux/src/driver/wifi/README +++ b/repos/dde_linux/src/driver/wifi/README @@ -1,5 +1,5 @@ -The wifi component is a port of the Linux mac802.11 stack as well as -libnl and wpa_supplicant to Genode. Depending on the used platform it +The wifi component consists of a port of the Linux mac802.11 stack as well +as libnl and wpa_supplicant to Genode. Depending on the used platform it features a selection of drivers for wireless devices. For example on the PC platform it contains the ath9k, iwlwifi and rtlwifi drivers for PCI(e) devices. @@ -10,14 +10,18 @@ The 'wifi' binary is the generic management part that includes the Wifi configuration interface and the 'wpa_supplicant'. A suitable driver library is loaded at run-time (see section [Debugging]). -To start the component on the PC platform the following configuration snippet -can be used: + +Configuration +~~~~~~~~~~~~~ + +This configuration snippet shows how to start the component on the PC +platform. ! ! ! ! -! +! ! ! ! @@ -38,14 +42,14 @@ can be used: ! ! -On other platforms the wifi library will be different. The following -snippet illustrates the use of the driver on the PinePhone: +On other platforms the wifi library will be different. So, the +following snippet illustrates the use of the driver on the PinePhone. ! ! ! ! -! +! ! ! ! @@ -79,55 +83,95 @@ directory in the driver's local VFS. It is up to the configuration how those files are made available. In these examples they are contained in an '.tar' archive that is request as a ROM module. -The driver will request access to the ROM module 'wifi_config' to -connect to a network: +The driver will request access to the 'wifi_config' ROM module that +contains its actual configuration in the '' node. This +node features the following attributes. -! -! -! +* :scan_interval: sets the time interval in seconds in which scan + operations are requested and is used when not already connected + to a network. The default is 5 seconds. -To temporarily prevent any radio activity, the 'rfkill' attribute -can be set to 'true'. +* :update_quality_interval: sets the time interval in which the current + signal quality of the connected access point is updated (RSSI polling). + The default value is 30 seconds. -If the network is protected by, e.g., WPA/WPA2/WPA3, the protection type, -either 'WPA', 'WPA2' or 'WPA3' as well as the the passphrase have to be -specified. -The 'bssid' attribute can be used to select a specifc accesspoint within a -network. Of all attributes only the 'ssid' attribute is mandatory, all others -are optional and should only be used when needed. +* :rfkill: allows to temporarily prevent any radio activity. The + default is 'false'. -The configuration may contain more than one network. In This case the driver -will try to select the best one it gets a response from. To prevent it -from automatically joining the network the 'auto_connect' attribute must be -set to 'false'; the default value is 'true'. If the 'explicit_scan' attribute -is set, the driver will pro-actively scan for a hidden network with the given -SSID: +* :bgscan: is an expert option that configures the way the + supplicant performs background scanning to steer or rather optimize + roaming decisions within the same network (SSID). The syntax of the + option string corresponds to the original WPA-supplicant 'bgscan' option. + The default value is set to 'simple:30:-70:600'. This functionality can + be disabled by specifying an empty value, e.g. 'bgscan=""'. If bgscan is + disabled the 'accesspoints' report will not be updated while the + supplicant is connected to a network. -! -! +* :log_level: allows for steering the verbosity of the supplicant + and may assist while diagnosing problems with the driver. + Valid values correspond to levels used by the supplicant + and are as follows 'excessive', 'msgdump', 'debug', 'info', + 'warning' and 'error'. The default value is 'error' and configures + the least amount of verbosity. + +* :verbose: allows for logging of diagnostic messages generated + by the managing portion of the driver. The default is 'false'. + +Besides those attributes the '' node can host one or +more '' nodes. Such a node describes the parameters of +a network and has the following attributes. + +* :ssid: sets the name of the network. + + Note: the SSID is copied verbatim and at the moment, there is no way + to express or escape non alphanumeric characters. + +* :bssid: can be used to select a specific access point within a + network. + +* :protection: specifies the used protection mechanism of the + network. Valid values are 'WPA', 'WPA2', 'WPA3' and 'NONE'. + The last one is used in case the network uses other means of + protection and access is open. + + Note: currently only personal WPA protection using a pre-shared-key + (PSK) is supported. + +* :passphrase: sets the PSK that is required should the + network be protected. + +* :auto_connect: allows for joining a network automatically. The + default value is 'true'. When more than one network is configured + with enabled 'auto_connect' the driver will try to select the best + one it gets a response from. + + Note: If configured auto-connect networks overlap in locality, the + driver might switch dynamically between the networks. + +* :explicit_scan: must be set to explicitly scan for hidden networks. + The default is 'false' and it should only be set for networks + that are hidden indeed. + +Of all attributes solely the 'ssid' attribute is mandatory and all +others are optional. They should be used when needed only. + +The following exemplary snippet showcases a config for two networks where +the first one should be automatically considered for joining and uses 'WPA2' +while the second one is hidden but should show up in the scan results and +uses 'WPA'. + +! +! ! +! explicit_scan="true"/> ! -By default, the driver scans for available networks only when not -connected. This can be changed with the 'connected_scan_interval' -attribute, which specifies the interval for connected scans in -seconds and directly influences any roaming decision, i.e., select -a better fit accesspoint for the configured network. -In addition, by specifing 'update_quality_interval', the driver will -every so often update the current signal quality of the established -connection to the accesspoint. Note that this option is only useable when -the 'connected_scan_interval' is set to '0' as both options are mutually -exclusive. - -Also, the driver can be switched to verbose logging during runtime -by setting the 'verbose' or 'verbose_state' attribute to 'true'. - -The wifi driver creates two distinct reports to communicate its state and -information about the wireless infrastructure to other components. The -first one is a list of all available accesspoints. The following examplary -report shows its general structure: +The wifi driver uses two distinct reports, 'state' and 'accesspoints', +to communicate its state of connectivity and information about the wireless +access points in the vicinity to other components. +This exemplary 'accesspoints' report shows its general structure. ! ! @@ -135,32 +179,50 @@ report shows its general structure: ! ! -Each accesspoint node has attributes that contain the SSID and the BSSID -of the accesspoint as well as the link quality (signal strength). These -attributes are mandatory. If the network is protected, the node will also -have an attribute describing the type of protection in addition. +The '' node can contain a fluctuating number of '' +nodes that describe an access point with the following attributes. -The second report provides information about the state of the connection -to the currently connected accesspoint: +* :ssid: specifies the name of the network the access point advertises. + Empty SSIDs are not reported. + +* :bssid: specifies the physical address of the access point. + +* :freq: specifies the frequency used by the access point. + +* :quality: specifies the approximated link quality (calculated from the + RSSI value). + +* :protection: specifies which kind of protection is employed by the access + point. + + Note: when a mixed protection is used by the network, like WPA2-PSK and + WPA3-PSK mixed-mode, only the strongest protection (WPA3-PSK) is + advertised. + +The 'state' report provides information about the state of the connectivity +and looks as follows. ! ! ! -Valid state values are 'connected', 'disconnected', 'connecting'. Depending -on the state, there are additional attributes that can be checked. In case -of an authentication error, e.g. the passphrase is wrong, the 'auth_failure' -attribute will be set to 'true'. The 'rfkilled' attribute is set to 'true' -if a disconnect was triggered by disabling the radio activity via setting -the 'rfkill' attribute. It can also contain the optional 'quality' attribute -to denote the current signal quality (see 'update_quality_interval'). +The '' node encompasses one '' node that has the +following additional attributes beside the ones already discussed. -By subscribing to both reports and providing the required 'wifi_config' ROM -module, a component is able control the wireless driver. +* :state: specifies the actual state of connectivity. Valid values + are 'connected', 'connecting' and 'disconnected'. -Currently only WPA/WPA2/WPA3 protection using a passphrase is supported and -the SSID is copied verbatim. At the moment, there is no way to express or -escape non alphanumeric characters. +* :auth_failure: is an optional attribute and set to 'true' in case + the PSK was wrong + +* :rfkilled: is an optional attribute and set to 'true' whenever + radio activity was temporarily disabled. + +* :not_found: is an optional attribute and is only set when a single + auto-connect network was configured but could not be found. + +By subscribing to both reports and providing the required 'wifi_config' +ROM module, a component is able control the wireless driver. The driver optionally reports the following information under the label "devices" if requested in the config as depicted. @@ -177,7 +239,7 @@ As mentioned in the introduction the 'wifi' component is special in the regard that the actual driver is provided as a shared-object to better isolate it from the the driver binary that is a Libc::Component managing the 'wpa_supplicant'. Since this code and in return the binary is the same for each -platform it is linked against an artifical 'wifi' library that only exists as +platform it is linked against an artificial 'wifi' library that only exists as an ABI stub created via 'lib/symbols/wifi'. In case the driver is integrated via depot archives this is, besides setting the proper ROM routes, of no concern. However, when the driver is built without the depot, the boot image @@ -198,7 +260,7 @@ rather then stub ABI library. This is achieved by adapting the driver's ![…] 'LIBS' must be changed as follows in case the PC wifi driver library is -used: +used. !LIBS := base pc_wifi diff --git a/repos/dde_linux/src/driver/wifi/frontend.h b/repos/dde_linux/src/driver/wifi/frontend.h deleted file mode 100644 index f003987132..0000000000 --- a/repos/dde_linux/src/driver/wifi/frontend.h +++ /dev/null @@ -1,1829 +0,0 @@ - /* - * \author Josef Soentgen - * \date 2018-07-31 - * - * This wifi driver front end uses the CTRL interface of the wpa_supplicant via - * a Genode specific backend, which merely wraps a shared memory buffer, to - * manage the supplicant. - * - * Depending on the 'wifi_config' ROM content it will instruct the supplicant - * to enable, disable and connect to wireless networks. Commands and their - * corresponding execute result are handled by the '_cmd_handler' dispatcher. - * This handler drives the front end's state-machine. Any different type of - * action can only be initiated from the 'IDLE' state. Unsolicited events, e.g. - * a scan-results-available event, may influence the current state. Config - * updates are deferred in case the current state is not 'IDLE'. - * - * brain-dump - * ========== - * - * config update overview: - * [[block any new update]] > [mark stale] > [rm stale] > [add new] > [update new] > [[unblock update]] - * - * add new network: - * [[new ap]] > [ssid] > bssid? + [bssid] > [psk] > auto? + [enable] > new ap? + [[new ap]] - * - * update network: - * [[update ap] > bssid? + [bssid] > psk? + [psk] > auto? + [enable] > update ap? + [[update ap]] - * - * remove network: - * [[mark stale]] > [remove network] > stale? + [remove network] - */ - -/* - * Copyright (C) 2018-2022 Genode Labs GmbH - * - * This file is distributed under the terms of the GNU General Public License - * version 2. - */ - -#ifndef _WIFI_FRONTEND_H_ -#define _WIFI_FRONTEND_H_ - -/* Genode includes */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* rep includes */ -#include -#include - -/* local includes */ -#include - -/* declare manually as it is a internal hack^Winterface */ -extern void wifi_kick_socketcall(); - - -namespace Wifi { - struct Frontend; -} - - -/* keep ordered! */ -static struct Recv_msg_table { - char const *string; - size_t len; -} recv_table[] = { - { "OK", 2 }, - { "FAIL", 4 }, - { "CTRL-EVENT-SCAN-RESULTS", 23 }, - { "CTRL-EVENT-CONNECTED", 20 }, - { "CTRL-EVENT-DISCONNECTED", 23 }, - { "SME: Trying to authenticate", 27 }, - { "CTRL-EVENT-NETWORK-NOT-FOUND", 28 }, -}; - -enum Rmi { - OK = 0, - FAIL, - SCAN_RESULTS, - CONNECTED, - DISCONNECTED, - SME_AUTH, - NOT_FOUND, -}; - - -static inline bool check_recv_msg(char const *msg, - Recv_msg_table const &entry) { - return Genode::strcmp(entry.string, msg, entry.len) == 0; } - - -static bool cmd_successful(char const *msg) { - return check_recv_msg(msg, recv_table[OK]); } - - -static bool cmd_fail(char const *msg) { - return check_recv_msg(msg, recv_table[FAIL]); } - - -static bool results_available(char const *msg) { - return check_recv_msg(msg, recv_table[SCAN_RESULTS]); } - - -static bool connecting_to_network(char const *msg) { - return check_recv_msg(msg, recv_table[SME_AUTH]); } - - -static bool network_not_found(char const *msg) { - return check_recv_msg(msg, recv_table[NOT_FOUND]); } - - -static bool scan_results(char const *msg) { - return Genode::strcmp("bssid", msg, 5) == 0; } - - -static bool list_network_results(char const *msg) { - return Genode::strcmp("network", msg, 7) == 0; } - - -/* - * Central network data structure - */ -struct Accesspoint : Genode::Interface -{ - using Bssid = Genode::String<17+1>; - using Freq = Genode::String< 4+1>; - using Prot = Genode::String< 7+1>; - using Ssid = Genode::String<32+1>; - using Pass = Genode::String<63+1>; - - /* - * Accesspoint information fields used by the front end - */ - Bssid bssid { }; - Freq freq { }; - Prot prot { }; - Ssid ssid { }; - Pass pass { }; - unsigned signal { 0 }; - - /* - * CTRL interface fields - * - * The 'enabled' field is set to true if ENABLE_NETWORK - * was successfully executed. The network itself might - * get disabled by wpa_supplicant itself in case it cannot - * connect to the network, which will _not_ be reflected - * here. - */ - int id { -1 }; - bool enabled { false }; - - /* - * Internal configuration fields - */ - bool auto_connect { false }; - bool update { false }; - bool stale { false }; - bool explicit_scan { false }; - - /** - * Default constructor - */ - Accesspoint() { } - - /** - * Constructor that initializes information fields - */ - Accesspoint(char const *bssid, char const *freq, - char const *prot, char const *ssid, unsigned signal) - : bssid(bssid), freq(freq), prot(prot), ssid(ssid), signal(signal) - { } - - void invalidate() { ssid = Ssid(); bssid = Bssid(); } - - bool valid() const { return ssid.length() > 1; } - bool bssid_valid() const { return bssid.length() > 1; } - bool wpa() const { return prot != "NONE"; } - bool wpa3() const { return prot == "WPA3"; } - bool stored() const { return id != -1; } -}; - - -template -static void for_each_line(char const *msg, FUNC const &func) -{ - char line_buffer[1024]; - size_t cur = 0; - - while (msg[cur] != 0) { - size_t until = Util::next_char(msg, cur, '\n'); - Genode::memcpy(line_buffer, &msg[cur], until); - line_buffer[until] = 0; - cur += until + 1; - - func(line_buffer); - } -} - - -template -static void for_each_result_line(char const *msg, FUNC const &func) -{ - char line_buffer[1024]; - size_t cur = 0; - - /* skip headline */ - size_t until = Util::next_char(msg, cur, '\n'); - cur += until + 1; - - while (msg[cur] != 0) { - until = Util::next_char(msg, cur, '\n'); - Genode::memcpy(line_buffer, &msg[cur], until); - line_buffer[until] = 0; - cur += until + 1; - - char const *s[5] = { }; - - for (size_t c = 0, i = 0; i < 5; i++) { - size_t pos = Util::next_char(line_buffer, c, '\t'); - line_buffer[c+pos] = 0; - s[i] = (char const*)&line_buffer[c]; - c += pos + 1; - } - - bool const is_wpa1 = Util::string_contains((char const*)s[3], "WPA"); - bool const is_wpa2 = Util::string_contains((char const*)s[3], "WPA2"); - bool const is_wpa3 = Util::string_contains((char const*)s[3], "SAE"); - - unsigned signal = Util::approximate_quality(s[2]); - - char const *prot = is_wpa1 ? "WPA" : "NONE"; - prot = is_wpa2 ? "WPA2" : prot; - prot = is_wpa3 ? "WPA3" : prot; - - Accesspoint ap(s[0], s[1], prot, s[4], signal); - - func(ap); - } -} - - -/* - * Wifi driver front end - */ -struct Wifi::Frontend : Wifi::Rfkill_notification_handler -{ - Frontend(const Frontend&) = delete; - Frontend& operator=(const Frontend&) = delete; - - /* accesspoint */ - - Genode::Heap _ap_allocator; - - using Accesspoint_r = Genode::Registered; - - Genode::Registry _aps { }; - - Accesspoint *_lookup_ap_by_ssid(Accesspoint::Ssid const &ssid) - { - Accesspoint *p = nullptr; - _aps.for_each([&] (Accesspoint &ap) { - if (ap.valid() && ap.ssid == ssid) { p = ≈ } - }); - return p; - } - - Accesspoint *_lookup_ap_by_bssid(Accesspoint::Bssid const &bssid) - { - Accesspoint *p = nullptr; - _aps.for_each([&] (Accesspoint &ap) { - if (ap.valid() && ap.bssid == bssid) { p = ≈ } - }); - return p; - } - - Accesspoint *_alloc_ap() - { - return new (&_ap_allocator) Accesspoint_r(_aps); - } - - void _free_ap(Accesspoint &ap) - { - Genode::destroy(&_ap_allocator, &ap); - } - - template - void _for_each_ap(FUNC const &func) - { - _aps.for_each([&] (Accesspoint &ap) { - func(ap); - }); - } - - unsigned _count_to_be_enabled() - { - unsigned count = 0; - auto enable = [&](Accesspoint const &ap) { - count += ap.auto_connect; - }; - _for_each_ap(enable); - return count; - } - - unsigned _count_enabled() - { - unsigned count = 0; - auto enabled = [&](Accesspoint const &ap) { - count += ap.enabled; - }; - _for_each_ap(enabled); - return count; - } - - unsigned _count_stored() - { - unsigned count = 0; - auto enabled = [&](Accesspoint const &ap) { - count += ap.stored(); - }; - _for_each_ap(enabled); - return count; - } - - /* remaining stuff */ - - Msg_buffer &_msg; - - Genode::Blockade _notify_blockade { }; - - void _notify_lock_lock() { _notify_blockade.block(); } - void _notify_lock_unlock() { _notify_blockade.wakeup(); } - - bool _rfkilled { false }; - - Genode::Signal_handler _rfkill_handler; - - void _handle_rfkill() - { - _rfkilled = Wifi::rfkill_blocked(); - - /* re-enable scan timer */ - if (!_rfkilled) { - _timer.sigh(_timer_sigh); - _try_arming_any_timer(); - } else { - _timer.sigh(Genode::Signal_context_capability()); - } - - if (_rfkilled && _state != State::IDLE) { - Genode::warning("rfkilled in state ", state_strings(_state)); - } - } - - /* config */ - - Genode::Attached_rom_dataspace _config_rom; - Genode::Signal_handler _config_sigh; - - bool _verbose { false }; - bool _verbose_state { false }; - - bool _deferred_config_update { false }; - bool _single_autoconnect { false }; - - Genode::uint64_t _connected_scan_interval { 30 }; - Genode::uint64_t _scan_interval { 5 }; - Genode::uint64_t _update_quality_interval { 0 }; - - void _config_update(bool signal) - { - _config_rom.update(); - - if (!_config_rom.valid()) { return; } - - Genode::Xml_node config = _config_rom.xml(); - - _verbose = config.attribute_value("verbose", _verbose); - _verbose_state = config.attribute_value("verbose_state", _verbose_state); - - Genode::uint64_t const connected_scan_interval = - Util::check_time(config.attribute_value("connected_scan_interval", - _connected_scan_interval), - 0, 15*60); - - Genode::uint64_t const scan_interval = - Util::check_time(config.attribute_value("scan_interval", - _scan_interval), - 5, 15*60); - - Genode::uint64_t const update_quality_interval = - Util::check_time(config.attribute_value("update_quality_interval", - _update_quality_interval), - 0, 15*60); - - bool const new_connected_scan_interval = - connected_scan_interval != _connected_scan_interval; - - bool const new_scan_interval = - connected_scan_interval != _scan_interval; - - bool const new_update_quality_interval = - update_quality_interval != _update_quality_interval; - - _connected_scan_interval = connected_scan_interval; - _scan_interval = scan_interval; - _update_quality_interval = update_quality_interval; - - /* - * Arm again if intervals changed, implicitly discards - * an already scheduled timer. - * - * First try to arm scanning and if that fails try arming - * signal-strength polling. - */ - if ( new_connected_scan_interval - || new_scan_interval - || new_update_quality_interval) - _try_arming_any_timer(); - - /* - * Always handle rfkill, regardless in which state we are currently in. - * When we come back from rfkill, will most certainly will be IDLE anyway. - */ - if (config.has_attribute("rfkill")) { - bool const blocked = config.attribute_value("rfkill", false); - Wifi::set_rfkill(blocked); - - /* - * In case we get blocked set rfkilled immediately to prevent - * any further scanning operation. The actual value will be set - * by the singal handler but is not expected to be any different - * as the rfkill call is not supposed to fail. - */ - if (blocked && !_rfkilled) { - _rfkilled = true; - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("state", "disconnected"); - xml.attribute("rfkilled", _rfkilled); - }); - }); - - _connected_ap.invalidate(); - } - } - - /* - * Block any further config updates until we have finished applying - * the current one. - */ - if (_state != State::IDLE) { - Genode::warning("deferring config update (", state_strings(_state), ")"); - _deferred_config_update = true; - return; - } - - bool single_autoconnect = false; - - /* update AP list */ - auto parse = [&] ( Genode::Xml_node node) { - - Accesspoint ap; - ap.ssid = node.attribute_value("ssid", Accesspoint::Ssid()); - ap.bssid = node.attribute_value("bssid", Accesspoint::Bssid()); - - size_t const ssid_len = ap.ssid.length() - 1; - if (ssid_len == 0 || ssid_len > 32) { - Genode::warning("ignoring accesspoint with invalid ssid"); - return; - } - - Accesspoint *p = _lookup_ap_by_ssid(ap.ssid); - if (p) { - if (_verbose) { Genode::log("Update: '", p->ssid, "'"); } - /* mark for updating */ - p->update = true; - } else { - p = _alloc_ap(); - if (!p) { - Genode::warning("could not add accesspoint, no slots left"); - return; - } - } - - ap.pass = node.attribute_value("passphrase", Accesspoint::Pass("")); - ap.prot = node.attribute_value("protection", Accesspoint::Prot("NONE")); - ap.auto_connect = node.attribute_value("auto_connect", true); - ap.explicit_scan = node.attribute_value("explicit_scan", false); - - if (ap.wpa()) { - size_t const psk_len = ap.pass.length() - 1; - if (psk_len < 8 || psk_len > 63) { - Genode::warning("ignoring accesspoint '", ap.ssid, - "' with invalid pass"); - return; - } - } - - - /* check if updating is really necessary */ - if (p->update) { - p->update = ((ap.bssid.length() > 1 && ap.bssid != p->bssid) - || ap.pass != p->pass - || ap.prot != p->prot - || ap.auto_connect != p->auto_connect); - } - - /* TODO add better way to check validity */ - if (ap.bssid.length() == 17 + 1) { p->bssid = ap.bssid; } - - p->ssid = ap.ssid; - p->prot = ap.prot; - p->pass = ap.pass; - p->auto_connect = ap.auto_connect; - p->explicit_scan = ap.explicit_scan; - - single_autoconnect |= (p->update || p->auto_connect) && !_connected_ap.valid(); - }; - config.for_each_sub_node("network", parse); - - /* - * To accomodate a management component that only deals - * with on network, e.g. the sculpt_manager, generate a - * fake connecting event. Either a connected or disconnected - * event will bring us to square one. - */ - if (signal && _count_to_be_enabled() == 1 && single_autoconnect && !_rfkilled) { - - auto lookup = [&] (Accesspoint const &ap) { - if (!ap.auto_connect) { return; } - - if (_verbose) { Genode::log("Single autoconnect event for '", ap.ssid, "'"); } - - try { - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("ssid", ap.ssid); - xml.attribute("state", "connecting"); - }); - }); - - _single_autoconnect = true; - - } catch (...) { } - }; - _for_each_ap(lookup); - } - - /* - * Marking removes stale APs first and triggers adding of - * new ones afterwards. - */ - _mark_stale_aps(config); - } - - void _handle_config_update() { _config_update(true); } - - /* state */ - - Accesspoint *_processed_ap { nullptr }; - Accesspoint _connected_ap { }; - - enum State { - IDLE = 0x00, - SCAN = 0x01, - NETWORK = 0x02, - CONNECT = 0x03, - STATUS = 0x04, - INFO = 0x05, - SIGNAL = 0x06, - - INITIATE_SCAN = 0x00|SCAN, - PENDING_RESULTS = 0x10|SCAN, - - ADD_NETWORK = 0x00|NETWORK, - FILL_NETWORK_SSID = 0x10|NETWORK, - FILL_NETWORK_BSSID = 0x20|NETWORK, - FILL_NETWORK_KEY_MGMT = 0x30|NETWORK, - FILL_NETWORK_PSK = 0x40|NETWORK, - REMOVE_NETWORK = 0x50|NETWORK, - ENABLE_NETWORK = 0x60|NETWORK, - DISABLE_NETWORK = 0x70|NETWORK, - LIST_NETWORKS = 0x90|NETWORK, - SET_NETWORK_PMF = 0xA0|NETWORK, - - CONNECTING = 0x00|CONNECT, - CONNECTED = 0x10|CONNECT, - DISCONNECTED = 0x20|CONNECT, - }; - - State _state { State::IDLE }; - - char const *state_strings(State state) - { - switch (state) { - case IDLE: return "idle"; - case INITIATE_SCAN: return "initiate scan"; - case PENDING_RESULTS: return "pending results"; - case ADD_NETWORK: return "add network"; - case FILL_NETWORK_SSID: return "fill network ssid"; - case FILL_NETWORK_BSSID: return "fill network bssid"; - case FILL_NETWORK_KEY_MGMT: return "fill network key_mgmt"; - case FILL_NETWORK_PSK: return "fill network pass"; - case REMOVE_NETWORK: return "remove network"; - case ENABLE_NETWORK: return "enable network"; - case DISABLE_NETWORK: return "disable network"; - case CONNECTING: return "connecting"; - case CONNECTED: return "connected"; - case DISCONNECTED: return "disconnected"; - case STATUS: return "status"; - case LIST_NETWORKS: return "list networks"; - case INFO: return "info"; - case SET_NETWORK_PMF: return "set network pmf"; - case SIGNAL: return "signal poll"; - default: return "unknown"; - }; - } - - void _state_transition(State ¤t, State next) - { - if (_verbose_state) { - using namespace Genode; - log("Transition: ", state_strings(current), " -> ", - state_strings(next)); - } - - current = next; - } - - using Cmd_str = Genode::String; - - void _submit_cmd(Cmd_str const &str) - { - Genode::memset(_msg.send, 0, sizeof(_msg.send)); - Genode::memcpy(_msg.send, str.string(), str.length()); - ++_msg.send_id; - - wpa_ctrl_set_fd(); - - /* - * We might have to pull the socketcall task out of poll_all() - * because otherwise we might be late and wpa_supplicant has - * already removed all scan results due to BSS age settings. - */ - wifi_kick_socketcall(); - } - - /* scan */ - - enum class Timer_type : uint8_t { CONNECTED_SCAN, SCAN, SIGNAL_POLL }; - - Genode::uint64_t _seconds_from_type(Timer_type const type) - { - switch (type) { - case Timer_type::CONNECTED_SCAN: return _connected_scan_interval; - case Timer_type::SCAN: return _scan_interval; - case Timer_type::SIGNAL_POLL: return _update_quality_interval; - } - /* never reached */ - return 0; - } - - static char const *_name_from_type(Timer_type const type) - { - switch (type) { - case Timer_type::CONNECTED_SCAN: return "connected-scan"; - case Timer_type::SCAN: return "scan"; - case Timer_type::SIGNAL_POLL: return "signal-poll"; - } - /* never reached */ - return nullptr; - } - - Timer::Connection _timer; - Genode::Signal_handler _timer_sigh; - - bool _arm_timer(Timer_type const type) - { - Genode::uint64_t const sec = _seconds_from_type(type); - if (!sec) { return false; } - - if (_verbose) - Genode::log("Arm timer for ", _name_from_type(type)); - - _timer.trigger_once(sec * (1000 * 1000)); - return true; - } - - void _request_scan() - { - /* skip as we will be scheduled some time soon(tm) anyway */ - if (_state != State::IDLE) { - if (_verbose) { - Genode::log("Not idle, ignore scan request, state: ", - Genode::Hex((unsigned)_state)); - } - return; - } - - /* left one attempt out */ - if (_scan_busy) { - if (_verbose) { Genode::log("Scan already pending, ignore scan request"); } - _scan_busy = false; - return; - } - - enum { SSID_ARG_LEN = 6 + 64, /* " ssid " + "a5a5a5a5..." */ }; - /* send buffer - 'SCAN ' + stuff */ - char ssid_buffer[sizeof(Msg_buffer::send)-16] = { }; - size_t buffer_pos = 0; - - auto valid_ssid = [&] (Accesspoint const &ap) { - - if (buffer_pos + SSID_ARG_LEN >= sizeof(ssid_buffer)) { - return; - } - - if (!ap.explicit_scan) { return; } - - char ssid_hex[64+1] = { }; - char const *ssid = ap.ssid.string(); - - for (size_t i = 0; i < ap.ssid.length() - 1; i++) { - Util::byte2hex((ssid_hex + i * 2), ssid[i]); - } - - Genode::String tmp(" ssid ", (char const*)ssid_hex); - size_t const tmp_len = tmp.length() - 1; - - Genode::memcpy((ssid_buffer + buffer_pos), tmp.string(), tmp_len); - buffer_pos += tmp_len; - }; - _for_each_ap(valid_ssid); - - _state_transition(_state, State::INITIATE_SCAN); - _submit_cmd(Cmd_str("SCAN", (char const*)ssid_buffer)); - } - - void _poll_signal_strength() - { - if (_state != State::IDLE) { - if (_verbose) - Genode::log("Not idle, ignore signal-poll request, state: ", - Genode::Hex((unsigned)_state)); - return; - } - - _state_transition(_state, State::SIGNAL); - _submit_cmd(Cmd_str("SIGNAL_POLL")); - } - - void _handle_timer() - { - /* - * If we are blocked or currently trying to join a network - * suspend scanning. - */ - if (_rfkilled || _connecting.length() > 1) { - if (_verbose) - Genode::log("Timer: suspend due to RFKILL or connection" - " attempt"); - return; - } - - /* - * First check if (connected-)scanning is enabled, re-arm - * the timer again and try to submit the request. In case we - * are not able to submit it the timer will trigger another - * attempt later on. - */ - if (_arm_scan_timer()) { - _request_scan(); - return; - } else - if (_verbose) - Genode::log("Timer: scanning disabled"); - - /* - * We arm the poll timer only when we are not scanning. - * So connected-scan MUST be disabled for the signal-strength - * polling to be active. - */ - if (_arm_poll_timer()) { - _poll_signal_strength(); - return; - } else - if (_verbose) - Genode::log("Timer: signal-strength polling disabled"); - } - - bool _arm_scan_timer() - { - Timer_type const type = _connected_ap.bssid_valid() - ? Timer_type::CONNECTED_SCAN - : Timer_type::SCAN; - return _arm_timer(type); - } - - bool _arm_poll_timer() - { - if (!_connected_ap.bssid_valid()) - return false; - - return _arm_timer(Timer_type::SIGNAL_POLL); - } - - void _try_arming_any_timer() - { - if (!_arm_scan_timer()) - (void)_arm_poll_timer(); - } - - Genode::Constructible _ap_reporter { }; - - void _generate_scan_results_report(char const *msg) - { - unsigned count_lines = 0; - for_each_line(msg, [&] (char const*) { count_lines++; }); - - if (!count_lines) { - if (_verbose) { Genode::log("Scan results empty"); } - return; - } - - bool connecting_attempt = false; - try { - - _ap_reporter->generate([&] (Genode::Xml_generator &xml) { - - for_each_result_line(msg, [&] (Accesspoint const &ap) { - - /* ignore potentially empty ssids */ - if (ap.ssid == "") { return; } - - xml.node("accesspoint", [&]() { - xml.attribute("ssid", ap.ssid); - xml.attribute("bssid", ap.bssid); - xml.attribute("freq", ap.freq); - xml.attribute("quality", ap.signal); - if (ap.wpa()) { xml.attribute("protection", ap.prot); } - }); - - auto check_existence = [&] (Accesspoint &lap) { - connecting_attempt |= (lap.ssid == ap.ssid) && ap.auto_connect; - }; - _for_each_ap(check_existence); - }); - }); - - } catch (...) { /* silently omit report */ } - - try { - if (!_connected_ap.bssid_valid() && connecting_attempt) { - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("state", "connecting"); - }); - }); - } - } catch (...) { /* silently omit state */ } - } - - /* network commands */ - - void _mark_stale_aps(Genode::Xml_node const &config) - { - auto mark_stale = [&] (Accesspoint &ap) { - ap.stale = true; - - config.for_each_sub_node("network", [&] ( Genode::Xml_node node) { - Accesspoint::Ssid ssid = node.attribute_value("ssid", Accesspoint::Ssid("")); - - if (ap.ssid == ssid) { ap.stale = false; } - }); - }; - _for_each_ap(mark_stale); - - _remove_stale_aps(); - } - - void _remove_stale_aps() - { - if (_state != State::IDLE) { - Genode::warning("cannot remove stale APs in non-idle state " - "(", state_strings(_state), ")"); - return; - } - - if (_processed_ap) { return; } - - _aps.for_each([&] (Accesspoint &ap) { - if (!_processed_ap && ap.valid() && ap.stale) { - _processed_ap = ≈ - } - }); - - if (!_processed_ap) { - /* TODO move State transition somewhere more sane */ - _state_transition(_state, State::IDLE); - _add_new_aps(); - return; - } - - if (_verbose) { - Genode::log("Remove network: '", _processed_ap->ssid, "'"); - } - - _state_transition(_state, State::REMOVE_NETWORK); - _submit_cmd(Cmd_str("REMOVE_NETWORK ", _processed_ap->id)); - } - - void _update_aps() - { - if (_state != State::IDLE) { - Genode::warning("cannot enable network in non-idle state"); - return; - } - - if (_processed_ap) { return; } - - _aps.for_each([&] (Accesspoint &ap) { - if (!_processed_ap && ap.stored() && ap.update) { - _processed_ap = ≈ - } - }); - - if (!_processed_ap) { return; } - - if (_verbose) { - Genode::log("Update network: '", _processed_ap->ssid, "'"); - } - - /* re-use state to change PSK */ - _state_transition(_state, State::FILL_NETWORK_PSK); - _network_set_psk(); - } - - - void _add_new_aps() - { - if (_state != State::IDLE) { - Genode::warning("cannot enable network in non-idle state"); - return; - } - - if (_processed_ap) { return; } - - _aps.for_each([&] (Accesspoint &ap) { - if (!_processed_ap && ap.valid() && !ap.stored()) { - _processed_ap = ≈ - } - }); - - if (!_processed_ap) { - /* XXX move State transition somewhere more sane */ - _state_transition(_state, State::IDLE); - _update_aps(); - return; - } - - if (_verbose) { - Genode::log("Add network: '", _processed_ap->ssid, "'"); - } - - _state_transition(_state, State::ADD_NETWORK); - _submit_cmd(Cmd_str("ADD_NETWORK")); - } - - void _network_enable(Accesspoint &ap) - { - if (_state != State::IDLE) { - Genode::warning("cannot enable network in non-idle state"); - return; - } - - if (ap.enabled) { return; } - - if (_verbose) { - Genode::log("Enable network: ", ap.id, " '", ap.ssid, "'"); - } - - ap.enabled = true; - - _state_transition(_state, State::ENABLE_NETWORK); - _submit_cmd(Cmd_str("ENABLE_NETWORK ", ap.id)); - } - - void _network_disable(Accesspoint &ap) - { - if (_state != State::IDLE) { - Genode::warning("cannot enable network in non-idle state"); - return; - } - - if (!ap.enabled) { return; } - - if (_verbose) { - Genode::log("Disable network: ", ap.id, " '", ap.ssid, "'"); - } - - ap.enabled = false; - - _state_transition(_state, State::DISABLE_NETWORK); - _submit_cmd(Cmd_str("DISABLE_NETWORK ", ap.id)); - } - - void _network_set_ssid(char const *msg) - { - long id = -1; - Genode::ascii_to(msg, id); - - _processed_ap->id = static_cast(id); - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " ssid \"", _processed_ap->ssid, "\"")); - } - - void _network_set_bssid() - { - bool const valid = _processed_ap->bssid.length() == 17 + 1; - char const *bssid = valid ? _processed_ap->bssid.string() : ""; - - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " bssid ", bssid)); - } - - void _network_set_key_mgmt_sae() - { - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " key_mgmt SAE")); - } - - void _network_set_pmf() - { - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " ieee80211w 2")); - } - - void _network_set_psk() - { - if (_processed_ap->wpa()) { - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " psk \"", _processed_ap->pass, "\"")); - } else { - _submit_cmd(Cmd_str("SET_NETWORK ", _processed_ap->id, - " key_mgmt NONE")); - } - } - - /* result handling */ - - bool _scan_busy { false }; - - void _handle_scan_results(State state, char const *msg) - { - switch (state) { - case State::INITIATE_SCAN: - if (!cmd_successful(msg)) { - _scan_busy = Genode::strcmp(msg, "FAIL-BUSY"); - if (!_scan_busy) { - Genode::warning("could not initiate scan: ", msg); - } - } - _state_transition(_state, State::IDLE); - break; - case State::PENDING_RESULTS: - if (scan_results(msg)) { - _state_transition(_state, State::IDLE); - _generate_scan_results_report(msg); - } - break; - default: - Genode::warning("unknown SCAN state: ", msg); - break; - } - } - - void _handle_network_results(State state, char const *msg) - { - bool successfully = false; - - switch (state) { - case State::ADD_NETWORK: - if (cmd_fail(msg)) { - Genode::error("could not add network: ", msg); - _state_transition(_state, State::IDLE); - } else { - _state_transition(_state, State::FILL_NETWORK_SSID); - _network_set_ssid(msg); - - successfully = true; - } - break; - case State::REMOVE_NETWORK: - { - _state_transition(_state, State::IDLE); - - Accesspoint &ap = *_processed_ap; - /* reset processed AP as this is an end state */ - _processed_ap = nullptr; - - if (cmd_fail(msg)) { - Genode::error("could not remove network: ", msg); - } else { - _free_ap(ap); - - /* trigger the next round */ - _remove_stale_aps(); - - successfully = true; - } - break; - } - case State::FILL_NETWORK_SSID: - _state_transition(_state, State::IDLE); - - if (!cmd_successful(msg)) { - Genode::error("could not set ssid for network: ", msg); - _state_transition(_state, State::IDLE); - } else { - _state_transition(_state, State::FILL_NETWORK_BSSID); - _network_set_bssid(); - - successfully = true; - } - break; - case State::FILL_NETWORK_BSSID: - _state_transition(_state, State::IDLE); - - if (!cmd_successful(msg)) { - Genode::error("could not set bssid for network: ", msg); - _state_transition(_state, State::IDLE); - } else { - - /* - * For the moment branch here to handle WPA3-personal-only - * explicitly. - */ - if (_processed_ap->wpa3()) { - _state_transition(_state, State::FILL_NETWORK_KEY_MGMT); - _network_set_key_mgmt_sae(); - } else { - _state_transition(_state, State::FILL_NETWORK_PSK); - _network_set_psk(); - } - - successfully = true; - } - break; - case State::FILL_NETWORK_KEY_MGMT: - _state_transition(_state, State::IDLE); - - if (!cmd_successful(msg)) { - Genode::error("could not set key_mgmt for network: ", msg); - _state_transition(_state, State::IDLE); - } else { - _state_transition(_state, State::SET_NETWORK_PMF); - _network_set_pmf(); - - successfully = true; - } - break; - case State::SET_NETWORK_PMF: - _state_transition(_state, State::IDLE); - - if (!cmd_successful(msg)) { - Genode::error("could not set PMF for network: ", msg); - _state_transition(_state, State::IDLE); - } else { - _state_transition(_state, State::FILL_NETWORK_PSK); - _network_set_psk(); - - successfully = true; - } - break; - case State::FILL_NETWORK_PSK: - { - _state_transition(_state, State::IDLE); - - Accesspoint &ap = *_processed_ap; - - if (!cmd_successful(msg)) { - Genode::error("could not set passphrase for network: ", msg); - } else { - - /* - * Disable network to trick wpa_supplicant into reloading - * the settings. - */ - if (ap.update) { - ap.enabled = true; - _network_disable(ap); - } else - - if (ap.auto_connect) { - _network_enable(ap); - } else { - /* trigger the next round */ - _add_new_aps(); - } - - successfully = true; - } - break; - } - case State::ENABLE_NETWORK: - { - _state_transition(_state, State::IDLE); - - /* reset processed AP as this is an end state */ - _processed_ap = nullptr; - - if (!cmd_successful(msg)) { - Genode::error("could not enable network: ", msg); - } else { - /* trigger the next round */ - _add_new_aps(); - - successfully = true; - } - break; - } - case State::DISABLE_NETWORK: - { - _state_transition(_state, State::IDLE); - - Accesspoint &ap = *_processed_ap; - /* reset processed AP as this is an end state */ - _processed_ap = nullptr; - - if (!cmd_successful(msg)) { - Genode::error("could not disable network: ", msg); - } else { - - /* - * Updated settings are applied, enable the network - * anew an try again. - */ - if (ap.update) { - ap.update = false; - - if (ap.auto_connect) { - _network_enable(ap); - } - } - - successfully = true; - } - break; - } - case State::LIST_NETWORKS: - _state_transition(_state, State::IDLE); - - if (list_network_results(msg)) { - Genode::error("List networks:\n", msg); - } - break; - default: - Genode::warning("unknown network state: ", msg); - break; - } - - /* - * If some step failed we have to generate a fake - * disconnect event. - */ - if (_single_autoconnect && !successfully) { - try { - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("state", "disconnected"); - xml.attribute("rfkilled", _rfkilled); - xml.attribute("config_error", true); - }); - }); - - _single_autoconnect = false; - } catch (...) { } - } - } - - void _handle_status_result(State &state, char const *msg) - { - _state_transition(state, State::IDLE); - - /* - * Querying the status might have failed but we already sent - * out a rudimentary report, just stop here. - */ - if (0 == msg[0]) { return; } - - Accesspoint ap { }; - - auto fill_ap = [&] (char const *line) { - if (Genode::strcmp(line, "ssid=", 5) == 0) { - ap.ssid = Accesspoint::Ssid(line+5); - } else - - if (Genode::strcmp(line, "bssid=", 6) == 0) { - ap.bssid = Accesspoint::Bssid(line+6); - } else - - if (Genode::strcmp(line, "freq=", 5) == 0) { - ap.freq = Accesspoint::Freq(line+5); - } - }; - for_each_line(msg, fill_ap); - - if (!ap.ssid.valid()) { - Genode::error("Cannot query SSID :-("); - return; - } - - Accesspoint *p = _lookup_ap_by_ssid(ap.ssid); - if (p) { - p->bssid = ap.bssid; - p->freq = ap.freq; - } - - _connected_ap.ssid = ap.ssid; - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("ssid", ap.ssid); - xml.attribute("bssid", ap.bssid); - xml.attribute("freq", ap.freq); - xml.attribute("state", "connected"); - - /* - * Only add the attribute when we have something - * to report so that a consumer of the state report - * may take appropriate actions. - */ - if (_connected_ap.signal) - xml.attribute("quality", _connected_ap.signal); - }); - }); - } - - void _handle_info_result(State &state, char const *msg) - { - _state_transition(state, State::IDLE); - - if (!_connected_event && !_disconnected_event) { return; } - - /* - * It might happen that the supplicant already flushed - * its internal BSS information and cannot help us out. - * Since we already sent out a rudimentary report, just - * stop here. - */ - if (0 == msg[0]) { return; } - - Accesspoint ap { }; - - auto fill_ap = [&] (char const *line) { - if (Genode::strcmp(line, "ssid=", 5) == 0) { - ap.ssid = Accesspoint::Ssid(line+5); - } else - - if (Genode::strcmp(line, "bssid=", 6) == 0) { - ap.bssid = Accesspoint::Bssid(line+6); - } else - - if (Genode::strcmp(line, "freq=", 5) == 0) { - ap.freq = Accesspoint::Freq(line+5); - } - }; - for_each_line(msg, fill_ap); - - /* - * When the config is changed while we are still connecting and - * for some reasons the accesspoint does not get disabled - * a connected event could arrive and we will get a nullptr... - */ - Accesspoint *p = _lookup_ap_by_ssid(ap.ssid); - - /* - * ... but we still generate a report and let the management - * component deal with it. - */ - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("ssid", ap.ssid); - xml.attribute("bssid", ap.bssid); - xml.attribute("freq", ap.freq); - xml.attribute("state", _connected_event ? "connected" - : "disconnected"); - if (!_connected_event) { - xml.attribute("rfkilled", _rfkilled); - xml.attribute("auth_failure", _disconnected_fail); - } - }); - }); - - if (_disconnected_fail) { - /* - * Being able to remove a failed network from the internal - * state of the supplicant relies on a sucessful BSS request. - * In case that failes the supplicant will try to join the - * network again and again... - */ - if (!p || _processed_ap) { - Genode::error("cannot disabled failed network"); - } else { - _processed_ap = p; - _network_disable(*_processed_ap); - } - } else - - if (_connected_event) { - /* - * In case the BSS cmd did not return a valid SSID, which - * was observed only with hidden networks so far, check the - * current status. - */ - if (!p) { - _state_transition(state, State::STATUS); - _submit_cmd(Cmd_str("STATUS")); - - } else { - p->bssid = ap.bssid; - p->freq = ap.freq; - } - - _connected_ap = ap; - } - } - - void _handle_signal_poll_result(State &state, char const *msg) - { - _state_transition(state, State::IDLE); - - using Rssi = Genode::String<5>; - Rssi rssi { }; - auto get_rssi = [&] (char const *line) { - if (Genode::strcmp(line, "RSSI=", 5) != 0) - return; - - rssi = Rssi(line + 5); - }; - for_each_line(msg, get_rssi); - - /* - * Use the same simplified approximation for denoting - * the quality to be in line with the scan results. - */ - _connected_ap.signal = - Util::approximate_quality(rssi.valid() ? rssi.string() - : "-100"); - - /* - * Query the status to incorporate the newly acquired - * quality into a new state report. - */ - _state_transition(state, State::STATUS); - _submit_cmd(Cmd_str("STATUS")); - } - - /* connection state */ - - Genode::Constructible _state_reporter { }; - - Accesspoint::Bssid _connecting { }; - - Accesspoint::Bssid const _extract_bssid(char const *msg, State state) - { - char bssid[32] = { }; - /* by the power of wc -c, I have the start pos... */ - enum { BSSID_CONNECT = 37, BSSID_DISCONNECT = 30, BSSID_CONNECTING = 33, }; - - bool const connected = state == State::CONNECTED; - bool const connecting = state == State::CONNECTING; - - size_t const len = 17; - size_t const start = connected ? BSSID_CONNECT - : connecting ? BSSID_CONNECTING - : BSSID_DISCONNECT; - Genode::memcpy(bssid, msg + start, len); - return Accesspoint::Bssid((char const*)bssid); - } - - bool _auth_failure(char const *msg) - { - enum { REASON_OFFSET = 55, }; - unsigned reason = 0; - Genode::ascii_to((msg + REASON_OFFSET), reason); - switch (reason) { - case 2: /* prev auth no longer valid */ - case 15: /* 4-way handshake timeout/failed */ - return true; - default: - return false; - } - } - - /* events */ - - bool _connected_event { false }; - bool _disconnected_event { false }; - bool _disconnected_fail { false }; - bool _was_connected { false }; - - enum { MAX_REAUTH_ATTEMPTS = 1 }; - unsigned _reauth_attempts { 0 }; - - enum { MAX_ATTEMPTS = 3, }; - unsigned _scan_attempts { 0 }; - - Accesspoint::Bssid _pending_bssid { }; - - void _handle_connection_events(char const *msg) - { - _connected_event = false; - _disconnected_event = false; - _disconnected_fail = false; - - bool const connected = check_recv_msg(msg, recv_table[Rmi::CONNECTED]); - bool const disconnected = check_recv_msg(msg, recv_table[Rmi::DISCONNECTED]); - bool const auth_failed = disconnected && _auth_failure(msg); - - State state = connected ? State::CONNECTED : State::DISCONNECTED; - Accesspoint::Bssid const &bssid = _extract_bssid(msg, state); - - /* simplistic heuristic to ignore re-authentication requests */ - if (_connected_ap.bssid.valid() && auth_failed) { - if (_reauth_attempts < MAX_ATTEMPTS) { - Genode::log("ignore deauth from: ", _connected_ap.bssid); - _reauth_attempts++; - return; - } - } - _reauth_attempts = 0; - - /* - * Always reset the "global" connection state first - */ - _connected_ap.invalidate(); - if (connected) { _connected_ap.bssid = bssid; } - if (connected || disconnected) { _connecting = Accesspoint::Bssid(); } - - /* - * Save local connection state here for later re-use when - * the BSS information are handled. - */ - _connected_event = connected; - _disconnected_event = disconnected; - _disconnected_fail = auth_failed; - - if (!_rfkilled) { - - /* - * As we only received the BSSID, try to gather more information - * so we may generate a more thorough follow-up state report. - */ - if (_state != State::IDLE) { - _pending_bssid = bssid; - } else { - _state_transition(_state, State::INFO); - _submit_cmd(Cmd_str("BSS ", bssid)); - } - - _try_arming_any_timer(); - } - - /* - * Generate the first rudimentary report whose missing information - * are (potentially) filled in later (see above). - */ - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("bssid", bssid); - xml.attribute("state", connected ? "connected" - : "disconnected"); - if (disconnected) { - xml.attribute("rfkilled", _rfkilled); - if (auth_failed) { - xml.attribute("auth_failure", auth_failed); - } - } - }); - }); - - /* reset */ - _single_autoconnect = false; - } - - Genode::Signal_handler _events_handler; - - unsigned _last_event_id { 0 }; - - void _handle_events() - { - char const *msg = reinterpret_cast(_msg.event); - unsigned const event_id = _msg.event_id; - - /* return early */ - if (_last_event_id == event_id) { - _notify_lock_unlock(); - return; - } - - if (results_available(msg)) { - - /* - * We might have to pull the socketcall task out of poll_all() - * because otherwise we might be late and wpa_supplicant has - * already removed all scan results due to BSS age settings. - */ - wifi_kick_socketcall(); - - if (_state == State::IDLE) { - _state_transition(_state, State::PENDING_RESULTS); - _submit_cmd(Cmd_str("SCAN_RESULTS")); - } - } else - - if (connecting_to_network(msg)) { - if (!_single_autoconnect) { - Accesspoint::Bssid const &bssid = _extract_bssid(msg, State::CONNECTING); - _connecting = bssid; - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("bssid", bssid); - xml.attribute("state", "connecting"); - }); - }); - } - } else - - if (network_not_found(msg)) { - - /* always try to update the accesspoint list */ - if (_state == State::IDLE) { - _state_transition(_state, State::PENDING_RESULTS); - _submit_cmd(Cmd_str("SCAN_RESULTS")); - } - - if (_single_autoconnect && ++_scan_attempts >= MAX_ATTEMPTS) { - _scan_attempts = 0; - _single_autoconnect = false; - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("state", "disconnected"); - xml.attribute("rfkilled", _rfkilled); - xml.attribute("not_found", true); - }); - }); - } - - } else - - { - _handle_connection_events(msg); - } - - _notify_lock_unlock(); - } - - Genode::Signal_handler _cmd_handler; - - unsigned _last_recv_id { 0 }; - - void _handle_cmds() - { - char const *msg = reinterpret_cast(_msg.recv); - unsigned const recv_id = _msg.recv_id; - - - /* return early */ - if (_last_recv_id == recv_id) { - _notify_lock_unlock(); - return; - } - - _last_recv_id = recv_id; - - switch (_state & 0xf) { - case State::SCAN: - _handle_scan_results(_state, msg); - break; - case State::NETWORK: - _handle_network_results(_state, msg); - break; - case State::STATUS: - _handle_status_result(_state, msg); - break; - case State::INFO: - _handle_info_result(_state, msg); - break; - case State::SIGNAL: - _handle_signal_poll_result(_state, msg); - break; - case State::IDLE: - default: - break; - } - _notify_lock_unlock(); - - if (_verbose_state) { - Genode::log("State:", - " connected: ", _connected_ap.bssid_valid(), - " connecting: ", _connecting.length() > 1, - " enabled: ", _count_enabled(), - " stored: ", _count_stored(), - ""); - } - - if (_state == State::IDLE && _deferred_config_update) { - _deferred_config_update = false; - _handle_config_update(); - } - - if (_state == State::IDLE && _pending_bssid.length() > 1) { - _state_transition(_state, State::INFO); - _submit_cmd(Cmd_str("BSS ", _pending_bssid)); - - _pending_bssid = Accesspoint::Bssid(); - } - } - - /** - * Constructor - */ - Frontend(Genode::Env &env, Msg_buffer &msg_buffer) - : - _ap_allocator(env.ram(), env.rm()), - _msg(msg_buffer), - _rfkill_handler(env.ep(), *this, &Wifi::Frontend::_handle_rfkill), - _config_rom(env, "wifi_config"), - _config_sigh(env.ep(), *this, &Wifi::Frontend::_handle_config_update), - _timer(env), - _timer_sigh(env.ep(), *this, &Wifi::Frontend::_handle_timer), - _events_handler(env.ep(), *this, &Wifi::Frontend::_handle_events), - _cmd_handler(env.ep(), *this, &Wifi::Frontend::_handle_cmds) - { - _config_rom.sigh(_config_sigh); - _timer.sigh(_timer_sigh); - - /* set/initialize as unblocked */ - _notify_blockade.wakeup(); - - try { - _ap_reporter.construct(env, "accesspoints", "accesspoints"); - _ap_reporter->generate([&] (Genode::Xml_generator &) { }); - } catch (...) { - Genode::warning("no Report session available, scan results will " - "not be reported"); - } - - try { - _state_reporter.construct(env, "state"); - _state_reporter->enabled(true); - - Genode::Reporter::Xml_generator xml(*_state_reporter, [&] () { - xml.node("accesspoint", [&] () { - xml.attribute("state", "disconnected"); - xml.attribute("rfkilled", _rfkilled); - }); - }); - } catch (...) { - Genode::warning("no Report session available, connectivity will " - "not be reported"); - } - - /* read in list of APs */ - _config_update(false); - - /* get initial RFKILL state */ - _handle_rfkill(); - - /* kick-off initial scanning */ - _handle_timer(); - } - - /** - * Trigger RFKILL notification - * - * Used by the wifi driver to notify front end. - */ - void rfkill_notify() override - { - _rfkill_handler.local_submit(); - } - - /** - * Get result signal capability - * - * Used by the wpa_supplicant to notify front end after processing - * a command. - */ - Genode::Signal_context_capability result_sigh() - { - return _cmd_handler; - } - - /** - * Get event signal capability - * - * Used by the wpa_supplicant to notify front whenever a event - * was triggered. - */ - Genode::Signal_context_capability event_sigh() - { - return _events_handler; - } - - /** - * Block until events were handled by the front end - * - * Used by the wpa_supplicant to wait for the front end. - */ - void block_for_processing() { _notify_lock_lock(); } -}; - -#endif /* _WIFI_FRONTEND_H_ */ diff --git a/repos/dde_linux/src/driver/wifi/main.cc b/repos/dde_linux/src/driver/wifi/main.cc index ef5842875e..18c1386bbd 100644 --- a/repos/dde_linux/src/driver/wifi/main.cc +++ b/repos/dde_linux/src/driver/wifi/main.cc @@ -28,68 +28,12 @@ /* local includes */ #include "util.h" #include "wpa.h" -#include "frontend.h" +#include "manager.h" #include "access_firmware.h" using namespace Genode; -static Msg_buffer _wifi_msg_buffer; -static Wifi::Frontend *_wifi_frontend = nullptr; - - -/** - * Notify front end about command processing - * - * Called by the CTRL interface after wpa_supplicant has processed - * the command. - */ -void wifi_block_for_processing(void) -{ - if (!_wifi_frontend) { - warning("frontend not available, dropping notification"); - return; - } - - /* - * Next time we block as long as the front end has not finished - * handling our previous request - */ - _wifi_frontend->block_for_processing(); - - /* XXX hack to trick poll() into returning faster */ - wpa_ctrl_set_fd(); -} - - -void wifi_notify_cmd_result(void) -{ - if (!_wifi_frontend) { - warning("frontend not available, dropping notification"); - return; - } - - Signal_transmitter(_wifi_frontend->result_sigh()).submit(); -} - - -/** - * Notify front end about triggered event - * - * Called by the CTRL interface whenever wpa_supplicant has triggered - * a event. - */ -void wifi_notify_event(void) -{ - if (!_wifi_frontend) { - Genode::warning("frontend not available, dropping notification"); - return; - } - - Signal_transmitter(_wifi_frontend->event_sigh()).submit(); -} - - /* exported by wifi.lib.so */ extern void wifi_init(Genode::Env&, Genode::Blockade&); extern void wifi_set_rfkill_sigh(Genode::Signal_context_capability); @@ -101,8 +45,8 @@ struct Main { Env &env; - Constructible _wpa; - Constructible _frontend; + Constructible _wpa; + Constructible _manager; struct Request_handler : Wifi::Firmware_request_handler { @@ -173,28 +117,17 @@ struct Main /* prepare Lx_kit::Env */ wifi_init(env, _wpa_startup_blockade); - _frontend.construct(env, _wifi_msg_buffer); - _wifi_frontend = &*_frontend; + _manager.construct(env); - Wifi::rfkill_establish_handler(*_wifi_frontend); + Wifi::rfkill_establish_handler(*_manager); Wifi::firmware_establish_handler(_request_handler); + Wifi::ctrl_init(_manager->msg_buffer()); _wpa.construct(env, _wpa_startup_blockade); } }; -/** - * Return shared-memory message buffer - * - * It is used by the wpa_supplicant CTRL interface. - */ -void *wifi_get_buffer(void) -{ - return &_wifi_msg_buffer; -} - - void Libc::Component::construct(Libc::Env &env) { static Main server(env); diff --git a/repos/dde_linux/src/driver/wifi/manager.h b/repos/dde_linux/src/driver/wifi/manager.h new file mode 100644 index 0000000000..93d02dcc5b --- /dev/null +++ b/repos/dde_linux/src/driver/wifi/manager.h @@ -0,0 +1,2131 @@ + /* + * \author Josef Soentgen + * \date 2018-07-31 + * + * Wifi manager uses the CTRL interface of the wpa_supplicant via a Genode + * specific ctrl_iface implementation that comprises two distinct memory + * buffers for communication - one for the command results and one for events. + */ + +/* + * Copyright (C) 2018-2024 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +#ifndef _WIFI_MANAGER_H_ +#define _WIFI_MANAGER_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* rep includes */ +#include +#include +using Ctrl_msg_buffer = Wifi::Msg_buffer; + +/* local includes */ +#include + +using namespace Genode; + +/* internal interface of lib/wifi/socket_call.cc */ +extern void wifi_kick_socketcall(); + + +namespace Wifi { + struct Manager; +} + + +enum Recv_msg_index : unsigned char { + OK = 0, + FAIL, + SCAN_RESULTS, + CONNECTED, + DISCONNECTED, + SME_AUTH, + NOT_FOUND, + MAX_INDEX, /* keep as last entry */ +}; + + +static struct { + char const *string; + size_t len; +} recv_table[Recv_msg_index::MAX_INDEX] = { + { "OK", 2 }, + { "FAIL", 4 }, + { "CTRL-EVENT-SCAN-RESULTS", 23 }, + { "CTRL-EVENT-CONNECTED", 20 }, + { "CTRL-EVENT-DISCONNECTED", 23 }, + { "SME: Trying to authenticate", 27 }, + { "CTRL-EVENT-NETWORK-NOT-FOUND", 28 }, +}; + + +static inline bool check_recv_msg(char const *msg, + auto const &entry) { + return Genode::strcmp(entry.string, msg, entry.len) == 0; } + + +static bool cmd_successful(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::OK]); } + + +static bool cmd_fail(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::FAIL]); } + + +static bool results_available(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::SCAN_RESULTS]); } + + +static bool connecting_to_network(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::SME_AUTH]); } + + +static bool network_not_found(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::NOT_FOUND]); } + + +static bool disconnected_from_network(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::DISCONNECTED]); } + + +static bool connected_to_network(char const *msg) { + return check_recv_msg(msg, recv_table[Recv_msg_index::CONNECTED]); } + + +static bool scan_results(char const *msg) { + return Genode::strcmp("bssid", msg, 5) == 0; } + + +using Cmd = String; +static void ctrl_cmd(Ctrl_msg_buffer &msg, Cmd const &cmd) +{ + memcpy(msg.send, cmd.string(), cmd.length()); + ++msg.send_id; + + wpa_ctrl_set_fd(); + + /* + * We might have to pull the socketcall task out of poll_all() + * because otherwise we might be late and wpa_supplicant has + * already removed all scan results due to BSS age settings. + */ + wifi_kick_socketcall(); +} + + +/* + * The Accesspoint object contains all information to join + * a wireless network. + */ +struct Accesspoint : Interface +{ + using Bssid = String<17+1>; + using Freq = String< 4+1>; + using Prot = String< 7+1>; + using Ssid = String<32+1>; + using Pass = String<63+1>; + + static bool valid(Ssid const &ssid) { + return ssid.length() > 1 && ssid.length() <= 32 + 1; } + + static bool valid(Pass const &pass) { + return pass.length() > 8 && pass.length() <= 63 + 1; } + + static bool valid(Bssid const &bssid) { + return bssid.length() == 17 + 1; } + + /* + * Accesspoint information fields used by manager + */ + Bssid bssid { }; + Freq freq { }; + Prot prot { }; + Ssid ssid { }; + Pass pass { }; + unsigned quality { 0 }; + + /* + * Internal configuration fields + */ + bool auto_connect { false }; + bool explicit_scan { false }; + + static Accesspoint from_xml(Xml_node const &node) + { + Accesspoint ap { }; + + ap.ssid = node.attribute_value("ssid", Accesspoint::Ssid()); + ap.bssid = node.attribute_value("bssid", Accesspoint::Bssid()); + + ap.pass = node.attribute_value("passphrase", Accesspoint::Pass("")); + ap.prot = node.attribute_value("protection", Accesspoint::Prot("NONE")); + ap.auto_connect = node.attribute_value("auto_connect", true); + ap.explicit_scan = node.attribute_value("explicit_scan", false); + + return ap; + } + + /* + * CTRL interface fields + */ + int id { -1 }; + + /** + * Default constructor + */ + Accesspoint() { } + + /** + * Constructor that initializes SSID fields + * + * It is used by the join-state handling to construct + * the connect/connecting AP. + */ + Accesspoint(Bssid const &bssid, Ssid const &ssid) + : bssid { bssid }, ssid { ssid } { } + + /** + * Constructor that initializes information fields + * + * It is used when parsing the scan results into an + * AP. + */ + Accesspoint(char const *bssid, char const *freq, + char const *prot, char const *ssid, unsigned quality) + : bssid(bssid), freq(freq), prot(prot), ssid(ssid), quality(quality) + { } + + void print(Output &out) const + { + Genode::print(out, "SSID: '", ssid, "'", " " + "BSSID: '", bssid, "'", " " + "protection: ", prot, " " + "id: ", id, " " + "quality: ", quality, " " + "auto_connect: ", auto_connect, " " + "explicit_scan: ", explicit_scan); + } + + bool wpa() const { return prot != "NONE"; } + bool wpa3() const { return prot == "WPA3"; } + bool stored() const { return id != -1; } + + bool updated_from(Accesspoint const &other) + { + bool const update = ((Accesspoint::valid(other.bssid) && other.bssid != bssid) + || pass != other.pass + || prot != other.prot + || explicit_scan != other.explicit_scan + || auto_connect != other.auto_connect); + if (!update) + return false; + + if (Accesspoint::valid(other.bssid)) + bssid = other.bssid; + + pass = other.pass; + prot = other.prot; + auto_connect = other.auto_connect; + explicit_scan = other.explicit_scan; + return true; + } +}; + + +struct Network : List_model::Element +{ + + Accesspoint _accesspoint { }; + + Network(Accesspoint const &ap) : _accesspoint { ap } { } + + virtual ~Network() { } + + void with_accesspoint(auto const &fn) { + fn(_accesspoint); } + + void with_accesspoint(auto const &fn) const { + fn(_accesspoint); } + + /************************** + ** List_model interface ** + **************************/ + + static bool type_matches(Xml_node const &node) { + return node.has_type("network"); } + + bool matches(Xml_node const &node) { + return _accesspoint.ssid == node.attribute_value("ssid", Accesspoint::Ssid()); } +}; + + +static void for_each_line(char const *msg, auto const &fn) +{ + char line_buffer[1024]; + size_t cur = 0; + + while (msg[cur] != 0) { + size_t until = Util::next_char(msg, cur, '\n'); + if (until >= sizeof(line_buffer)) { + Genode::error(__func__, ": line too large, abort processing"); + return; + } + memcpy(line_buffer, &msg[cur], until); + line_buffer[until] = 0; + cur += until + 1; + + fn(line_buffer); + } +} + + +static void for_each_result_line(char const *msg, auto const &fn) +{ + char line_buffer[1024]; + size_t cur = 0; + + /* skip headline */ + size_t until = Util::next_char(msg, cur, '\n'); + cur += until + 1; + + while (msg[cur] != 0) { + until = Util::next_char(msg, cur, '\n'); + if (until >= sizeof(line_buffer)) { + Genode::error(__func__, ": line too large, abort processing"); + return; + } + memcpy(line_buffer, &msg[cur], until); + line_buffer[until] = 0; + cur += until + 1; + + char const *s[5] = { }; + + for (size_t c = 0, i = 0; i < 5; i++) { + size_t pos = Util::next_char(line_buffer, c, '\t'); + line_buffer[c+pos] = 0; + s[i] = (char const*)&line_buffer[c]; + c += pos + 1; + } + + bool const is_wpa1 = Util::string_contains((char const*)s[3], "WPA"); + bool const is_wpa2 = Util::string_contains((char const*)s[3], "WPA2"); + bool const is_wpa3 = Util::string_contains((char const*)s[3], "SAE"); + + unsigned const quality = Util::approximate_quality(s[2]); + + char const *prot = is_wpa1 ? "WPA" : "NONE"; + prot = is_wpa2 ? "WPA2" : prot; + prot = is_wpa3 ? "WPA3" : prot; + + fn(Accesspoint(s[0], s[1], prot, s[4], quality)); + } +} + + +struct Action : Fifo::Element +{ + enum class Type : unsigned { COMMAND, QUERY }; + enum class Command : unsigned { + INVALID, ADD, DISABLE, ENABLE, EXPLICIT_SCAN, + LOG_LEVEL, REMOVE, SCAN, SCAN_RESULTS, SET, UPDATE, }; + enum class Query : unsigned { + INVALID, BSS, RSSI, STATUS, }; + + Type const type; + Command const command; + Query const query; + + bool successful; + + Action(Command cmd) + : + type { Type::COMMAND }, + command { cmd }, + query { Query::INVALID }, + successful { true } + { } + + Action(Query query) + : + type { Type::QUERY }, + command { Command::INVALID }, + query { query }, + successful { true } + { } + + virtual void execute() { } + + virtual void check(char const *) { } + + virtual void response(char const *, Accesspoint &) { } + + virtual bool complete() const = 0; + + virtual void print(Genode::Output &) const = 0; +}; + + +/* + * Action for adding a new network + * + * In case the 'auto_connect' option is set for the network it + * will also be enabled to active auto-joining. + */ +struct Add_network_cmd : Action +{ + enum class State : unsigned { + INIT, ADD_NETWORK, FILL_NETWORK_SSID, FILL_NETWORK_BSSID, + FILL_NETWORK_KEY_MGMT, SET_NETWORK_PMF, FILL_NETWORK_PSK, + SET_SCAN_SSID, ENABLE_NETWORK, COMPLETE + }; + + Ctrl_msg_buffer &_msg; + Accesspoint _accesspoint; + State _state; + + Add_network_cmd(Ctrl_msg_buffer &msg, Accesspoint const &ap) + : + Action { Command::ADD }, + _msg { msg }, + _accesspoint { ap }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Add_network_cmd[", (unsigned)_state, + "] '", _accesspoint.ssid, "'"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("ADD_NETWORK")); + _state = State::ADD_NETWORK; + break; + case State::ADD_NETWORK: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " ssid \"", _accesspoint.ssid, "\"")); + _state = State::FILL_NETWORK_SSID; + break; + case State::FILL_NETWORK_SSID: + { + bool const valid = Accesspoint::valid(_accesspoint.bssid); + char const *bssid = valid ? _accesspoint.bssid.string() : ""; + + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " bssid ", bssid)); + _state = State::FILL_NETWORK_BSSID; + break; + } + case State::FILL_NETWORK_BSSID: + if (_accesspoint.wpa3()) { + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " key_mgmt SAE")); + _state = State::FILL_NETWORK_KEY_MGMT; + } else { + if (_accesspoint.wpa()) + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " psk \"", _accesspoint.pass, "\"")); + else + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " key_mgmt NONE")); + _state = State::FILL_NETWORK_PSK; + } + break; + case State::FILL_NETWORK_KEY_MGMT: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " ieee80211w 2")); + _state = State::SET_NETWORK_PMF; + break; + case State::SET_NETWORK_PMF: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " psk \"", _accesspoint.pass, "\"")); + _state = State::FILL_NETWORK_PSK; + break; + case State::FILL_NETWORK_PSK: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " scan_ssid ", + _accesspoint.explicit_scan ? "1" : "0")); + _state = State::SET_SCAN_SSID; + break; + case State::SET_SCAN_SSID: + if (_accesspoint.auto_connect) { + ctrl_cmd(_msg, Cmd("ENABLE_NETWORK ", _accesspoint.id)); + _state = State::ENABLE_NETWORK; + } else + _state = State::COMPLETE; + break; + case State::ENABLE_NETWORK: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + /* + * Handle response by expected failure handling + * and use fallthrough switch cases to reduce code. + */ + switch (_state) { + case State::INIT: break; + + case State::ADD_NETWORK: + if (cmd_fail(msg)) { + error("ADD_NETWORK(", (unsigned)_state, ") failed: ", msg); + Action::successful = false; + complete = true; + } + break; + + case State::FILL_NETWORK_SSID: [[fallthrough]]; + case State::FILL_NETWORK_BSSID: [[fallthrough]]; + case State::FILL_NETWORK_KEY_MGMT: [[fallthrough]]; + case State::SET_NETWORK_PMF: [[fallthrough]]; + case State::FILL_NETWORK_PSK: [[fallthrough]]; + case State::SET_SCAN_SSID: [[fallthrough]]; + case State::ENABLE_NETWORK: + if (!cmd_successful(msg)) { + error("ADD_NETWORK(", (unsigned)_state, ") failed: ", msg); + Action::successful = false; + complete = true; + } + break; + case State::COMPLETE: break; + } + + if (complete) { + _state = State::COMPLETE; + return; + } + + switch (_state) { + case State::INIT: break; + case State::ADD_NETWORK: + { + long id = -1; + ascii_to(msg, id); + _accesspoint.id = static_cast(id); + break; + } + case State::FILL_NETWORK_SSID: break; + case State::FILL_NETWORK_BSSID: break; + case State::FILL_NETWORK_KEY_MGMT: break; + case State::SET_NETWORK_PMF: break; + case State::FILL_NETWORK_PSK: break; + case State::SET_SCAN_SSID: break; + case State::ENABLE_NETWORK: break; + case State::COMPLETE: break; + } + } + + Accesspoint const &accesspoint() const + { + return _accesspoint; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for removing a network + */ +struct Remove_network_cmd : Action +{ + enum class State : unsigned { + INIT, REMOVE_NETWORK, COMPLETE + }; + + Ctrl_msg_buffer &_msg; + int _id; + State _state; + + Remove_network_cmd(Ctrl_msg_buffer &msg, int id) + : + Action { Command::REMOVE }, + _msg { msg }, + _id { id }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Remove_network_cmd[", (unsigned)_state, "] id: ", _id); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("REMOVE_NETWORK ", _id)); + _state = State::REMOVE_NETWORK; + break; + case State::REMOVE_NETWORK: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::REMOVE_NETWORK: + if (cmd_fail(msg)) { + error("could not remove network: ", msg); + Action::successful = false; + complete = true; + } + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for updating a network + * + * For now only the PSK is updated and depending on the + * auto_connect configuration the network will also be + * enabled to allow for auto-join after the alteration. + */ +struct Update_network_cmd : Action +{ + enum class State : unsigned { + INIT, UPDATE_NETWORK_PSK, + DISABLE_NETWORK, SET_SCAN_SSID, + ENABLE_NETWORK, COMPLETE + }; + Ctrl_msg_buffer &_msg; + Accesspoint _accesspoint; + State _state; + + Update_network_cmd(Ctrl_msg_buffer &msg, Accesspoint const &ap) + : + Action { Command::UPDATE }, + _msg { msg }, + _accesspoint { ap }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Update_network_cmd[", (unsigned)_state, + "] id: ", _accesspoint.id); + } + + void execute() override + { + // XXX change to disable -> psk ?-> enable + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " psk \"", _accesspoint.pass, "\"")); + _state = State::UPDATE_NETWORK_PSK; + break; + case State::UPDATE_NETWORK_PSK: + ctrl_cmd(_msg, Cmd("DISABLE_NETWORK ", _accesspoint.id)); + _state = State::SET_SCAN_SSID; + break; + case State::SET_SCAN_SSID: + ctrl_cmd(_msg, Cmd("SET_NETWORK ", _accesspoint.id, + " scan_ssid ", + _accesspoint.explicit_scan ? "1" : "0")); + _state = State::DISABLE_NETWORK; + break; + case State::DISABLE_NETWORK: + if (_accesspoint.auto_connect) { + ctrl_cmd(_msg, Cmd("ENABLE_NETWORK ", _accesspoint.id)); + _state = State::ENABLE_NETWORK; + } else { + _state = State::COMPLETE; + } + break; + case State::ENABLE_NETWORK: + _state = State::COMPLETE; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::UPDATE_NETWORK_PSK: [[fallthrough]]; + case State::ENABLE_NETWORK: [[fallthrough]]; + case State::SET_SCAN_SSID: [[fallthrough]]; + case State::DISABLE_NETWORK: + if (!cmd_successful(msg)) { + error("UPDATE_NETWORK(", (unsigned)_state, ") failed: ", msg); + Action::successful = false; + complete = true; + } + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for initiating a scan request + */ +struct Scan_cmd : Action +{ + enum class State : unsigned { + INIT, SCAN, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Scan_cmd(Ctrl_msg_buffer &msg) + : + Action { Command::SCAN }, + _msg { msg }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Scan_cmd[", (unsigned)_state, "]"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("SCAN")); + _state = State::SCAN; + break; + case State::SCAN: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::SCAN: + if (!cmd_successful(msg)) { + /* ignore busy fails silently */ + bool const scan_busy = strcmp(msg, "FAIL-BUSY"); + if (!scan_busy) { + error("could not initiate scan: ", msg); + Action::successful = false; + complete = true; + } + } + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for initiating a scan results request + */ +struct Scan_results_cmd : Action +{ + enum class State : unsigned { + INIT, SCAN_RESULTS, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Expanding_reporter &_reporter; + + void _generate_report(char const *msg) + { + unsigned count_lines = 0; + for_each_line(msg, [&] (char const*) { count_lines++; }); + + if (!count_lines) + return; + + try { + _reporter.generate([&] (Xml_generator &xml) { + + for_each_result_line(msg, [&] (Accesspoint const &ap) { + + /* ignore potentially empty ssids */ + if (ap.ssid == "") + return; + + xml.node("accesspoint", [&]() { + xml.attribute("ssid", ap.ssid); + xml.attribute("bssid", ap.bssid); + xml.attribute("freq", ap.freq); + xml.attribute("quality", ap.quality); + if (ap.wpa()) { xml.attribute("protection", ap.prot); } + }); + }); + }); + + } catch (...) { /* silently omit report */ } + } + + Scan_results_cmd(Ctrl_msg_buffer &msg, + Genode::Expanding_reporter &reporter) + : + Action { Command::SCAN_RESULTS }, + _msg { msg }, + _state { State::INIT }, + _reporter { reporter } + { } + + void print(Output &out) const override + { + Genode::print(out, "Scan_results_cmd[", (unsigned)_state, "]"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("SCAN_RESULTS")); + _state = State::SCAN_RESULTS; + break; + case State::SCAN_RESULTS: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::SCAN_RESULTS: + if (scan_results(msg)) + _generate_report(msg); + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for setting a configuration variable + */ +struct Set_cmd : Action +{ + using Key = String<64>; + using Value = String<128>; + + enum class State : unsigned { + INIT, SET, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Key _key; + Value _value; + + Set_cmd(Ctrl_msg_buffer &msg, Key key, Value value) + : + Action { Command::SET }, + _msg { msg }, + _state { State::INIT }, + _key { key }, + _value { value } + { } + + void print(Output &out) const override + { + Genode::print(out, "Set_cmd[", (unsigned)_state, "] key: '", + _key, "' value: '", _value, "'"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("SET ", _key, " \"", _value, "\"")); + _state = State::SET; + break; + case State::SET: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::SET: + if (!cmd_successful(msg)) { + error("could not set '", _key, "' to '", + _value, "': '", msg, "'"); + Action::successful = false; + complete = true; + } + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for setting a configuration variable + */ +struct Log_level_cmd : Action +{ + using Level = Genode::String<16>; + + enum class State : unsigned { + INIT, LOG_LEVEL, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Level _level; + + Log_level_cmd(Ctrl_msg_buffer &msg, Level const &level) + : + Action { Command::LOG_LEVEL }, + _msg { msg }, + _state { State::INIT }, + _level { level } + { } + + void print(Output &out) const override + { + Genode::print(out, "Log_level_cmd[", (unsigned)_state, "] '", _level, "'"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("LOG_LEVEL ", _level)); + _state = State::LOG_LEVEL; + break; + case State::LOG_LEVEL: + _state = State::COMPLETE; + break; + case State::COMPLETE: + break; + } + } + + void check(char const *msg) override + { + using namespace Genode; + + bool complete = false; + + switch (_state) { + case State::INIT: break; + case State::LOG_LEVEL: + if (!cmd_successful(msg)) { + error("could not set LOG_LEVEL to ", _level); + Action::successful = false; + complete = true; + } + break; + case State::COMPLETE: break; + } + + if (complete) + _state = State::COMPLETE; + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for querying BSS information + */ +struct Bss_query : Action +{ + enum class State : unsigned { + INIT, BSS, COMPLETE + }; + Ctrl_msg_buffer &_msg; + Accesspoint::Bssid _bssid; + State _state; + + Bss_query(Ctrl_msg_buffer &msg, Accesspoint::Bssid bssid) + : + Action { Query::BSS }, + _msg { msg }, + _bssid { bssid }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Bss_query[", (unsigned)_state, "] ", _bssid); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("BSS ", _bssid)); + _state = State::BSS; + break; + case State::BSS: break; + case State::COMPLETE: break; + } + } + + void response(char const *msg, Accesspoint &ap) override + { + if (_state != State::BSS) + return; + + _state = State::COMPLETE; + + /* + * It might happen that the supplicant already flushed + * its internal BSS information and cannot help us out. + * Since we already sent out a rudimentary report, just + * stop here. + */ + if (0 == msg[0]) + return; + + auto fill_ap = [&] (char const *line) { + if (Genode::strcmp(line, "ssid=", 5) == 0) { + ap.ssid = Accesspoint::Ssid(line+5); + } else + + if (Genode::strcmp(line, "bssid=", 6) == 0) { + ap.bssid = Accesspoint::Bssid(line+6); + } else + + if (Genode::strcmp(line, "freq=", 5) == 0) { + ap.freq = Accesspoint::Freq(line+5); + } + }; + for_each_line(msg, fill_ap); + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for querying RSSI information + */ +struct Rssi_query : Action +{ + enum class State : unsigned { + INIT, RSSI, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Rssi_query(Ctrl_msg_buffer &msg) + : + Action { Query::RSSI }, + _msg { msg }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Rssi_query[", (unsigned)_state, "]"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("SIGNAL_POLL")); + _state = State::RSSI; + break; + case State::RSSI: break; + case State::COMPLETE: break; + } + } + + void response(char const *msg, Accesspoint &ap) override + { + if (_state != State::RSSI) + return; + + _state = State::COMPLETE; + + using Rssi = Genode::String<5>; + Rssi rssi { }; + auto get_rssi = [&] (char const *line) { + if (strcmp(line, "RSSI=", 5) != 0) + return; + + rssi = Rssi(line + 5); + }; + for_each_line(msg, get_rssi); + + /* + * Use the same simplified approximation for denoting + * the quality to be in line with the scan results. + */ + ap.quality = Util::approximate_quality(rssi.valid() ? rssi.string() + : "-100"); + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Action for querying the current connection status + */ +struct Status_query : Action +{ + enum class State : unsigned { + INIT, STATUS, COMPLETE + }; + Ctrl_msg_buffer &_msg; + State _state; + + Status_query(Ctrl_msg_buffer &msg) + : + Action { Query::STATUS }, + _msg { msg }, + _state { State::INIT } + { } + + void print(Output &out) const override + { + Genode::print(out, "Status_query[", (unsigned)_state, "]"); + } + + void execute() override + { + switch (_state) { + case State::INIT: + ctrl_cmd(_msg, Cmd("STATUS")); + _state = State::STATUS; + break; + case State::STATUS: break; + case State::COMPLETE: break; + } + } + + void response(char const *msg, Accesspoint &ap) override + { + if (_state != State::STATUS) + return; + + _state = State::COMPLETE; + + /* + * It might happen that the supplicant already flushed + * its internal BSS information and cannot help us out. + * Since we already sent out a rudimentary report, just + * stop here. + */ + if (0 == msg[0]) + return; + + auto fill_ap = [&] (char const *line) { + if (strcmp(line, "ssid=", 5) == 0) { + ap.ssid = Accesspoint::Ssid(line+5); + } else + + if (strcmp(line, "bssid=", 6) == 0) { + ap.bssid = Accesspoint::Bssid(line+6); + } else + + if (strcmp(line, "freq=", 5) == 0) { + ap.freq = Accesspoint::Freq(line+5); + } + }; + for_each_line(msg, fill_ap); + } + + bool complete() const override { + return _state == State::COMPLETE; } +}; + + +/* + * Wifi driver manager + */ +struct Wifi::Manager : Wifi::Rfkill_notification_handler +{ + Manager(const Manager&) = delete; + Manager& operator=(const Manager&) = delete; + + /* Network handling */ + + Heap _network_allocator; + List_model _network_list { }; + + /* + * Action queue handling + */ + + Heap _actions_alloc; + Fifo _actions { }; + + Action *_pending_action { nullptr }; + + void _queue_action(Action &action, bool verbose) + { + _actions.enqueue(action); + if (verbose) + Genode::log("Queue ", action); + } + + enum class Pending_action_result : unsigned { + INCOMPLETE, COMPLETE }; + + void _with_pending_action(auto const &fn) + { + if (!_pending_action) + _actions.dequeue([&] (Action &action) { + _pending_action = &action; }); + + Pending_action_result const result = + _pending_action ? fn(*_pending_action) + : Pending_action_result::INCOMPLETE; + if (result == Pending_action_result::COMPLETE) { + destroy(_actions_alloc, _pending_action); + _pending_action = nullptr; + } + } + + void _dispatch_action_if_needed() + { + if (_pending_action) + return; + + /* + * Grab the next action and call execute() + * to poke the CTRL interface. + */ + + _actions.dequeue([&] (Action &action) { + _pending_action = &action; + _pending_action->execute(); + }); + } + + Signal_handler _cmd_handler; + Signal_handler _events_handler; + + Blockade _notify_blockade { }; + + struct Notify : Notify_interface + { + Signal_handler &_response; + Signal_handler &_event; + Blockade &_blockade; + + Notify(Signal_handler &response, + Signal_handler &event, + Blockade &blockade) + : + _response { response }, + _event { event }, + _blockade { blockade } + { } + + /********************** + ** Notify_interface ** + **********************/ + + void submit_response() override { + _response.local_submit(); } + + void submit_event() override { + _event.local_submit(); } + + void block_for_processing() override { + _blockade.block(); } + } _notify { _cmd_handler, _events_handler, _notify_blockade }; + + Msg_buffer _msg { _notify }; + + void _handle_rfkill() + { + _join.rfkilled = Wifi::rfkill_blocked(); + + /* re-enable scan timer */ + if (!_join.rfkilled) + _try_arming_any_timer(); + } + + Signal_handler _rfkill_handler; + + /* + * Configuration handling + */ + + Attached_rom_dataspace _config_rom; + Signal_handler _config_sigh; + + struct Config + { + enum { + DEFAULT_CONNECTED_SCAN_INTERVAL = 30, + DEFAULT_SCAN_INTERVAL = 5, + DEFAULT_UPDATE_QUAILITY_INTERVAL = 30, + + DEFAULT_VERBOSE = false, + DEFAULT_RFKILL = false, + }; + + unsigned scan_interval { DEFAULT_SCAN_INTERVAL }; + unsigned update_quality_interval { DEFAULT_UPDATE_QUAILITY_INTERVAL }; + + bool intervals_changed(Config const &cfg) const + { + return scan_interval != cfg.scan_interval + || update_quality_interval != cfg.update_quality_interval; + } + + bool verbose { DEFAULT_VERBOSE }; + bool rfkill { DEFAULT_RFKILL }; + + bool rfkill_changed(Config const &cfg) const { + return rfkill != cfg.rfkill; } + + /* see wpa_debug.h - EXCESSIVE, MSGDUMP, DEBUG, INFO, WARNING, ERROR */ + using Log_level = Log_level_cmd::Level; + Log_level log_level { "" }; + + bool log_level_changed(Config const &cfg) const { + return log_level != cfg.log_level; } + + bool log_level_set() const { + return log_level.length() > 1; } + + using Bgscan = Genode::String<16>; + Bgscan bgscan { "" }; + + bool bgscan_changed(Config const &cfg) const { + return bgscan != cfg.bgscan; } + + bool bgscan_set() const { + return bgscan.length() >= 1; } + + static Config from_xml(Xml_node const &node) + { + bool const verbose = node.attribute_value("verbose", + (bool)DEFAULT_VERBOSE); + bool const rfkill = node.attribute_value("rfkill", + (bool)DEFAULT_RFKILL); + Log_level log_level = + node.attribute_value("log_level", Log_level("error")); + /* always enforce at leaast error level of verbosity */ + if (log_level.length() <= 1) + log_level = Log_level("error"); + + Bgscan const bgscan = + node.attribute_value("bgscan", Bgscan("simple:30:-70:600")); + + unsigned const scan_interval = + Util::check_time(node.attribute_value("scan_interval", + (unsigned)DEFAULT_SCAN_INTERVAL), + 5, 15*60); + + unsigned const update_quality_interval = + Util::check_time(node.attribute_value("update_quality_interval", + (unsigned)DEFAULT_UPDATE_QUAILITY_INTERVAL), + 10, 15*60); + + Config new_config { + .scan_interval = scan_interval, + .update_quality_interval = update_quality_interval, + .verbose = verbose, + .rfkill = rfkill, + .log_level = log_level, + .bgscan = bgscan + }; + return new_config; + } + }; + + Config _config { }; + + void _config_update(bool initial_config) + { + _config_rom.update(); + + if (!_config_rom.valid()) + return; + + Xml_node const config_node = _config_rom.xml(); + + Config const old_config = _config; + + _config = Config::from_xml(config_node); + + if (_config.intervals_changed(old_config) || initial_config) + _try_arming_any_timer(); + + if (_config.rfkill_changed(old_config) || initial_config) { + Wifi::set_rfkill(_config.rfkill); + + /* + * In case we get blocked set rfkilled immediately to prevent + * any further scanning operation. The actual value will be set + * by the singal handler but is not expected to be any different + * as the rfkill call is not supposed to fail. + */ + if (_config.rfkill && !_join.rfkilled) + _join.rfkilled = true; + } + + if (_config.log_level_changed(old_config) || initial_config) + if (_config.log_level_set()) + _queue_action(*new (_actions_alloc) + Log_level_cmd(_msg, _config.log_level), _config.verbose); + + if (_config.bgscan_changed(old_config) || initial_config) + if (_config.bgscan_set()) + _queue_action(*new (_actions_alloc) + Set_cmd(_msg, Set_cmd::Key("bgscan"), + Set_cmd::Value(_config.bgscan)), + _config.verbose); + + _network_list.update_from_xml(config_node, + + [&] (Genode::Xml_node const &node) -> Network & { + + Accesspoint const ap = Accesspoint::from_xml(node); + + bool const ssid_invalid = !Accesspoint::valid(ap.ssid); + if (ssid_invalid) + warning("accesspoint has invalid ssid: '", ap.ssid, "'"); + + bool const pass_invalid = ap.wpa() && !Accesspoint::valid(ap.pass); + if (pass_invalid) + warning("accesspoint '", ap.ssid, "' has invalid psk"); + + /* + * Only make the supplicant acquainted with the network + * when it is usable but created the Network object nonetheless + * to satisfy the List_model requirements. + */ + if (!ssid_invalid || !pass_invalid) + _queue_action(*new (_actions_alloc) + Add_network_cmd(_msg, ap), _config.verbose); + + return *new (_network_allocator) Network(ap); + }, + [&] (Network &network) { + + network.with_accesspoint([&] (Accesspoint &ap) { + + if (!Accesspoint::valid(ap.ssid) || !ap.stored()) + return; + + _queue_action(*new (_actions_alloc) + Remove_network_cmd(_msg, ap.id), _config.verbose); + }); + + Genode::destroy(_network_allocator, &network); + }, + [&] (Network &network, Genode::Xml_node const &node) { + Accesspoint const updated_ap = Accesspoint::from_xml(node); + + network.with_accesspoint([&] (Accesspoint &ap) { + + if (!ap.updated_from(updated_ap)) + return; + + if (!ap.stored()) + return; + + _queue_action(*new (_actions_alloc) + Update_network_cmd(_msg, ap), _config.verbose); + }); + }); + + _dispatch_action_if_needed(); + } + + void _handle_config_update() { _config_update(false); } + + /* + * Timeout handling + */ + + Timer::Connection _timer; + + Timer::One_shot_timeout _scan_timeout { + _timer, *this, &Wifi::Manager::_handle_scan_timeout }; + + Timer::One_shot_timeout _quality_timeout { + _timer, *this, &Wifi::Manager::_handle_quality_timeout }; + + enum class Timer_type : uint8_t { SCAN, SIGNAL_POLL }; + + bool _arm_timer(Timer_type const type) + { + auto seconds_from_type = [&] (Timer_type const type) { + switch (type) { + case Timer_type::SCAN: return _config.scan_interval; + case Timer_type::SIGNAL_POLL: return _config.update_quality_interval; + } + return 0u; + }; + + Microseconds const us { seconds_from_type(type) * 1000'000u }; + if (!us.value) + return false; + + if (_config.verbose) { + auto name_from_type = [&] (Timer_type const type) { + switch (type) { + case Timer_type::SCAN: return "scan"; + case Timer_type::SIGNAL_POLL: return "signal-poll"; + } + return ""; + }; + + log("Arm timer for ", name_from_type(type), ": ", us); + } + + switch (type) { + case Timer_type::SCAN: _scan_timeout.schedule(us); break; + case Timer_type::SIGNAL_POLL: _quality_timeout.schedule(us); break; + } + return true; + } + + bool _arm_scan_timer() + { + if (_join.state == Join_state::State::CONNECTED) + return false; + + return _arm_timer(Timer_type::SCAN); + } + + bool _arm_poll_timer() + { + if (_join.state != Join_state::State::CONNECTED) + return false; + + return _arm_timer(Timer_type::SIGNAL_POLL); + } + + void _try_arming_any_timer() + { + _arm_scan_timer(); + _arm_poll_timer(); + } + + void _handle_scan_timeout(Genode::Duration) + { + if (_join.rfkilled) { + if (_config.verbose) + log("Scanning: suspend due to RFKILL"); + return; + } + + if (!_arm_scan_timer()) { + if (_config.verbose) + log("Timer: scanning disabled"); + return; + } + + _queue_action(*new (_actions_alloc) Scan_cmd(_msg), _config.verbose); + + _dispatch_action_if_needed(); + } + + void _handle_quality_timeout(Genode::Duration) + { + if (_join.rfkilled) { + if (_config.verbose) + log("Quality polling: suspend due to RFKIL"); + return; + } + + if (!_arm_poll_timer()) { + if (_config.verbose) + log("Timer: signal-strength polling disabled"); + return; + } + + _queue_action(*new (_actions_alloc) Rssi_query(_msg), _config.verbose); + + _dispatch_action_if_needed(); + } + + /* + * CTRL interface event handling + */ + + Constructible _state_reporter { }; + Constructible _ap_reporter { }; + + enum class Bssid_offset : unsigned { + /* by the power of wc -c, I have the start pos... */ + CONNECT = 37, CONNECTING = 33, DISCONNECT = 30, }; + + Accesspoint::Bssid const _extract_bssid(char const *msg, Bssid_offset offset) + { + char bssid[32] = { }; + + size_t const len = 17; + size_t const start = (size_t)offset; + + memcpy(bssid, msg + start, len); + return Accesspoint::Bssid((char const*)bssid); + } + + Accesspoint::Ssid const _extract_ssid(char const *msg) + { + char ssid[64] = { }; + size_t const start = 58; + + /* XXX assume "SME:.*SSID='xx xx' ...)", so look for the + * closing ' but we _really_ should use something like + * printf_encode/printf_deccode functions + * (see wpa_supplicant/src/utils/common.c) and + * remove our patch… + */ + size_t const len = Util::next_char(msg, start, 0x27); + if (!len || len >= 33) + return Accesspoint::Ssid(); + + memcpy(ssid, msg + start, len); + + return Accesspoint::Ssid((char const *)ssid); + } + + enum class Auth_result : unsigned { + OK, FAILED, INVALIDED }; + + Auth_result _auth_result(char const *msg) + { + enum { REASON_OFFSET = 55, }; + unsigned reason = 0; + ascii_to((msg + REASON_OFFSET), reason); + switch (reason) { + case 2: /* prev auth no longer valid */ + return Auth_result::INVALIDED; + case 15: /* 4-way handshake timeout/failed */ + return Auth_result::FAILED; + default: + return Auth_result::OK; + } + } + + struct Join_state + { + enum class State : unsigned { + DISCONNECTED, CONNECTING, CONNECTED }; + + Accesspoint ap { }; + + State state { DISCONNECTED }; + + bool auth_failure { false }; + bool not_found { false }; + bool rfkilled { false }; + + enum { MAX_REAUTH_ATTEMPTS = 3 }; + unsigned reauth_attempts { 0 }; + + enum { MAX_NOT_FOUND_IGNORE_ATTEMPTS = 3 }; + unsigned ignore_not_found { 0 }; + + void print(Output &out) const + { + auto state_string = [&] (State const state) { + switch (state) { + case State::DISCONNECTED: return "disconnected"; + case State::CONNECTED: return "connected"; + case State::CONNECTING: return "connecting"; + } + return ""; + }; + Genode::print(out, state_string(state), " " + "ssid: '", ap.ssid, "' " + "bssid: ", ap.bssid, " " + "freq: ", ap.freq, " " + "quality: ", ap.quality, " " + "auth_failure: ", auth_failure, " " + "reauth_attempts: ", reauth_attempts, " " + "not_found: ", not_found, " " + "ignore_not_found: ", ignore_not_found, " " + "rfkilled: ", rfkilled); + } + + void generate_state_report_if_needed(Expanding_reporter &reporter, + Join_state const &old) + { + /* + * Explicitly check for the all changes provoked by + * actions or events. + */ + if (state == old.state + && ap.quality == old.ap.quality + && ap.ssid == old.ap.ssid + && ap.bssid == old.ap.bssid + && ap.freq == old.ap.freq) + return; + + reporter.generate([&] (Xml_generator &xml) { + xml.node("accesspoint", [&] () { + xml.attribute("ssid", ap.ssid); + xml.attribute("bssid", ap.bssid); + xml.attribute("freq", ap.freq); + + if (state == Join_state::State::CONNECTED) + xml.attribute("state", "connected"); + else + + if (state == Join_state::State::DISCONNECTED) { + xml.attribute("state", "disconnected"); + xml.attribute("rfkilled", rfkilled); + xml.attribute("auth_failure", auth_failure); + xml.attribute("not_found", not_found); + } else + + if (state == Join_state::State::CONNECTING) + xml.attribute("state", "connecting"); + + /* + * Only add the attribute when we have something + * to report so that a consumer of the state report + * may take appropriate actions. + */ + if (ap.quality) + xml.attribute("quality", ap.quality); + }); + }); + } + }; + + Join_state _join { }; + + bool _single_autoconnect() const + { + unsigned count = 0; + _network_list.for_each([&] (Network const &network) { + network.with_accesspoint([&] (Accesspoint const &ap) { + count += ap.auto_connect; }); }); + return count == 1; + } + + void _handle_events() + { + Join_state const old_join = _join; + + _msg.with_new_event([&] (char const *msg) { + + /* + * CTRL-EVENT-SCAN-RESULTS + */ + if (results_available(msg)) { + + /* + * We might have to pull the socketcall task out of poll_all() + * because otherwise we might be late and wpa_supplicant has + * already removed all scan results due to BSS age settings. + */ + wifi_kick_socketcall(); + + _queue_action(*new (_actions_alloc) + Scan_results_cmd(_msg, *_ap_reporter), _config.verbose); + } else + + /* + * SME: Trying to authenticate with ... + */ + if (connecting_to_network(msg)) { + + _join.state = Join_state::State::CONNECTING; + _join.ap = Accesspoint(_extract_bssid(msg, Bssid_offset::CONNECTING), + _extract_ssid(msg)); + _join.auth_failure = false; + _join.not_found = false; + } else + + /* + * CTRL-EVENT-NETWORK-NOT-FOUND + */ + if (network_not_found(msg)) { + + /* + * In case there is only one auto-connect network configured + * generate a disconnect event so that a management component + * can deal with that situation. However, we do not disable the + * network to allow for automatically rejoining a reapparing + * network that was previously not found. + * + * This may happen when an accesspoint is power-cycled or when + * there is a key management mismatch due to operator error. + * Unfortunately we cannot easily distinguish a wrongly prepared + * where the 'protection' attribute does not match + * as we do not have the available accesspoints at hand to compare + * that. + */ + if ((_join.state == Join_state::State::CONNECTING) && _single_autoconnect()) { + + /* + * Ignore the event for a while as it may happen that hidden + * networks may take some time. + */ + if (++_join.ignore_not_found >= Join_state::MAX_NOT_FOUND_IGNORE_ATTEMPTS) { + _join.ignore_not_found = 0; + + _network_list.for_each([&] (Network &network) { + network.with_accesspoint([&] (Accesspoint &ap) { + + if (ap.ssid != _join.ap.ssid) + return; + + _join.state = Join_state::State::DISCONNECTED; + _join.ap = Accesspoint(); + _join.not_found = true; + }); + }); + } + } + } else + + /* + * CTRL-EVENT-DISCONNECTED ... reason=... + */ + if (disconnected_from_network(msg)) { + + Join_state::State const old_state = _join.state; + + Auth_result const auth_result = _auth_result(msg); + + _join.auth_failure = auth_result != Auth_result::OK; + _join.state = Join_state::State::DISCONNECTED; + _join.not_found = false; + + Accesspoint::Bssid const bssid = + _extract_bssid(msg, Bssid_offset::DISCONNECT); + + if (bssid != _join.ap.bssid) + warning(bssid, " does not match stored ", _join.ap.bssid); + + /* + * Use a simplistic heuristic to ignore re-authentication requests + * and hope for the supplicant to do its magic. + */ + if ((old_state == Join_state::State::CONNECTED) && _join.auth_failure) + if (++_join.reauth_attempts <= Join_state::MAX_REAUTH_ATTEMPTS) { + log("ignore deauth from: ", bssid); + return; + } + _join.reauth_attempts = 0; + + _network_list.for_each([&] (Network &network) { + network.with_accesspoint([&] (Accesspoint &ap) { + + if (ap.ssid != _join.ap.ssid) + return; + + if (!_join.auth_failure) + return; + + /* + * Prevent the supplicant from trying to join the network + * again. At this point intervention by the management + * component is needed. + */ + ap.auto_connect = false; + + _queue_action(*new (_actions_alloc) + Update_network_cmd(_msg, ap), _config.verbose); + }); + }); + } else + + /* + * CTRL-EVENT-CONNECTED - Connection to ... + */ + if (connected_to_network(msg)) { + + _join.state = Join_state::State::CONNECTED; + _join.ap.bssid = _extract_bssid(msg, Bssid_offset::CONNECT); + _join.auth_failure = false; + _join.not_found = false; + _join.reauth_attempts = 0; + + /* collect further information like frequency and so on */ + _queue_action(*new (_actions_alloc) Status_query(_msg), + _config.verbose); + + _arm_poll_timer(); + } + }); + + _notify_blockade.wakeup(); + + _join.generate_state_report_if_needed(*_state_reporter, old_join); + + _dispatch_action_if_needed(); + } + + /* + * CTRL interface command handling + */ + + void _handle_cmds() + { + Join_state const old_join = _join; + + _msg.with_new_reply([&] (char const *msg) { + + _with_pending_action([&] (Action &action) { + + /* + * Check response first as we ended up here due + * to an already submitted cmd. + */ + switch (action.type) { + + case Action::Type::COMMAND: + action.check(msg); + break; + + case Action::Type::QUERY: + action.response(msg, _join.ap); + break; + } + + /* + * We always switch to the next state after checking and + * handling the response from the CTRL interface. + */ + action.execute(); + + if (!action.complete()) + return Pending_action_result::INCOMPLETE; + + switch (action.command) { + case Action::Command::ADD: + { + Add_network_cmd const &add_cmd = + *dynamic_cast(&action); + + bool handled = false; + Accesspoint const &added_ap = add_cmd.accesspoint(); + _network_list.for_each([&] (Network &network) { + network.with_accesspoint([&] (Accesspoint &ap) { + if (ap.ssid != added_ap.ssid) + return; + + if (ap.stored()) { + error("accesspoint for SSID '", ap.ssid, "' " + "already stored ", ap.id); + return; + } + + ap.id = added_ap.id; + handled = true; + }); + }); + + /* + * We have to guard against having the accesspoint removed via a config + * update while we are still adding it to the supplicant by removing the + * + * network directly afterwards. + */ + if (!handled) { + _queue_action(*new (_actions_alloc) + Remove_network_cmd(_msg, added_ap.id), _config.verbose); + } else + + if (handled && _single_autoconnect()) + /* + * To accomodate a management component that only deals + * with one network, e.g. the sculpt_manager, generate a + * fake connecting event. Either a connected or disconnected + * event will bring us to square one. + */ + if ((_join.state != Join_state::State::CONNECTED) && !_join.rfkilled) { + _network_list.for_each([&] (Network const &network) { + network.with_accesspoint([&] (Accesspoint const &ap) { + + _join.ap = ap; + _join.state = Join_state::State::CONNECTING; + }); + }); + } + + break; + } + default: /* ignore the rest */ + break; + } + + return Pending_action_result::COMPLETE; + }); + }); + + _notify_blockade.wakeup(); + + _join.generate_state_report_if_needed(*_state_reporter, old_join); + + _dispatch_action_if_needed(); + } + + /** + * Constructor + */ + Manager(Env &env) + : + _network_allocator(env.ram(), env.rm()), + _actions_alloc(env.ram(), env.rm()), + _cmd_handler(env.ep(), *this, &Wifi::Manager::_handle_cmds), + _events_handler(env.ep(), *this, &Wifi::Manager::_handle_events), + _rfkill_handler(env.ep(), *this, &Wifi::Manager::_handle_rfkill), + _config_rom(env, "wifi_config"), + _config_sigh(env.ep(), *this, &Wifi::Manager::_handle_config_update), + _timer(env) + { + _config_rom.sigh(_config_sigh); + + /* set/initialize as unblocked */ + _notify_blockade.wakeup(); + + /* + * Both Report sessions are mandatory, let the driver fail in + * case they cannot be created. + */ + { + _ap_reporter.construct(env, "accesspoints", "accesspoints"); + _ap_reporter->generate([&] (Genode::Xml_generator &) { }); + } + + { + _state_reporter.construct(env, "state", "state"); + _state_reporter->generate([&] (Genode::Xml_generator &xml) { + xml.node("accesspoint", [&] () { + xml.attribute("state", "disconnected"); + }); + }); + } + + /* read in list of APs */ + _config_update(true); + + /* get initial RFKILL state */ + _handle_rfkill(); + + /* kick-off initial scanning */ + _handle_scan_timeout(Duration(Microseconds(0))); + } + + /** + * Trigger RFKILL notification + * + * Used by the wifi driver to notify the manager. + */ + void rfkill_notify() override { + _rfkill_handler.local_submit(); } + + /** + * Return message buffer + * + * Used for communication with the CTRL interface + */ + Msg_buffer &msg_buffer() { return _msg; } +}; + +#endif /* _WIFI_MANAGER_H_ */ diff --git a/repos/dde_linux/src/driver/wifi/util.h b/repos/dde_linux/src/driver/wifi/util.h index 0db3931afc..2285fd12fa 100644 --- a/repos/dde_linux/src/driver/wifi/util.h +++ b/repos/dde_linux/src/driver/wifi/util.h @@ -1,5 +1,5 @@ /* - * \brief Wifi front end utilities + * \brief Wifi manager utilities * \author Josef Soentgen * \date 2018-07-23 */ @@ -59,9 +59,9 @@ namespace Util { } } - /********************************** - ** Front end specific utilities ** - **********************************/ + /******************************** + ** Manager-specific utilities ** + ********************************/ inline unsigned approximate_quality(char const *str) { diff --git a/repos/dde_linux/src/lib/wifi/lx_emul.c b/repos/dde_linux/src/lib/wifi/lx_emul.c index a8c0712862..c08c03d9ef 100644 --- a/repos/dde_linux/src/lib/wifi/lx_emul.c +++ b/repos/dde_linux/src/lib/wifi/lx_emul.c @@ -460,10 +460,10 @@ static int rfkill_task_function(void *arg) bool rfkilled = !!rfkill_get_global_sw_state(RFKILL_TYPE_WLAN); - if (rfkilled != _rfkill_state.blocked) + if (rfkilled != _rfkill_state.blocked) { rfkill_switch_all(RFKILL_TYPE_WLAN, !!_rfkill_state.blocked); - - _rfkill_state.rfkilled = rfkilled; + _rfkill_state.rfkilled = !!_rfkill_state.blocked; + } lx_emul_task_schedule(true); } diff --git a/repos/dde_linux/src/lib/wifi/wlan.cc b/repos/dde_linux/src/lib/wifi/wlan.cc index 518c3648ba..c7d44eb7eb 100644 --- a/repos/dde_linux/src/lib/wifi/wlan.cc +++ b/repos/dde_linux/src/lib/wifi/wlan.cc @@ -342,7 +342,11 @@ void Wifi::set_rfkill(bool blocked) */ lx_emul_task_unblock(uplink_task_struct_ptr); Lx_kit::env().scheduler.execute(); +} + +void Wifi::rfkill_notify() +{ if (_wlan_ptr->rfkill_helper.constructed()) _wlan_ptr->rfkill_helper->submit_notification(); } diff --git a/repos/dde_linux/src/lib/wpa_driver_nl80211/rfkill_genode.cc b/repos/dde_linux/src/lib/wpa_driver_nl80211/rfkill_genode.cc index c7749c9df4..b85106b9b4 100644 --- a/repos/dde_linux/src/lib/wpa_driver_nl80211/rfkill_genode.cc +++ b/repos/dde_linux/src/lib/wpa_driver_nl80211/rfkill_genode.cc @@ -54,6 +54,8 @@ static void rfkill_receive(int sock, void *eloop_ctx, void *sock_ctx) } else { rfkill->cfg->unblocked_cb(rfkill->cfg->ctx); } + + Wifi::rfkill_notify(); } } diff --git a/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.c b/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.c deleted file mode 100644 index fc5d359665..0000000000 --- a/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * \brief WPA Supplicant frontend - * \author Josef Soentgen - * \date 2018-07-18 - */ - -/* - * Copyright (C) 2018 Genode Labs GmbH - * - * This file is distributed under the terms of the GNU General Public License - * version 2. - */ - -/* - * based on: - * - * WPA Supplicant / UNIX domain socket -based control interface - * Copyright (c) 2004-2014, Jouni Malinen - * - * This software may be distributed under the terms of the BSD license. - * See README for more details. - */ - -/* wpa_supplicant includes */ -#include "includes.h" -#include "utils/common.h" -#include "utils/eloop.h" -#include "utils/list.h" -#include "common/ctrl_iface_common.h" -#include "eapol_supp/eapol_supp_sm.h" -#include "config.h" -#include "wpa_supplicant_i.h" -#include "ctrl_iface.h" - -/* rep includes */ -#include - -typedef unsigned unaligned_unsigned __attribute__ ((aligned (1))); - -struct ctrl_iface_priv { - struct wpa_supplicant *wpa_s; - int fd; - int level; - - /* TODO replace w/ Msg_buffer */ - char *send_buffer; - size_t send_buffer_size; - unaligned_unsigned *send_id; - - char *recv_buffer; - size_t recv_buffer_size; - unaligned_unsigned *recv_id; - - unsigned last_recv_id; - - char *event_buffer; - size_t event_buffer_size; - unaligned_unsigned *event_id; -}; - - -struct ctrl_iface_global_priv { - struct wpa_global *global; -}; - - -extern void nl_set_wpa_ctrl_fd(void); - - -void wpa_ctrl_set_fd() -{ - nl_set_wpa_ctrl_fd(); -} - - -static void send_reply(struct ctrl_iface_priv *priv, char const *txt, size_t len) -{ - char *msg = priv->send_buffer; - size_t mlen = priv->send_buffer_size; - - if (len >= mlen) { - len = mlen - 1; - } - - memcpy(msg, txt, len); - msg[len] = 0; - (*priv->send_id)++; -} - - -/* - * This function is called by wpa_supplicant whenever it receives a - * command via the CTRL interface, i.e. the front end has sent a new - * message. - */ -static void wpa_supplicant_ctrl_iface_receive(int fd, void *eloop_ctx, - void *sock_ctx) -{ - struct wpa_supplicant *wpa_s = eloop_ctx; - struct ctrl_iface_priv *priv = sock_ctx; - - char *msg = priv->recv_buffer; - - unsigned const recv_id = *priv->recv_id; - - char *reply = NULL; - size_t reply_len = 0; - - if (msg[0] == 0 || recv_id == priv->last_recv_id) { return; } - - priv->last_recv_id = recv_id; - - reply = wpa_supplicant_ctrl_iface_process(wpa_s, msg, - &reply_len); - - if (reply) { - wifi_block_for_processing(); - send_reply(priv, reply, reply_len); - wifi_notify_cmd_result(); - os_free(reply); - } else - - if (reply_len == 1) { - wifi_block_for_processing(); - send_reply(priv, "FAIL", 4); - wifi_notify_cmd_result(); - } else - - if (reply_len == 2) { - wifi_block_for_processing(); - send_reply(priv, "OK", 2); - wifi_notify_cmd_result(); - } -} - - -static void send_event(struct ctrl_iface_priv *priv, char const *txt, size_t len) -{ - char *msg = priv->event_buffer; - size_t mlen = priv->event_buffer_size; - - if (len >= mlen) { - len = mlen - 1; - } - - memcpy(msg, txt, len); - msg[len] = 0; - (*priv->event_id)++; -} - - -/* - * This function is called by wpa_supplicant whenever it wants to - * forward some message. We filter these messages and forward only - * those, which are of interest to the front end. - */ -static void wpa_supplicant_ctrl_iface_msg_cb(void *ctx, int level, - enum wpa_msg_type type, - const char *txt, size_t len) -{ - /* there is not global support */ - if (type == WPA_MSG_ONLY_GLOBAL) { return; } - - struct wpa_supplicant *wpa_s = ctx; - if (wpa_s == NULL) { return; } - - struct ctrl_iface_priv *priv = wpa_s->ctrl_iface; - if (!priv || level < priv->level) { return; } - - /* - * Filter messages and only forward events the front end cares - * about or rather knows how to handle. - */ - int const forward = - strncmp(txt, "CTRL-EVENT-SCAN-RESULTS", 23) == 0 - || strncmp(txt, "CTRL-EVENT-CONNECTED", 20) == 0 - || strncmp(txt, "CTRL-EVENT-DISCONNECTED", 23) == 0 - || strncmp(txt, "CTRL-EVENT-NETWORK-NOT-FOUND", 28) == 0 - /* needed to detect connecting state */ - || strncmp(txt, "SME: Trying to authenticate", 27) == 0 - ; - if (!forward) { return; } - - wifi_notify_event(); - send_event(priv, txt, len); -} - - -struct ctrl_iface_priv * -wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) -{ - struct ctrl_iface_priv *priv; - - priv = os_zalloc(sizeof(*priv)); - if (priv == NULL) { return NULL; } - - if (wpa_s->conf->ctrl_interface == NULL) { - return priv; - } - - struct Msg_buffer *msg_buffer = (struct Msg_buffer*)wifi_get_buffer(); - priv->recv_buffer = (char *)msg_buffer->send; - priv->recv_buffer_size = sizeof(msg_buffer->send); - priv->send_buffer = (char *)msg_buffer->recv; - priv->send_buffer_size = sizeof(msg_buffer->recv); - priv->send_id = &msg_buffer->recv_id; - priv->recv_id = &msg_buffer->send_id; - - priv->event_buffer = (char *)msg_buffer->event; - priv->event_buffer_size = sizeof(msg_buffer->event); - priv->event_id = &msg_buffer->event_id; - - priv->level = MSG_INFO; - priv->fd = WPA_CTRL_FD; - - eloop_register_read_sock(priv->fd, - wpa_supplicant_ctrl_iface_receive, - wpa_s, priv); - - wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb); - return priv; -} - - -void wpa_supplicant_ctrl_iface_deinit(struct wpa_supplicant *wpa_s, - struct ctrl_iface_priv *priv) -{ - (void)wpa_s; - - os_free(priv); -} - - -void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv) { } - - -struct ctrl_iface_global_priv * -wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global) -{ - struct ctrl_iface_global_priv *priv; - - priv = os_zalloc(sizeof(*priv)); - return priv; -} - - -void wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *p) -{ - os_free(p); -} diff --git a/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.cc b/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.cc new file mode 100644 index 0000000000..9dfac61e1d --- /dev/null +++ b/repos/dde_linux/src/lib/wpa_supplicant/ctrl_iface_genode.cc @@ -0,0 +1,240 @@ +/* + * \brief Genode-specific WPA supplicant ctrl_iface + * \author Josef Soentgen + * \date 2018-07-18 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +/* + * based on: + * + * WPA Supplicant / UNIX domain socket -based control interface + * Copyright (c) 2004-2014, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +/* wpa_supplicant includes */ +extern "C" { +#include "includes.h" +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/list.h" +#include "common/ctrl_iface_common.h" +#include "eapol_supp/eapol_supp_sm.h" +#include "config.h" +#include "wpa_supplicant_i.h" +#include "ctrl_iface.h" +} + +/* rep includes */ +#include + + +static Wifi::Msg_buffer *_msg_buffer; + + +void Wifi::ctrl_init(Msg_buffer &buffer) +{ + _msg_buffer = &buffer; +} + + +struct ctrl_iface_priv { + struct wpa_supplicant *wpa_s; + int fd; + int level; + + Wifi::Msg_buffer *buffer; + unsigned last_send_id; +}; + + +struct ctrl_iface_global_priv { + struct wpa_global *global; +}; + + +extern "C" +void nl_set_wpa_ctrl_fd(void); + + +void wpa_ctrl_set_fd() +{ + nl_set_wpa_ctrl_fd(); +} + + +static void send_reply(Wifi::Msg_buffer &buffer, char const *txt, size_t len) +{ + buffer.block_for_processing(); + + /* XXX hack to trick poll() into returning faster */ + wpa_ctrl_set_fd(); + + if (len >= sizeof(buffer.recv)) + len = sizeof(buffer.recv) - 1; + + memcpy(buffer.recv, txt, len); + buffer.recv[len] = 0; + buffer.recv_id++; + + buffer.notify_response(); +} + + +/* + * This function is called by wpa_supplicant whenever it receives a + * command via the CTRL interface, i.e. the manager has sent a new + * message. + */ +static void wpa_supplicant_ctrl_iface_receive(int fd, void *eloop_ctx, + void *sock_ctx) +{ + struct wpa_supplicant *wpa_s = static_cast(eloop_ctx); + struct ctrl_iface_priv *priv = static_cast(sock_ctx); + + Wifi::Msg_buffer &buffer = *priv->buffer; + + char *reply = NULL; + size_t reply_len = 0; + + if (buffer.send[0] == 0 || buffer.send_id == priv->last_send_id) + return; + + priv->last_send_id = buffer.send_id; + + reply = wpa_supplicant_ctrl_iface_process(wpa_s, + buffer.send, + &reply_len); + + if (reply) { + send_reply(buffer, reply, reply_len); + os_free(reply); + } else + + if (reply_len == 1) { + send_reply(buffer, "FAIL", 4); + } else + + if (reply_len == 2) { + send_reply(buffer, "OK", 2); + } +} + + +static void send_event(Wifi::Msg_buffer &buffer, char const *txt, size_t len) +{ + buffer.block_for_processing(); + + /* XXX hack to trick poll() into returning faster */ + wpa_ctrl_set_fd(); + + if (len >= sizeof(buffer.event)) + len = sizeof(buffer.event) - 1; + + memcpy(buffer.event, txt, len); + buffer.event[len] = 0; + buffer.event_id++; + + buffer.notify_event(); +} + + +/* + * This function is called by wpa_supplicant whenever it wants to + * forward some message. We filter these messages and forward only + * those, which are of interest to the Wifi manager. + */ +static void wpa_supplicant_ctrl_iface_msg_cb(void *ctx, int level, + enum wpa_msg_type type, + const char *txt, size_t len) +{ + /* there is not global support */ + if (type == WPA_MSG_ONLY_GLOBAL) + return; + + struct wpa_supplicant *wpa_s = static_cast(ctx); + if (wpa_s == NULL) + return; + + struct ctrl_iface_priv *priv = wpa_s->ctrl_iface; + if (!priv || level < priv->level) + return; + + /* + * Filter messages and only forward events the manager cares + * about or rather knows how to handle. + */ + bool const forward = + strncmp(txt, "CTRL-EVENT-SCAN-RESULTS", 23) == 0 + || strncmp(txt, "CTRL-EVENT-CONNECTED", 20) == 0 + || strncmp(txt, "CTRL-EVENT-DISCONNECTED", 23) == 0 + || strncmp(txt, "CTRL-EVENT-NETWORK-NOT-FOUND", 28) == 0 + /* needed to detect connecting state */ + || strncmp(txt, "SME: Trying to authenticate", 27) == 0; + if (!forward) + return; + + Wifi::Msg_buffer &buffer = *priv->buffer; + send_event(buffer, txt, len); +} + + +extern "C" struct ctrl_iface_priv * +wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) +{ + struct ctrl_iface_priv *priv = (ctrl_iface_priv*)os_zalloc(sizeof(*priv)); + if (priv == NULL) { return NULL; } + + if (wpa_s->conf->ctrl_interface == NULL) { + return priv; + } + + priv->buffer = _msg_buffer; + priv->level = MSG_INFO; + priv->fd = Wifi::CTRL_FD; + + eloop_register_read_sock(priv->fd, + wpa_supplicant_ctrl_iface_receive, + wpa_s, priv); + + wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb); + return priv; +} + +extern "C" +void wpa_supplicant_ctrl_iface_deinit(struct wpa_supplicant *wpa_s, + struct ctrl_iface_priv *priv) +{ + (void)wpa_s; + + os_free(priv); +} + + +extern "C" +void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv) { } + + +extern "C" struct ctrl_iface_global_priv * +wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global) +{ + struct ctrl_iface_global_priv *priv = + (ctrl_iface_global_priv*)os_zalloc(sizeof(*priv)); + return priv; +} + + +extern "C" +void wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *p) +{ + os_free(p); +} diff --git a/repos/dde_linux/src/lib/wpa_supplicant/main.c b/repos/dde_linux/src/lib/wpa_supplicant/main.c index 0a18ba610f..7bc61b7108 100644 --- a/repos/dde_linux/src/lib/wpa_supplicant/main.c +++ b/repos/dde_linux/src/lib/wpa_supplicant/main.c @@ -1,5 +1,5 @@ /* - * \brief WPA Supplicant frontend + * \brief Custom WPA Supplicant main routine * \author Josef Soentgen * \date 2014-12-08 */ @@ -45,8 +45,7 @@ int wpa_main(void) memset(¶ms, 0, sizeof(params)); - // TODO use CTRL interface for setting debug level - params.wpa_debug_level = 1 ? MSG_DEBUG : MSG_INFO; + params.wpa_debug_level = MSG_ERROR; params.ctrl_interface = "GENODE"; global = wpa_supplicant_init(¶ms); diff --git a/repos/dde_linux/src/lib/wpa_supplicant/symbol.map b/repos/dde_linux/src/lib/wpa_supplicant/symbol.map index 2e8f244a66..6db93374fe 100644 --- a/repos/dde_linux/src/lib/wpa_supplicant/symbol.map +++ b/repos/dde_linux/src/lib/wpa_supplicant/symbol.map @@ -7,7 +7,8 @@ /* needed by wifi driver */ wpa_main; wpa_reporter_init; - wpa_ctrl_set_fd; + *wpa_ctrl_set_fd*; + *ctrl_init*; /* needed by wpa_driver_nl80211 */ __hide_aliasing_typecast; diff --git a/repos/gems/sculpt/wifi/default b/repos/gems/sculpt/wifi/default index 361d2eaff4..ab825e4630 100644 --- a/repos/gems/sculpt/wifi/default +++ b/repos/gems/sculpt/wifi/default @@ -1,3 +1,3 @@ - + diff --git a/repos/gems/src/app/sculpt_manager/network.h b/repos/gems/src/app/sculpt_manager/network.h index 4647ef233a..71307b153e 100644 --- a/repos/gems/src/app/sculpt_manager/network.h +++ b/repos/gems/src/app/sculpt_manager/network.h @@ -141,7 +141,6 @@ struct Sculpt::Network : Noncopyable _wlan_config.generate([&] (Xml_generator &xml) { - xml.attribute("connected_scan_interval", 0U); xml.attribute("scan_interval", 10U); xml.attribute("update_quality_interval", 30U); @@ -172,7 +171,6 @@ struct Sculpt::Network : Noncopyable _wlan_config.generate([&] (Xml_generator &xml) { - xml.attribute("connected_scan_interval", 0U); xml.attribute("scan_interval", 10U); xml.attribute("update_quality_interval", 30U); diff --git a/repos/pc/run/pc_wifi.run b/repos/pc/run/pc_wifi.run index 64be6b23bd..8f2413f6ab 100644 --- a/repos/pc/run/pc_wifi.run +++ b/repos/pc/run/pc_wifi.run @@ -30,29 +30,27 @@ proc wifi_wpa { } { # You may script your tests with this function in the dynamic_rom config below. # The syntax for the networks parameter is # -# { ssid protection passphrase explicit_scan } +# { ssid protection passphrase auto_connect explicit_scan } # # Example dynamic_rom config: # # { -# } [wifi_config 30 5 no [list "net1 WPA2 net1_psk no" "net2 WPA2 net2_psk no"]] { +# } [wifi_config 5 no [list "net1 WPA2 net1_psk yes no" "net2 WPA2 net2_psk yes no"]] { # # -# } [wifi_config 30 5 no [list "net1 WPA2 net1_psk no" "net2 WPA2 net2_psk yes"]] { +# } [wifi_config 5 no [list "net1 WPA2 net1_psk yes no" "net2 WPA2 net2_psk yes yes"]] { # } -set wifi_verbose false -set wifi_verbose_state false +set wifi_verbose false -proc wifi_config { connected_scan_interval scan_interval rfkill networks } { +proc wifi_config { scan_interval update_quality_interval rfkill networks } { global wifi_verbose global wifi_verbose_state set config "\n" foreach n $networks { @@ -60,7 +58,8 @@ proc wifi_config { connected_scan_interval scan_interval rfkill networks } { append config " ssid=\"[lindex $n 0]\"" append config " protection=\"[lindex $n 1]\"" append config " passphrase=\"[lindex $n 2]\"" - append config " explicit_scan=\"[lindex $n 3]\"" + append config " auto_connect=\"[lindex $n 3]\"" + append config " explicit_scan=\"[lindex $n 4]\"" append config "/>\n" } append config "\n" @@ -236,19 +235,19 @@ append config { -} [wifi_config 30 5 no {}] { +} [wifi_config 5 10 no {}] { -} [wifi_config 30 5 no [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes"]] { +} [wifi_config 5 10 no [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes no "]] { -} [wifi_config 30 5 yes [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes"]] { +} [wifi_config 5 10 yes [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes no "]] { -} [wifi_config 30 5 no [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes"]] { +} [wifi_config 5 10 no [list "[wifi_ssid] [wifi_wpa] [wifi_psk] yes no "]] { @@ -327,7 +326,7 @@ install_config $config # boot-module assembly # if {$debug_driver} { -exec rm bin/wifi.lib.so +catch {exec rm bin/wifi.lib.so} exec echo dummy > bin/wifi.lib.so } diff --git a/repos/ports/run/netperf.inc b/repos/ports/run/netperf.inc index 4173f6abd3..16df043016 100644 --- a/repos/ports/run/netperf.inc +++ b/repos/ports/run/netperf.inc @@ -244,8 +244,8 @@ if { $use_wifi_driver } { -} -append config "" +} +append config "" append config {