From 3afc341c7bf19beead996a66edf06ec8b89135f7 Mon Sep 17 00:00:00 2001 From: taschenb Date: Tue, 19 Jun 2018 05:16:09 +0200 Subject: [PATCH] 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. --- README.md | 2 +- build.sh | 16 +-- cmake/02-opts.cmake | 8 +- cmake/03-libs.cmake | 6 +- include/adapters/net.hpp | 46 +++++++- include/settings.hpp.cmake | 1 + src/CMakeLists.txt | 7 ++ src/adapters/net.cpp | 141 +--------------------- src/adapters/net_iw.cpp | 137 ++++++++++++++++++++++ src/adapters/net_nl.cpp | 233 +++++++++++++++++++++++++++++++++++++ 10 files changed, 448 insertions(+), 149 deletions(-) create mode 100644 src/adapters/net_iw.cpp create mode 100644 src/adapters/net_nl.cpp diff --git a/README.md b/README.md index 02acb83e..aba084c6 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/build.sh b/build.sh index 39dbe47a..7dc8814f 100755 --- a/build.sh +++ b/build.sh @@ -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++" diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake index 728a3878..306d91a1 100644 --- a/cmake/02-opts.cmake +++ b/cmake/02-opts.cmake @@ -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) diff --git a/cmake/03-libs.cmake b/cmake/03-libs.cmake index 26661ae5..fc879f10 100644 --- a/cmake/03-libs.cmake +++ b/cmake/03-libs.cmake @@ -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) diff --git a/include/adapters/net.hpp b/include/adapters/net.hpp index 97e11469..f9089df4 100644 --- a/include/adapters/net.hpp +++ b/include/adapters/net.hpp @@ -5,7 +5,6 @@ #include #include -#include #ifdef inline #undef inline @@ -17,6 +16,15 @@ #include "components/logger.hpp" #include "utils/math.hpp" +#if WITH_LIBNL +#include + +struct nl_msg; +struct nlattr; +#else +#include +#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; using wired_t = unique_ptr; -} +} // namespace net POLYBAR_NS_END diff --git a/include/settings.hpp.cmake b/include/settings.hpp.cmake index d76359c2..e358d26f 100644 --- a/include/settings.hpp.cmake +++ b/include/settings.hpp.cmake @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 78a0666a..e0bae708 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/adapters/net.cpp b/src/adapters/net.cpp index c4ce414d..d32f4b25 100644 --- a/src/adapters/net.cpp +++ b/src/adapters/net.cpp @@ -1,21 +1,16 @@ #include "adapters/net.hpp" -#include -#include -#include #include -#include -#include #include #include #include #include #include +#include #include +#include #include -#include -#include #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 diff --git a/src/adapters/net_iw.cpp b/src/adapters/net_iw.cpp new file mode 100644 index 00000000..49c46f90 --- /dev/null +++ b/src/adapters/net_iw.cpp @@ -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 diff --git a/src/adapters/net_nl.cpp b/src/adapters/net_nl.cpp new file mode 100644 index 00000000..816812e4 --- /dev/null +++ b/src/adapters/net_nl.cpp @@ -0,0 +1,233 @@ +#include "adapters/net.hpp" + +#include +#include +#include + +#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(instance); + auto gnlh = static_cast(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(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(c)); }); + } + } + } + + /** + * Set frequency + */ + void wireless_network::parse_frequency(struct nlattr** bss) { + if (bss[NL80211_BSS_FREQUENCY] != nullptr) { + // in MHz + m_frequency = static_cast(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(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