From d280f5e8bdca216a86cbb83224e83a121311e86e Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 17 Aug 2018 14:24:00 +0200 Subject: [PATCH] Network detect (#26) --- include/modules/network.hpp | 12 +- resources/config | 6 +- resources/style.css | 4 + src/modules/battery.cpp | 2 +- src/modules/cpu.cpp | 2 +- src/modules/memory.cpp | 2 +- src/modules/network.cpp | 264 ++++++++++++++++++++++++++++++-- src/modules/sway/ipc/client.cpp | 2 +- 8 files changed, 274 insertions(+), 20 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index f30e88ba..2f655514 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -18,8 +18,12 @@ class Network : public IModule { auto update() -> void; operator Gtk::Widget &(); private: + static uint64_t netlinkRequest(int, void*, uint32_t, uint32_t groups = 0); + static uint64_t netlinkResponse(int, void*, uint32_t, uint32_t groups = 0); static int scanCb(struct nl_msg*, void*); + void disconnected(); + int getExternalInterface(); void parseEssid(struct nlattr**); void parseSignal(struct nlattr**); bool associatedOrJoined(struct nlattr**); @@ -28,8 +32,14 @@ class Network : public IModule { Gtk::Label label_; waybar::util::SleeperThread thread_; Json::Value config_; - std::size_t ifid_; + + int ifid_; + sa_family_t family_; + int sock_fd_; + struct sockaddr_nl nladdr_ = {0}; + std::string essid_; + std::string ifname_; int signal_strength_dbm_; uint16_t signal_strength_; }; diff --git a/resources/config b/resources/config index 479c6707..ba3073fc 100644 --- a/resources/config +++ b/resources/config @@ -30,8 +30,10 @@ "format-icons": ["", "", "", "", ""] }, "network": { - "interface": "wlp2s0", - "format": "{essid} ({signalStrength}%) " + // "interface": "wlp2s0", // (Optional) To force the use of this interface + "format-wifi": "{essid} ({signalStrength}%) ", + "format-ethernet": "{ifname} ", + "format-disconnected": "Disconnected ⚠" }, "pulseaudio": { "format": "{volume}% {icon}", diff --git a/resources/style.css b/resources/style.css index 2c92e824..2ef0bdf4 100644 --- a/resources/style.css +++ b/resources/style.css @@ -76,6 +76,10 @@ window { background: #2980b9; } +#network.disconnected { + background: #f53c3c; +} + #pulseaudio { background: #f1c40f; color: black; diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 455e3b93..fe18b7b0 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -27,7 +27,7 @@ waybar::modules::Battery::Battery(Json::Value config) Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Battery::update)); label_.set_name("battery"); thread_ = [this, fd] { - struct inotify_event event = {}; + struct inotify_event event = {0}; int nbytes = read(fd, &event, sizeof(event)); if (nbytes != sizeof(event)) { return; diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index a484dcf7..9668c6a8 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -13,7 +13,7 @@ waybar::modules::Cpu::Cpu(Json::Value config) auto waybar::modules::Cpu::update() -> void { - struct sysinfo info = {}; + struct sysinfo info = {0}; if (sysinfo(&info) == 0) { float f_load = 1.f / (1U << SI_LOAD_SHIFT); uint16_t load = info.loads[0] * f_load * 100 / get_nprocs(); diff --git a/src/modules/memory.cpp b/src/modules/memory.cpp index f49c9cfc..4c7c18ba 100644 --- a/src/modules/memory.cpp +++ b/src/modules/memory.cpp @@ -13,7 +13,7 @@ waybar::modules::Memory::Memory(Json::Value config) auto waybar::modules::Memory::update() -> void { - struct sysinfo info = {}; + struct sysinfo info = {0}; if (sysinfo(&info) == 0) { auto total = info.totalram * info.mem_unit; auto freeram = info.freeram * info.mem_unit; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 83037e7f..17ea13cf 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -1,34 +1,272 @@ #include "modules/network.hpp" -#include - waybar::modules::Network::Network(Json::Value config) - : config_(std::move(config)), - ifid_(if_nametoindex(config_["interface"].asCString())), + : config_(std::move(config)), family_(AF_INET), signal_strength_dbm_(0), signal_strength_(0) { - if (ifid_ == 0) { - throw std::runtime_error("Can't found network interface"); + sock_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock_fd_ < 0) { + throw std::runtime_error("Can't open network socket"); + } + nladdr_.nl_family = AF_NETLINK; + nladdr_.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; + if (bind(sock_fd_, reinterpret_cast(&nladdr_), + sizeof(nladdr_)) != 0) { + throw std::runtime_error("Can't bind network socket"); + } + if (config_["interface"]) { + ifid_ = if_nametoindex(config_["interface"].asCString()); + ifname_ = config_["interface"].asString(); + if (ifid_ <= 0) { + throw std::runtime_error("Can't found network interface"); + } + } else { + ifid_ = getExternalInterface(); + if (ifid_ > 0) { + char ifname[IF_NAMESIZE]; + if_indextoname(ifid_, ifname); + ifname_ = ifname; + } } label_.set_name("network"); - uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 30; - thread_ = [this, interval] { - getInfo(); - Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update)); - thread_.sleep_for(chrono::seconds(interval)); + getInfo(); + Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update)); + thread_ = [this] { + char buf[4096]; + uint64_t len = netlinkResponse(sock_fd_, buf, sizeof(buf), + RTMGRP_LINK | RTMGRP_IPV4_IFADDR); + bool need_update = false; + for (auto nh = reinterpret_cast(buf); NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_type == NLMSG_DONE) { + break; + } + if (nh->nlmsg_type == NLMSG_ERROR) { + continue; + } + if (nh->nlmsg_type < RTM_NEWADDR) { + auto rtif = static_cast(NLMSG_DATA(nh)); + if (rtif->ifi_index == static_cast(ifid_)) { + need_update = true; + if (!(rtif->ifi_flags & IFF_RUNNING)) { + disconnected(); + } + } + } + } + if (ifid_ <= 0 && !config_["interface"]) { + // Need to wait before get external interface + thread_.sleep_for(std::chrono::seconds(1)); + ifid_ = getExternalInterface(); + if (ifid_ > 0) { + char ifname[IF_NAMESIZE]; + if_indextoname(ifid_, ifname); + ifname_ = ifname; + need_update = true; + } + } + if (need_update) { + getInfo(); + Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update)); + } }; }; auto waybar::modules::Network::update() -> void { - auto format = config_["format"] ? config_["format"].asString() : "{essid}"; + auto format = config_["format"] ? config_["format"].asString() : "{ifname}"; + if (ifid_ <= 0) { + format = config_["format-disconnected"] + ? config_["format-disconnected"].asString() : format; + label_.get_style_context()->add_class("disconnected"); + } else { + if (essid_.empty()) { + format = config_["format-ethernet"] + ? config_["format-ethernet"].asString() : format; + } else { + format = config_["format-wifi"] + ? config_["format-wifi"].asString() : format; + } + label_.get_style_context()->remove_class("disconnected"); + } label_.set_text(fmt::format(format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), - fmt::arg("signalStrength", signal_strength_) + fmt::arg("signalStrength", signal_strength_), + fmt::arg("ifname", ifname_) )); } +void waybar::modules::Network::disconnected() +{ + essid_.clear(); + signal_strength_dbm_ = 0; + signal_strength_ = 0; + ifname_.clear(); + ifid_ = -1; +} + +// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 +int waybar::modules::Network::getExternalInterface() +{ + struct nlmsghdr *hdr = nullptr; + struct rtmsg *rt = nullptr; + void *resp = nullptr; + int ifidx = -1; + + /* Allocate space for the request. */ + uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); + void *req = nullptr; + if ((req = calloc(1, reqlen)) == nullptr) { + goto out; /* ENOBUFS */ + } + + /* Build the RTM_GETROUTE request. */ + hdr = static_cast(req); + hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt)); + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + hdr->nlmsg_type = RTM_GETROUTE; + rt = static_cast(NLMSG_DATA(hdr)); + rt->rtm_family = family_; + rt->rtm_table = RT_TABLE_MAIN; + + /* Issue the query. */ + if (netlinkRequest(sock_fd_, req, reqlen) < 0) { + goto out; + } + + /* Allocate space for the response. */ + static const uint32_t route_buffer_size = 8192; + if ((resp = calloc(1, route_buffer_size)) == nullptr) { + goto out; /* ENOBUFS */ + } + + /* Read the response(s). + * + * WARNING: All the packets generated by the request must be consumed (as in, + * consume responses till NLMSG_DONE/NLMSG_ERROR is encountered). + */ + do { + uint64_t len = netlinkResponse(sock_fd_, resp, route_buffer_size); + if (len < 0) { + goto out; + } + + /* Parse the response payload into netlink messages. */ + for (hdr = static_cast(resp); NLMSG_OK(hdr, len); + hdr = NLMSG_NEXT(hdr, len)) { + if (hdr->nlmsg_type == NLMSG_DONE) { + goto out; + } + if (hdr->nlmsg_type == NLMSG_ERROR) { + /* Even if we found the interface index, something is broken with the + * netlink socket, so return an error. + */ + ifidx = -1; + goto out; + } + + /* If we found the correct answer, skip parsing the attributes. */ + if (ifidx != -1) { + continue; + } + + /* Find the message(s) concerting the main routing table, each message + * corresponds to a single routing table entry. + */ + rt = static_cast(NLMSG_DATA(hdr)); + if (rt->rtm_table != RT_TABLE_MAIN) { + continue; + } + + /* Parse all the attributes for a single routing table entry. */ + struct rtattr *attr = RTM_RTA(rt); + uint64_t attrlen = RTM_PAYLOAD(hdr); + bool has_gateway = false; + bool has_destination = false; + int temp_idx = -1; + for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { + /* Determine if this routing table entry corresponds to the default + * route by seeing if it has a gateway, and if a destination addr is + * set, that it is all 0s. + */ + switch (attr->rta_type) { + case RTA_GATEWAY: + /* The gateway of the route. + * + * If someone every needs to figure out the gateway address as well, + * it's here as the attribute payload. + */ + has_gateway = true; + break; + case RTA_DST: { + /* The destination address. + * Should be either missing, or maybe all 0s. Accept both. + */ + const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16; + unsigned char c = 0; + size_t dstlen = RTA_PAYLOAD(attr); + if (dstlen != nr_zeroes) { + break; + } + for (uint32_t i = 0; i < dstlen; i += 1) { + c |= *(unsigned char *)(RTA_DATA(attr) + i); + } + has_destination = (c == 0); + break; + } + case RTA_OIF: + /* The output interface index. */ + temp_idx = *static_cast(RTA_DATA(attr)); + break; + default: + break; + } + } + /* If this is the default route, and we know the interface index, + * we can stop parsing this message. + */ + if (has_gateway && !has_destination && temp_idx != -1) { + ifidx = temp_idx; + break; + } + } + } while (true); + +out: + if (req) + free(req); + if (resp) + free(resp); + return ifidx; +} + +uint64_t waybar::modules::Network::netlinkRequest(int fd, void *req, + uint32_t reqlen, uint32_t groups) +{ + struct sockaddr_nl sa = {0}; + sa.nl_family = AF_NETLINK; + sa.nl_groups = groups; + struct iovec iov = { req, reqlen }; + struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; + return sendmsg(fd, &msg, 0); +} + +uint64_t waybar::modules::Network::netlinkResponse(int fd, void *resp, + uint32_t resplen, uint32_t groups) +{ + uint64_t ret; + struct sockaddr_nl sa = {0}; + sa.nl_family = AF_NETLINK; + sa.nl_groups = groups; + struct iovec iov = { resp, resplen }; + struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; + ret = recvmsg(fd, &msg, 0); + if (msg.msg_flags & MSG_TRUNC) + return -1; + return ret; +} + int waybar::modules::Network::scanCb(struct nl_msg *msg, void *data) { auto net = static_cast(data); auto gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index ead9d7de..6840c6dd 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -34,7 +34,7 @@ std::string getSocketPath() { } int ipcOpenSocket(const std::string &socketPath) { - struct sockaddr_un addr = {}; + struct sockaddr_un addr = {0}; int socketfd; if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { throw std::runtime_error("Unable to open Unix socket");