genode/repos/dde_linux/src/drivers/wifi/frontend.h

1654 lines
40 KiB
C++

/*
* \brief Startup Wifi driver
* \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 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 <libc/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/log.h>
#include <base/registry.h>
#include <base/sleep.h>
#include <os/reporter.h>
#include <timer_session/connection.h>
#include <util/interface.h>
#include <util/xml_node.h>
/* rep includes */
#include <wifi/ctrl.h>
#include <wifi/rfkill.h>
/* local includes */
#include <util.h>
/* 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 stored() const { return id != -1; }
};
template <typename FUNC>
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 <typename FUNC>
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");
unsigned signal = Util::approximate_quality(s[2]);
char const *prot = is_wpa1 ? "WPA" : "NONE";
prot = is_wpa2 ? "WPA2" : prot;
Accesspoint ap(s[0], s[1], prot, s[4], signal);
func(ap);
}
}
/*
* Wifi driver front end
*/
struct Wifi::Frontend
{
/* accesspoint */
Genode::Heap _ap_allocator;
using Accesspoint_r = Genode::Registered<Accesspoint>;
Genode::Registry<Accesspoint_r> _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 = &ap; }
});
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 = &ap; }
});
return p;
}
Accesspoint *_alloc_ap()
{
return new (&_ap_allocator) Accesspoint_r(_aps);
}
void _free_ap(Accesspoint &ap)
{
Genode::destroy(&_ap_allocator, &ap);
}
template <typename FUNC>
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::Lock _notify_lock { Genode::Lock::UNLOCKED };
void _notify_lock_lock() { _notify_lock.lock(); }
void _notify_lock_unlock() { _notify_lock.unlock(); }
bool _rfkilled { false };
Genode::Signal_handler<Wifi::Frontend> _rfkill_handler;
void _handle_rfkill()
{
_rfkilled = wifi_get_rfkill();
/* re-enable scan timer */
if (!_rfkilled) {
_scan_timer.sigh(_scan_timer_sigh);
_arm_scan_timer(false);
} else {
_scan_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<Wifi::Frontend> _config_sigh;
bool _verbose { false };
bool _verbose_state { false };
bool _use_11n { true };
bool _deferred_config_update { false };
bool _single_autoconnect { false };
Genode::uint64_t _connected_scan_interval { 30 };
Genode::uint64_t _scan_interval { 5 };
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);
/* only evaluated at start-up */
_use_11n = config.attribute_value("use_11n", _use_11n);
Genode::uint64_t connected_scan_interval =
Util::check_time(config.attribute_value("connected_scan_interval",
_connected_scan_interval),
0, 15*60);
Genode::uint64_t scan_interval =
Util::check_time(config.attribute_value("scan_interval",
_scan_interval),
5, 15*60);
if ( connected_scan_interval > _connected_scan_interval
|| scan_interval > _scan_interval) {
_arm_scan_timer(_connected_ap.bssid_valid());
}
_connected_scan_interval = connected_scan_interval;
_scan_interval = scan_interval;
/*
* 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,
INITIATE_SCAN = 0x00|SCAN,
PENDING_RESULTS = 0x10|SCAN,
ADD_NETWORK = 0x00|NETWORK,
FILL_NETWORK_SSID = 0x10|NETWORK,
FILL_NETWORK_BSSID = 0x20|NETWORK,
FILL_NETWORK_PSK = 0x30|NETWORK,
REMOVE_NETWORK = 0x40|NETWORK,
ENABLE_NETWORK = 0x50|NETWORK,
DISABLE_NETWORK = 0x60|NETWORK,
DISCONNECT_NETWORK = 0x70|NETWORK,
LIST_NETWORKS = 0x80|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_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";
default: return "unknown";
};
}
void _state_transition(State &current, State next)
{
if (_verbose_state) {
using namespace Genode;
log("Transition: ", state_strings(current), " -> ",
state_strings(next));
}
current = next;
}
using Cmd_str = Genode::String<sizeof(Msg_buffer::send)>;
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 */
Timer::Connection _scan_timer;
Genode::Signal_handler<Wifi::Frontend> _scan_timer_sigh;
void _handle_scan_timer()
{
/*
* If we are blocked or currently trying to join a network
* suspend scanning.
*/
if (_rfkilled || _connecting.length() > 1) {
if (_verbose) { Genode::log("Suspend scan timer"); }
return;
}
/* scanning was disabled, ignore current request */
if (!_arm_scan_timer(_connected_ap.bssid_valid())) {
if (_verbose) { Genode::log("Scanning disabled, ignore current scan request"); }
return;
}
/* 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<SSID_ARG_LEN + 1> 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));
}
bool _arm_scan_timer(bool connected)
{
Genode::uint64_t const sec = connected ? _connected_scan_interval : _scan_interval;
if (!sec) { return false; }
if (_verbose) {
Genode::log("Arm ", connected ? "connected " : "",
"scan: ", sec, " sec");
}
_scan_timer.trigger_once(sec * (1000 * 1000));
return true;
}
Genode::Constructible<Genode::Expanding_reporter> _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 = &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 = &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 = &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()
{
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.enabled && ap.auto_connect) {
_processed_ap = &ap;
}
});
if (!_processed_ap) {
return;
}
if (_verbose) {
Genode::log("Enable network: '", _processed_ap->ssid, "'");
}
_state_transition(_state, State::ENABLE_NETWORK);
_submit_cmd(Cmd_str("ENABLE_NETWORK ", _processed_ap->id));
}
void _network_disable()
{
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.enabled && ap.auto_connect) {
_processed_ap = &ap;
}
});
if (!_processed_ap) {
/* XXX move State transition somewhere more sane */
_state_transition(_state, State::IDLE);
_add_new_aps();
return;
}
if (_verbose) {
Genode::log("Disable network: '", _processed_ap->ssid, "'");
}
_state_transition(_state, State::DISABLE_NETWORK);
_submit_cmd(Cmd_str("DISABLE_NETWORK ", _processed_ap->id));
}
void _network_disconnect()
{
_state_transition(_state, State::DISCONNECT_NETWORK);
_submit_cmd(Cmd_str("DISCONNECT"));
}
void _network_set_ssid(char const *msg)
{
long id = -1;
Genode::ascii_to(msg, id);
_processed_ap->id = 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_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);
if (cmd_fail(msg)) {
Genode::error("could not remove network: ", msg);
} else {
_free_ap(*_processed_ap);
/* trigger the next round */
_processed_ap = nullptr;
_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 {
_state_transition(_state, State::FILL_NETWORK_PSK);
_network_set_psk();
successfully = true;
}
break;
case State::FILL_NETWORK_PSK:
_state_transition(_state, State::IDLE);
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 (_processed_ap->update) {
_processed_ap->enabled = false;
_state_transition(_state, State::DISABLE_NETWORK);
_submit_cmd(Cmd_str("DISABLE_NETWORK ", _processed_ap->id));
} else
if (_processed_ap->auto_connect) {
_state_transition(_state, State::ENABLE_NETWORK);
_submit_cmd(Cmd_str("ENABLE_NETWORK ", _processed_ap->id));
} else {
/* trigger the next round */
_processed_ap = nullptr;
_add_new_aps();
}
successfully = true;
}
break;
case State::ENABLE_NETWORK:
_state_transition(_state, State::IDLE);
if (!cmd_successful(msg)) {
Genode::error("could not enable network: ", msg);
} else {
_processed_ap->enabled = true;
/* trigger the next round */
_processed_ap = nullptr;
_add_new_aps();
successfully = true;
}
break;
case State::DISABLE_NETWORK:
_state_transition(_state, State::IDLE);
if (!cmd_successful(msg)) {
Genode::error("could not disable network: ", msg);
} else {
/*
* Updated settings are applied, enable the network
* anew an try again.
*/
if (_processed_ap->update) {
_processed_ap->update = false;
if (_processed_ap->auto_connect) {
_state_transition(_state, State::ENABLE_NETWORK);
_submit_cmd(Cmd_str("ENABLE_NETWORK ", _processed_ap->id));
}
} else {
_processed_ap->enabled = false;
/* trigger the next round */
_processed_ap = nullptr;
_network_disable();
}
successfully = true;
}
break;
case State::DISCONNECT_NETWORK:
_state_transition(_state, State::IDLE);
if (!cmd_successful(msg)) {
Genode::error("could not disconnect from network: ", msg);
} else {
_network_disable();
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");
});
});
}
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;
_state_transition(_state, State::DISABLE_NETWORK);
_submit_cmd(Cmd_str("DISABLE_NETWORK ", p->id));
}
} 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;
}
}
/* connection state */
Genode::Constructible<Genode::Reporter> _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 };
enum { MAX_ATTEMPTS = 3, };
unsigned _scan_attempts { 0 };
Accesspoint::Bssid _pending_bssid { };
void _handle_connection_events(char const *msg)
{
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);
/*
* 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));
}
_arm_scan_timer(connected);
}
/*
* 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<Wifi::Frontend> _events_handler;
unsigned _last_event_id { 0 };
void _handle_events()
{
char const *msg = reinterpret_cast<char const*>(_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<Wifi::Frontend> _cmd_handler;
unsigned _last_recv_id { 0 };
void _handle_cmds()
{
char const *msg = reinterpret_cast<char const*>(_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::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)
:
_ap_allocator(env.ram(), env.rm()),
_rfkill_handler(env.ep(), *this, &Wifi::Frontend::_handle_rfkill),
_config_rom(env, "wifi_config"),
_config_sigh(env.ep(), *this, &Wifi::Frontend::_handle_config_update),
_scan_timer(env),
_scan_timer_sigh(env.ep(), *this, &Wifi::Frontend::_handle_scan_timer),
_events_handler(env.ep(), *this, &Wifi::Frontend::_handle_events),
_cmd_handler(env.ep(), *this, &Wifi::Frontend::_handle_cmds)
{
_config_rom.sigh(_config_sigh);
_scan_timer.sigh(_scan_timer_sigh);
try {
_ap_reporter.construct(env, "accesspoints", "accesspoints");
_ap_reporter->generate([&] (Genode::Xml_generator &xml) { });
} 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);
/* kick-off initial scanning */
_handle_scan_timer();
}
/**
* Return if 11n operation is enabled
*/
bool use_11n() const { return _use_11n; }
/**
* Get RFKILL signal capability
*
* Used by the wifi_drv to notify front end.
*/
Genode::Signal_context_capability rfkill_sigh()
{
return _rfkill_handler;
}
/**
* 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(); }
/**
* Return shared memory message buffer
*
* Used for communication between front end and wpa_supplicant.
*/
Msg_buffer &msg_buffer() { return _msg; }
};
#endif /* _WIFI_FRONTEND_H_ */