feat(net): Add nl80211 support (#1009)

This patch enables support for nl80211. In case the libnl-genl-3.0
library isn't found, it will fall back to Wext instead.
The library to use can also be manually set with the CMake option
WITH_LIBNL.

The Wireless-Extensions (WE or Wext) are deprecated and long replaced
by cfg80211.

Although Wext isn't used by WiFi drivers anymore, CFG80211_WEXT allows
old tools to communicate with modern drivers by providing a wrapper
API.
This commit is contained in:
taschenb 2018-06-19 05:16:09 +02:00 committed by NBonaparte
parent d43a4a8d9d
commit 3afc341c7b
10 changed files with 448 additions and 149 deletions

View File

@ -106,7 +106,7 @@ A compiler with C++14 support ([clang-3.4+](http://llvm.org/releases/download.ht
- jsoncpp *required by `internal/i3`*
- libmpdclient *required by `internal/mpd`*
- libcurl *required by `internal/github`*
- wireless_tools *required by `internal/network`*
- libnl-genl or wireless_tools *required by `internal/network`*
Find a more complete list on the [dedicated wiki page](https://github.com/jaagr/polybar/wiki/Compiling).

View File

@ -38,21 +38,21 @@ function main
msg "Setting build options"
read -r -p "$(msg "Use GCC even if Clang is installed -------------------------------- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Use GCC even if Clang is installed ----------------------------- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && try_to_use_clang="ON"
read -r -p "$(msg "Include support for \"internal/i3\" (requires i3) ------------------- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/i3\" (requires i3) ---------------- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_i3="OFF"
read -r -p "$(msg "Include support for \"internal/alsa\" (requires alsalib) ------------ [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/alsa\" (requires alsalib) --------- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_alsa="OFF"
read -r -p "$(msg "Include support for \"internal/pulseaudio\" (requires libpulse) ----- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/pulseaudio\" (requires libpulse) -- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_pulseaudio="OFF"
read -r -p "$(msg "Include support for \"internal/network\" (requires wireless_tools) -- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/network\" (requires libnl/libiw) -- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_network="OFF"
read -r -p "$(msg "Include support for \"internal/mpd\" (requires libmpdclient) -------- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/mpd\" (requires libmpdclient) ----- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_mpd="OFF"
read -r -p "$(msg "Include support for \"internal/github\" (requires libcurl) ---------- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Include support for \"internal/github\" (requires libcurl) ------- [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && enable_curl="OFF"
read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages --------------------- [y/N]: ")" -n 1 p && echo
read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages ------------------ [y/N]: ")" -n 1 p && echo
[[ "${p^^}" != "Y" ]] && build_ipc_msg="OFF"
local cxx="c++"

View File

@ -6,7 +6,12 @@ checklib(ENABLE_ALSA "pkg-config" alsa)
checklib(ENABLE_CURL "pkg-config" libcurl)
checklib(ENABLE_I3 "binary" i3)
checklib(ENABLE_MPD "pkg-config" libmpdclient)
checklib(ENABLE_NETWORK "cmake" Libiw)
checklib(WITH_LIBNL "pkg-config" libnl-genl-3.0)
if(WITH_LIBNL)
checklib(ENABLE_NETWORK "pkg-config" libnl-genl-3.0)
else()
checklib(ENABLE_NETWORK "cmake" Libiw)
endif()
checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse)
checklib(ENABLE_PULSEAUDIO "binary" pulseaudio)
checklib(WITH_XKB "pkg-config" xcb-xkb)
@ -28,6 +33,7 @@ option(ENABLE_ALSA "Enable alsa support" ON)
option(ENABLE_CURL "Enable curl support" ON)
option(ENABLE_I3 "Enable i3 support" ON)
option(ENABLE_MPD "Enable mpd support" ON)
option(WITH_LIBNL "Use netlink interface for wireless" ON)
option(ENABLE_NETWORK "Enable network support" ON)
option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON)
option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON)

View File

@ -10,7 +10,11 @@ querylib(TRUE "pkg-config" cairo-fc libs dirs)
querylib(ENABLE_ALSA "pkg-config" alsa libs dirs)
querylib(ENABLE_CURL "pkg-config" libcurl libs dirs)
querylib(ENABLE_MPD "pkg-config" libmpdclient libs dirs)
querylib(ENABLE_NETWORK "cmake" Libiw libs dirs)
if(WITH_LIBNL)
querylib(ENABLE_NETWORK "pkg-config" libnl-genl-3.0 libs dirs)
else()
querylib(ENABLE_NETWORK "cmake" Libiw libs dirs)
endif()
querylib(ENABLE_PULSEAUDIO "pkg-config" libpulse libs dirs)
querylib(WITH_XCOMPOSITE "pkg-config" xcb-composite libs dirs)

View File

@ -5,7 +5,6 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <iwlib.h>
#ifdef inline
#undef inline
@ -17,6 +16,15 @@
#include "components/logger.hpp"
#include "utils/math.hpp"
#if WITH_LIBNL
#include <net/if.h>
struct nl_msg;
struct nlattr;
#else
#include <iwlib.h>
#endif
POLYBAR_NS
class file_descriptor;
@ -103,6 +111,39 @@ namespace net {
};
// }}}
#if WITH_LIBNL
// class : wireless_network {{{
class wireless_network : public network {
public:
wireless_network(string interface) : network(interface), m_ifid(if_nametoindex(interface.c_str())){};
bool query(bool accumulate = false) override;
bool connected() const override;
string essid() const;
int signal() const;
int quality() const;
protected:
static int scan_cb(struct nl_msg* msg, void* instance);
bool associated_or_joined(struct nlattr** bss);
void parse_essid(struct nlattr** bss);
void parse_frequency(struct nlattr** bss);
void parse_quality(struct nlattr** bss);
void parse_signal(struct nlattr** bss);
private:
unsigned int m_ifid{};
string m_essid{};
int m_frequency{};
quality_range m_signalstrength{};
quality_range m_linkquality{};
};
// }}}
#else
// class : wireless_network {{{
class wireless_network : public network {
@ -128,9 +169,10 @@ namespace net {
};
// }}}
#endif
using wireless_t = unique_ptr<wireless_network>;
using wired_t = unique_ptr<wired_network>;
}
} // namespace net
POLYBAR_NS_END

View File

@ -20,6 +20,7 @@
#cmakedefine01 ENABLE_ALSA
#cmakedefine01 ENABLE_MPD
#cmakedefine01 ENABLE_NETWORK
#cmakedefine01 WITH_LIBNL
#cmakedefine01 ENABLE_I3
#cmakedefine01 ENABLE_CURL
#cmakedefine01 ENABLE_PULSEAUDIO

View File

@ -23,6 +23,13 @@ endif()
if(NOT ENABLE_NETWORK)
list(REMOVE_ITEM files modules/network.cpp)
list(REMOVE_ITEM files adapters/net.cpp)
list(REMOVE_ITEM files adapters/net_iw.cpp)
list(REMOVE_ITEM files adapters/net_nl.cpp)
endif()
if(WITH_LIBNL)
list(REMOVE_ITEM files adapters/net_iw.cpp)
else()
list(REMOVE_ITEM files adapters/net_nl.cpp)
endif()
if(NOT ENABLE_I3)
list(REMOVE_ITEM files modules/i3.cpp)

View File

@ -1,21 +1,16 @@
#include "adapters/net.hpp"
#include <cerrno>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <utility>
#include <arpa/inet.h>
#include <linux/ethtool.h>
#include <linux/if_link.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <climits>
#include <csignal>
#ifdef inline
#undef inline
@ -192,9 +187,9 @@ namespace net {
driver.cmd = ETHTOOL_GDRVINFO;
memset(&request, 0, sizeof(request));
/*
* Only copy array size minus one bytes over to ensure there is a
* Only copy array size minus one bytes over to ensure there is a
* terminating NUL byte (which is guaranteed by memset)
*/
strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
@ -306,133 +301,7 @@ namespace net {
}
// }}}
// class : wireless_network {{{
/**
* Query the wireless device for information
* about the current connection
*/
bool wireless_network::query(bool accumulate) {
if (!network::query(accumulate)) {
return false;
}
auto socket_fd = file_util::make_file_descriptor(iw_sockets_open());
if (!*socket_fd) {
return false;
}
struct iwreq req {};
if (iw_get_ext(*socket_fd, m_interface.c_str(), SIOCGIWMODE, &req) == -1) {
return false;
}
// Ignore interfaces in ad-hoc mode
if (req.u.mode == IW_MODE_ADHOC) {
return false;
}
query_essid(*socket_fd);
query_quality(*socket_fd);
return true;
}
/**
* Check current connection state
*/
bool wireless_network::connected() const {
if (!network::test_interface()) {
return false;
}
return !m_essid.empty();
}
/**
* ESSID reported by last query
*/
string wireless_network::essid() const {
return m_essid;
}
/**
* Signal strength percentage reported by last query
*/
int wireless_network::signal() const {
return m_signalstrength.percentage();
}
/**
* Link quality percentage reported by last query
*/
int wireless_network::quality() const {
return m_linkquality.percentage();
}
/**
* Query for ESSID
*/
void wireless_network::query_essid(const int& socket_fd) {
char essid[IW_ESSID_MAX_SIZE + 1];
struct iwreq req {};
req.u.essid.pointer = &essid;
req.u.essid.length = sizeof(essid);
req.u.essid.flags = 0;
if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWESSID, &req) != -1) {
m_essid = string{essid};
} else {
m_essid.clear();
}
}
/**
* Query for device driver quality values
*/
void wireless_network::query_quality(const int& socket_fd) {
iwrange range{};
iwstats stats{};
// Fill range
if (iw_get_range_info(socket_fd, m_interface.c_str(), &range) == -1) {
return;
}
// Fill stats
if (iw_get_stats(socket_fd, m_interface.c_str(), &stats, &range, 1) == -1) {
return;
}
// Check if the driver supplies the quality value
if (stats.qual.updated & IW_QUAL_QUAL_INVALID) {
return;
}
// Check if the driver supplies the quality level value
if (stats.qual.updated & IW_QUAL_LEVEL_INVALID) {
return;
}
// Check if the link quality has been uodated
if (stats.qual.updated & IW_QUAL_QUAL_UPDATED) {
m_linkquality.val = stats.qual.qual;
m_linkquality.max = range.max_qual.qual;
}
// Check if the signal strength has been uodated
if (stats.qual.updated & IW_QUAL_LEVEL_UPDATED) {
m_signalstrength.val = stats.qual.level;
m_signalstrength.max = range.max_qual.level;
// Check if the values are defined in dBm
if (stats.qual.level > range.max_qual.level) {
m_signalstrength.val -= 0x100;
m_signalstrength.max = (stats.qual.level - range.max_qual.level) - 0x100;
}
}
}
// }}}
}
} // namespace net
POLYBAR_NS_END

137
src/adapters/net_iw.cpp Normal file
View File

@ -0,0 +1,137 @@
#include "adapters/net.hpp"
#include "utils/file.hpp"
POLYBAR_NS
namespace net {
// class : wireless_network {{{
/**
* Query the wireless device for information
* about the current connection
*/
bool wireless_network::query(bool accumulate) {
if (!network::query(accumulate)) {
return false;
}
auto socket_fd = file_util::make_file_descriptor(iw_sockets_open());
if (!*socket_fd) {
return false;
}
struct iwreq req {};
if (iw_get_ext(*socket_fd, m_interface.c_str(), SIOCGIWMODE, &req) == -1) {
return false;
}
// Ignore interfaces in ad-hoc mode
if (req.u.mode == IW_MODE_ADHOC) {
return false;
}
query_essid(*socket_fd);
query_quality(*socket_fd);
return true;
}
/**
* Check current connection state
*/
bool wireless_network::connected() const {
if (!network::test_interface()) {
return false;
}
return !m_essid.empty();
}
/**
* ESSID reported by last query
*/
string wireless_network::essid() const {
return m_essid;
}
/**
* Signal strength percentage reported by last query
*/
int wireless_network::signal() const {
return m_signalstrength.percentage();
}
/**
* Link quality percentage reported by last query
*/
int wireless_network::quality() const {
return m_linkquality.percentage();
}
/**
* Query for ESSID
*/
void wireless_network::query_essid(const int& socket_fd) {
char essid[IW_ESSID_MAX_SIZE + 1];
struct iwreq req {};
req.u.essid.pointer = &essid;
req.u.essid.length = sizeof(essid);
req.u.essid.flags = 0;
if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWESSID, &req) != -1) {
m_essid = string{essid};
} else {
m_essid.clear();
}
}
/**
* Query for device driver quality values
*/
void wireless_network::query_quality(const int& socket_fd) {
iwrange range{};
iwstats stats{};
// Fill range
if (iw_get_range_info(socket_fd, m_interface.c_str(), &range) == -1) {
return;
}
// Fill stats
if (iw_get_stats(socket_fd, m_interface.c_str(), &stats, &range, 1) == -1) {
return;
}
// Check if the driver supplies the quality value
if (stats.qual.updated & IW_QUAL_QUAL_INVALID) {
return;
}
// Check if the driver supplies the quality level value
if (stats.qual.updated & IW_QUAL_LEVEL_INVALID) {
return;
}
// Check if the link quality has been uodated
if (stats.qual.updated & IW_QUAL_QUAL_UPDATED) {
m_linkquality.val = stats.qual.qual;
m_linkquality.max = range.max_qual.qual;
}
// Check if the signal strength has been uodated
if (stats.qual.updated & IW_QUAL_LEVEL_UPDATED) {
m_signalstrength.val = stats.qual.level;
m_signalstrength.max = range.max_qual.level;
// Check if the values are defined in dBm
if (stats.qual.level > range.max_qual.level) {
m_signalstrength.val -= 0x100;
m_signalstrength.max = (stats.qual.level - range.max_qual.level) - 0x100;
}
}
}
// }}}
} // namespace net
POLYBAR_NS_END

233
src/adapters/net_nl.cpp Normal file
View File

@ -0,0 +1,233 @@
#include "adapters/net.hpp"
#include <linux/nl80211.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include "utils/file.hpp"
POLYBAR_NS
namespace net {
// class : wireless_network {{{
/**
* Query the wireless device for information
* about the current connection
*/
bool wireless_network::query(bool accumulate) {
if (!network::query(accumulate)) {
return false;
}
struct nl_sock* sk = nl_socket_alloc();
if (sk == nullptr) {
return false;
}
if (genl_connect(sk) < 0) {
return false;
}
int driver_id = genl_ctrl_resolve(sk, "nl80211");
if (driver_id < 0) {
nl_socket_free(sk);
return false;
}
if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, scan_cb, this) != 0) {
nl_socket_free(sk);
return false;
}
struct nl_msg* msg = nlmsg_alloc();
if (msg == nullptr) {
nl_socket_free(sk);
return false;
}
if ((genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) == nullptr) ||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, m_ifid) < 0) {
nlmsg_free(msg);
nl_socket_free(sk);
return false;
}
// nl_send_sync always frees msg
if (nl_send_sync(sk, msg) < 0) {
nl_socket_free(sk);
return false;
}
nl_socket_free(sk);
return true;
}
/**
* Check current connection state
*/
bool wireless_network::connected() const {
if (!network::test_interface()) {
return false;
}
return !m_essid.empty();
}
/**
* ESSID reported by last query
*/
string wireless_network::essid() const {
return m_essid;
}
/**
* Signal strength percentage reported by last query
*/
int wireless_network::signal() const {
return m_signalstrength.percentage();
}
/**
* Link quality percentage reported by last query
*/
int wireless_network::quality() const {
return m_linkquality.percentage();
}
/**
* Callback to parse scan results
*/
int wireless_network::scan_cb(struct nl_msg* msg, void* instance) {
auto wn = static_cast<wireless_network*>(instance);
auto gnlh = static_cast<genlmsghdr*>(nlmsg_data(nlmsg_hdr(msg)));
struct nlattr* tb[NL80211_ATTR_MAX + 1];
struct nlattr* bss[NL80211_BSS_MAX + 1];
struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};
bss_policy[NL80211_BSS_TSF].type = NLA_U64;
bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC;
bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16;
bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16;
bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC;
bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32;
bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
return NL_SKIP;
}
if (tb[NL80211_ATTR_BSS] == nullptr) {
return NL_SKIP;
}
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {
return NL_SKIP;
}
if (!wn->associated_or_joined(bss)) {
return NL_SKIP;
}
wn->parse_essid(bss);
wn->parse_frequency(bss);
wn->parse_signal(bss);
wn->parse_quality(bss);
return NL_SKIP;
}
/**
* Check for a connection to a AP
*/
bool wireless_network::associated_or_joined(struct nlattr** bss) {
if (bss[NL80211_BSS_STATUS] == nullptr) {
return false;
}
auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);
switch (status) {
case NL80211_BSS_STATUS_ASSOCIATED:
case NL80211_BSS_STATUS_IBSS_JOINED:
case NL80211_BSS_STATUS_AUTHENTICATED:
return true;
default:
return false;
}
}
/**
* Set the ESSID
*/
void wireless_network::parse_essid(struct nlattr** bss) {
m_essid.clear();
if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
// Information Element ID from ieee80211.h
#define WLAN_EID_SSID 0
auto ies = static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
const auto hdr_len = 2;
while (ies_len > hdr_len && ies[0] != WLAN_EID_SSID) {
ies_len -= ies[1] + hdr_len;
ies += ies[1] + hdr_len;
}
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1];
// Only use printable characters of the current locale
std::copy_if(essid_begin, essid_end, std::back_inserter(m_essid),
[](char c) { return isprint(static_cast<unsigned char>(c)); });
}
}
}
/**
* Set frequency
*/
void wireless_network::parse_frequency(struct nlattr** bss) {
if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
// in MHz
m_frequency = static_cast<int>(nla_get_u32(bss[NL80211_BSS_FREQUENCY]));
}
}
/**
* Set device driver quality values
*/
void wireless_network::parse_quality(struct nlattr** bss) {
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
// Signal strength in unspecified units, scaled to 0..100 (u8)
m_linkquality.val = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
m_linkquality.max = 100;
}
}
/**
* Set the signalstrength
*/
void wireless_network::parse_signal(struct nlattr** bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm
int signalstrength = static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
// WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardware_max = -20;
const int hardware_min = -90;
signalstrength = std::max(hardware_min, std::min(signalstrength, hardware_max));
// Shift for positive values
m_signalstrength.val = signalstrength - hardware_min;
m_signalstrength.max = hardware_max - hardware_min;
}
}
} // namespace net
POLYBAR_NS_END