From dbc06abf1826a3c5752a39f1843144a0cc2a87a1 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 1/7] network: Initialise cidr_ like clearIface() does --- src/modules/network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a8aaffaf..8dcfb572 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -86,7 +86,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), - cidr_(-1), + cidr_(0), signal_strength_dbm_(0), signal_strength_(0), #ifdef WANT_RFKILL From 9357a6cb8802b3d57a6c140a3d9a9a2068403089 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 2/7] network: Start the module with some text in the label_ Fix modules starting with no text, but not hidding. Start with some "text" in the module's label_, update() will then update it. Since the text should be different, update() will be able to show or hide the event_box_. This is to work around the case where the module start with no text, but the the event_box_ is shown. --- src/modules/network.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 8dcfb572..e52ed027 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -93,6 +93,13 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf rfkill_{RFKILL_TYPE_WLAN}, #endif frequency_(0) { + + // Start with some "text" in the module's label_, update() will then + // update it. Since the text should be different, update() will be able + // to show or hide the event_box_. This is to work around the case where + // the module start with no text, but the the event_box_ is shown. + label_.set_markup(""); + auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); if (down_octets) { From 63fdf66ad66914354d4ac510f08d51dd25389c36 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 3/7] network: Read all available messages on ev_sock_ When more than one message is available to read on the ev_sock_ socket, only the first one is read. Make some changes to be able to read all the messages available by setting the socket to non-blocking. This way we can detect when there's nothing left to read and loop back to wait with epoll. --- src/modules/network.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e52ed027..bd84c1a6 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -160,6 +160,9 @@ void waybar::modules::Network::createEventSocket() { if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) { throw std::runtime_error("Can't connect network socket"); } + if (nl_socket_set_nonblocking(ev_sock_)) { + throw std::runtime_error("Can't set non-blocking on network socket"); + } nl_socket_add_membership(ev_sock_, RTNLGRP_LINK); if (family_ == AF_INET) { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); @@ -235,7 +238,23 @@ void waybar::modules::Network::worker() { int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1); if (ec > 0) { for (auto i = 0; i < ec; i++) { - if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) { + if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) { + int rc = 0; + // Read as many message as possible, until the socket blocks + while (true) { + errno = 0; + rc = nl_recvmsgs_default(ev_sock_); + if (rc == -NLE_AGAIN || errno == EAGAIN) { + rc = 0; + break; + } + } + if (rc < 0) { + spdlog::error("nl_recvmsgs_default error: {}", nl_geterror(-rc)); + thread_.stop(); + break; + } + } else { thread_.stop(); break; } From c9bbaa7241bb40f06bed523c54faa9a8643f7ec2 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 4/7] network: Rework initial interface search by using a dump Instead of using an alternative way to list all links in order to choose one when an "interface" is in the configuration, we can ask for a dump of all interface an reuse the handleEvents() function. This patch also start to rework the handleEvents() function to grab more information out of each event, like the interface name. --- include/modules/network.hpp | 6 ++ src/modules/network.cpp | 130 ++++++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 41 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 539f4583..2d3a1ff1 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -28,8 +28,11 @@ class Network : public ALabel { static const uint8_t EPOLL_MAX = 200; static int handleEvents(struct nl_msg*, void*); + static int handleEventsDone(struct nl_msg*, void*); static int handleScan(struct nl_msg*, void*); + void askForStateDump(void); + void worker(); void createInfoSocket(); void createEventSocket(); @@ -59,6 +62,9 @@ class Network : public ALabel { int nl80211_id_; std::mutex mutex_; + bool want_link_dump_; + bool dump_in_progress_; + unsigned long long bandwidth_down_total_; unsigned long long bandwidth_up_total_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index bd84c1a6..65d82b62 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -86,6 +86,8 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), + want_link_dump_(false), + dump_in_progress_(false), cidr_(0), signal_strength_dbm_(0), signal_strength_(0), @@ -114,6 +116,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf bandwidth_up_total_ = 0; } + if (config_["interface"].isString()) { + // Look for an interface that match "interface" + want_link_dump_ = true; + } + createEventSocket(); createInfoSocket(); auto default_iface = getPreferredIface(-1, false); @@ -125,6 +132,10 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf getInterfaceAddress(); } dp.emit(); + // Ask for a dump of interfaces and then addresses to populate our + // information. First the interface dump, and once done, the callback + // will be called again which will ask for addresses dump. + askForStateDump(); worker(); } @@ -155,6 +166,7 @@ void waybar::modules::Network::createEventSocket() { ev_sock_ = nl_socket_alloc(); nl_socket_disable_seq_check(ev_sock_); nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this); + nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this); auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR); nl_join_groups(ev_sock_, groups); // Deprecated if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) { @@ -590,28 +602,8 @@ bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::strin int waybar::modules::Network::getPreferredIface(int skip_idx, bool wait) const { int ifid = -1; if (config_["interface"].isString()) { - ifid = if_nametoindex(config_["interface"].asCString()); - if (ifid > 0) { - return ifid; - } else { - // Try with wildcard - struct ifaddrs *ifaddr, *ifa; - int success = getifaddrs(&ifaddr); - if (success != 0) { - return -1; - } - ifa = ifaddr; - ifid = -1; - while (ifa != nullptr) { - if (wildcardMatch(config_["interface"].asString(), ifa->ifa_name)) { - ifid = if_nametoindex(ifa->ifa_name); - break; - } - ifa = ifa->ifa_next; - } - freeifaddrs(ifaddr); - return ifid; - } + // Using link dump instead + return -1; } // getExternalInterface may need some delay to detect external interface for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) { @@ -656,6 +648,55 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { std::lock_guard lock(net->mutex_); auto nh = nlmsg_hdr(msg); auto ifi = static_cast(NLMSG_DATA(nh)); + bool is_del_event = false; + + switch (nh->nlmsg_type) { + case RTM_DELLINK: + is_del_event = true; + case RTM_NEWLINK: { + struct ifinfomsg *ifi = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = IFLA_PAYLOAD(nh); + struct rtattr *ifla = IFLA_RTA(ifi); + const char *ifname = NULL; + size_t ifname_len = 0; + + if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) { + return NL_OK; + } + + for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) { + switch (ifla->rta_type) { + case IFLA_IFNAME: + ifname = static_cast(RTA_DATA(ifla)); + ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0 + break; + } + } + + if (!is_del_event && net->ifid_ == -1) { + // Checking if it's an interface we care about. + std::string new_ifname (ifname, ifname_len); + if (net->checkInterface(ifi, new_ifname)) { + spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); + + net->ifname_ = new_ifname; + net->ifid_ = ifi->ifi_index; + net->getInterfaceAddress(); + net->thread_timer_.wake_up(); + } + } else if (is_del_event && net->ifid_ >= 0) { + // Our interface has been deleted, start looking/waiting for one we care. + spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_); + + net->clearIface(); + // Check for a new interface and get info + net->checkNewInterface(ifi); + net->dp.emit(); + } + return NL_OK; + } + } + if (nh->nlmsg_type == RTM_DELADDR) { // Check for valid interface if (ifi->ifi_index == net->ifid_) { @@ -671,25 +712,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } return NL_OK; } - } else if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) { - char ifname[IF_NAMESIZE]; - if_indextoname(ifi->ifi_index, ifname); - // Check for valid interface - if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) { - net->ifname_ = ifname; - net->ifid_ = ifi->ifi_index; - // Get Iface and WIFI info - net->getInterfaceAddress(); - net->thread_timer_.wake_up(); - return NL_OK; - } else if (ifi->ifi_index == net->ifid_ && - (!(ifi->ifi_flags & IFF_RUNNING) || !(ifi->ifi_flags & IFF_UP) || - !net->checkInterface(ifi, ifname))) { - net->clearIface(); - // Check for a new interface and get info - net->checkNewInterface(ifi); - return NL_OK; - } } else { char ifname[IF_NAMESIZE]; if_indextoname(ifi->ifi_index, ifname); @@ -713,6 +735,32 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_SKIP; } +void waybar::modules::Network::askForStateDump(void) { + /* We need to wait until the current dump is done before sending new + * messages. handleEventsDone() is called when a dump is done. */ + if (dump_in_progress_) + return; + + struct rtgenmsg rt_hdr = { + .rtgen_family = AF_UNSPEC, + }; + + if (want_link_dump_) { + nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_link_dump_ = false; + dump_in_progress_ = true; + + } +} + +int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) { + auto net = static_cast(data); + net->dump_in_progress_ = false; + net->askForStateDump(); + return NL_OK; +} + int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { auto net = static_cast(data); auto gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); From 0fc7ef6685321e1b7d966c21c508e94e6fbf3692 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 5/7] network: Rework address lookup to use only events In order to get the IP address of an interface, we can get the information out of NEWADDR events without needed to call getifaddrs(). And when now events are expected, we can requests a dump of all addresses and handle addresses changes the same way via handleEvents() only. --- include/modules/network.hpp | 3 +- src/modules/network.cpp | 176 ++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 92 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 2d3a1ff1..cc8b3f59 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -37,7 +36,6 @@ class Network : public ALabel { void createInfoSocket(); void createEventSocket(); int getExternalInterface(int skip_idx = -1) const; - void getInterfaceAddress(); int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const; int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const; void parseEssid(struct nlattr**); @@ -63,6 +61,7 @@ class Network : public ALabel { std::mutex mutex_; bool want_link_dump_; + bool want_addr_dump_; bool dump_in_progress_; unsigned long long bandwidth_down_total_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 65d82b62..f4cee78b 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -87,6 +87,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf efd_(-1), ev_fd_(-1), want_link_dump_(false), + want_addr_dump_(false), dump_in_progress_(false), cidr_(0), signal_strength_dbm_(0), @@ -118,7 +119,9 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf if (config_["interface"].isString()) { // Look for an interface that match "interface" + // and then find the address associated with it. want_link_dump_ = true; + want_addr_dump_ = true; } createEventSocket(); @@ -129,7 +132,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf char ifname[IF_NAMESIZE]; if_indextoname(default_iface, ifname); ifname_ = ifname; - getInterfaceAddress(); + want_addr_dump_ = true; } dp.emit(); // Ask for a dump of interfaces and then addresses to populate our @@ -502,55 +505,6 @@ out: return ifidx; } -void waybar::modules::Network::getInterfaceAddress() { - struct ifaddrs *ifaddr, *ifa; - cidr_ = 0; - int success = getifaddrs(&ifaddr); - if (success != 0) { - return; - } - ifa = ifaddr; - while (ifa != nullptr) { - if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ && - ifa->ifa_name == ifname_) { - char ipaddr[INET6_ADDRSTRLEN]; - char netmask[INET6_ADDRSTRLEN]; - unsigned int cidr = 0; - if (family_ == AF_INET) { - ipaddr_ = inet_ntop(AF_INET, - &reinterpret_cast(ifa->ifa_addr)->sin_addr, - ipaddr, - INET_ADDRSTRLEN); - auto net_addr = reinterpret_cast(ifa->ifa_netmask); - netmask_ = inet_ntop(AF_INET, &net_addr->sin_addr, netmask, INET_ADDRSTRLEN); - unsigned int cidrRaw = net_addr->sin_addr.s_addr; - while (cidrRaw) { - cidr += cidrRaw & 1; - cidrRaw >>= 1; - } - } else { - ipaddr_ = inet_ntop(AF_INET6, - &reinterpret_cast(ifa->ifa_addr)->sin6_addr, - ipaddr, - INET6_ADDRSTRLEN); - auto net_addr = reinterpret_cast(ifa->ifa_netmask); - netmask_ = inet_ntop(AF_INET6, &net_addr->sin6_addr, netmask, INET6_ADDRSTRLEN); - for (size_t i = 0; i < sizeof(net_addr->sin6_addr.s6_addr); ++i) { - unsigned char cidrRaw = net_addr->sin6_addr.s6_addr[i]; - while (cidrRaw) { - cidr += cidrRaw & 1; - cidrRaw >>= 1; - } - } - } - cidr_ = cidr; - break; - } - ifa = ifa->ifa_next; - } - freeifaddrs(ifaddr); -} - int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const { struct sockaddr_nl sa = {}; sa.nl_family = AF_NETLINK; @@ -635,7 +589,8 @@ void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) { char ifname[IF_NAMESIZE]; if_indextoname(new_iface, ifname); ifname_ = ifname; - getInterfaceAddress(); + want_addr_dump_ = true; + askForStateDump(); thread_timer_.wake_up(); } else { ifid_ = -1; @@ -647,7 +602,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { auto net = static_cast(data); std::lock_guard lock(net->mutex_); auto nh = nlmsg_hdr(msg); - auto ifi = static_cast(NLMSG_DATA(nh)); bool is_del_event = false; switch (nh->nlmsg_type) { @@ -681,8 +635,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->ifname_ = new_ifname; net->ifid_ = ifi->ifi_index; - net->getInterfaceAddress(); net->thread_timer_.wake_up(); + /* An address for this new interface should be received via an + * RTM_NEWADDR event either because we ask for a dump of both links + * and addrs, or because this interface has just been created and + * the addr will be sent after the RTM_NEWLINK event. + * So we don't need to do anything. */ } } else if (is_del_event && net->ifid_ >= 0) { // Our interface has been deleted, start looking/waiting for one we care. @@ -693,46 +651,78 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->checkNewInterface(ifi); net->dp.emit(); } - return NL_OK; + break; + } + + case RTM_DELADDR: + is_del_event = true; + case RTM_NEWADDR: { + struct ifaddrmsg *ifa = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = IFA_PAYLOAD(nh); + struct rtattr *ifa_rta = IFA_RTA(ifa); + + if ((int)ifa->ifa_index != net->ifid_) { + return NL_OK; + } + + if (ifa->ifa_family != net->family_) { + return NL_OK; + } + + // We ignore address mark as scope for the link or host, + // which should leave scope global addresses. + if (ifa->ifa_scope >= RT_SCOPE_LINK) { + return NL_OK; + } + + for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { + switch (ifa_rta->rta_type) { + case IFA_ADDRESS: { + char ipaddr[INET6_ADDRSTRLEN]; + if (!is_del_event) { + net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), + ipaddr, sizeof (ipaddr)); + net->cidr_ = ifa->ifa_prefixlen; + switch (ifa->ifa_family) { + case AF_INET: { + struct in_addr netmask; + netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen)); + net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, + ipaddr, sizeof (ipaddr)); + } + case AF_INET6: { + struct in6_addr netmask; + for (int i = 0; i < 16; i++) { + int v = (i + 1) * 8 - ifa->ifa_prefixlen; + if (v < 0) v = 0; + if (v > 8) v = 8; + netmask.s6_addr[i] = ~0 << v; + } + net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, + ipaddr, sizeof (ipaddr)); + } + } + spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); + } else { + net->ipaddr_.clear(); + net->cidr_ = 0; + net->netmask_.clear(); + spdlog::debug("network: {} addr deleted {}/{}", + net->ifname_, + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), + ipaddr, sizeof (ipaddr)), + ifa->ifa_prefixlen); + } + net->dp.emit(); + break; + } + } + } + break; } } - if (nh->nlmsg_type == RTM_DELADDR) { - // Check for valid interface - if (ifi->ifi_index == net->ifid_) { - net->ipaddr_.clear(); - net->netmask_.clear(); - net->cidr_ = 0; - if (!(ifi->ifi_flags & IFF_RUNNING)) { - net->clearIface(); - // Check for a new interface and get info - net->checkNewInterface(ifi); - } else { - net->dp.emit(); - } - return NL_OK; - } - } else { - char ifname[IF_NAMESIZE]; - if_indextoname(ifi->ifi_index, ifname); - // Auto detected network can also be assigned here - if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) { - // If iface is different, clear data - if (ifi->ifi_index != net->ifid_) { - net->clearIface(); - } - net->ifname_ = ifname; - net->ifid_ = ifi->ifi_index; - } - // Check for valid interface - if (ifi->ifi_index == net->ifid_) { - // Get Iface and WIFI info - net->getInterfaceAddress(); - net->thread_timer_.wake_up(); - return NL_OK; - } - } - return NL_SKIP; + return NL_OK; } void waybar::modules::Network::askForStateDump(void) { @@ -751,6 +741,12 @@ void waybar::modules::Network::askForStateDump(void) { want_link_dump_ = false; dump_in_progress_ = true; + } else if (want_addr_dump_) { + rt_hdr.rtgen_family = family_; + nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_addr_dump_ = false; + dump_in_progress_ = true; } } From 0bb436f9493424d63e66a39e2dc2f5e6e2ada9a3 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 6/7] network: Rework interface auto detection, handle route change events Last part of the rework of handleEvents(), this time we take the getExternalInterface() function and add it to the handleEvents() function. That way, waybar can react immediately when a new "external interface" is available and doesn't need to probe. Also that avoid to have two different functions consuming from the same socket and we don't need to recode some of the functions that are already available via libnl (to send and receive messages). --- include/modules/network.hpp | 9 +- src/modules/network.cpp | 356 +++++++++++++++--------------------- 2 files changed, 146 insertions(+), 219 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index cc8b3f59..964597ab 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -35,17 +34,12 @@ class Network : public ALabel { void worker(); void createInfoSocket(); void createEventSocket(); - int getExternalInterface(int skip_idx = -1) const; - int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const; - int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const; void parseEssid(struct nlattr**); void parseSignal(struct nlattr**); void parseFreq(struct nlattr**); bool associatedOrJoined(struct nlattr**); - bool checkInterface(struct ifinfomsg* rtif, std::string name); - int getPreferredIface(int skip_idx = -1, bool wait = true) const; + bool checkInterface(std::string name); auto getInfo() -> void; - void checkNewInterface(struct ifinfomsg* rtif); const std::string getNetworkState() const; void clearIface(); bool wildcardMatch(const std::string& pattern, const std::string& text) const; @@ -60,6 +54,7 @@ class Network : public ALabel { int nl80211_id_; std::mutex mutex_; + bool want_route_dump_; bool want_link_dump_; bool want_addr_dump_; bool dump_in_progress_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index f4cee78b..508af910 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -86,6 +86,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), + want_route_dump_(false), want_link_dump_(false), want_addr_dump_(false), dump_in_progress_(false), @@ -117,7 +118,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf bandwidth_up_total_ = 0; } - if (config_["interface"].isString()) { + if (!config_["interface"].isString()) { + // "interface" isn't configure, then try to guess the external + // interface currently used for internet. + want_route_dump_ = true; + } else { // Look for an interface that match "interface" // and then find the address associated with it. want_link_dump_ = true; @@ -126,14 +131,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf createEventSocket(); createInfoSocket(); - auto default_iface = getPreferredIface(-1, false); - if (default_iface != -1) { - ifid_ = default_iface; - char ifname[IF_NAMESIZE]; - if_indextoname(default_iface, ifname); - ifname_ = ifname; - want_addr_dump_ = true; - } + dp.emit(); // Ask for a dump of interfaces and then addresses to populate our // information. First the interface dump, and once done, the callback @@ -184,6 +182,14 @@ void waybar::modules::Network::createEventSocket() { } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } + if (!config_["interface"].isString()) { + if (family_ == AF_INET) { + nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE); + } else { + nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE); + } + } + efd_ = epoll_create1(EPOLL_CLOEXEC); if (efd_ < 0) { throw std::runtime_error("Can't create epoll"); @@ -383,196 +389,16 @@ auto waybar::modules::Network::update() -> void { ALabel::update(); } -// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 -int waybar::modules::Network::getExternalInterface(int skip_idx) const { - static const uint32_t route_buffer_size = 8192; - struct nlmsghdr * hdr = nullptr; - struct rtmsg * rt = nullptr; - char resp[route_buffer_size] = {0}; - int ifidx = -1; - - /* Prepare request. */ - constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); - char req[reqlen] = {0}; - - /* Build the RTM_GETROUTE request. */ - hdr = reinterpret_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(req, reqlen) < 0) { - goto out; - } - - /* 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 { - auto len = netlinkResponse(resp, route_buffer_size); - if (len < 0) { - goto out; - } - - /* Parse the response payload into netlink messages. */ - for (hdr = reinterpret_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 && temp_idx != skip_idx) { - ifidx = temp_idx; - break; - } - } - } while (true); - -out: - return ifidx; -} - -int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const { - struct sockaddr_nl sa = {}; - sa.nl_family = AF_NETLINK; - sa.nl_groups = groups; - struct iovec iov = {req, reqlen}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - return sendmsg(nl_socket_get_fd(ev_sock_), &msg, 0); -} - -int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) const { - struct sockaddr_nl sa = {}; - sa.nl_family = AF_NETLINK; - sa.nl_groups = groups; - struct iovec iov = {resp, resplen}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - auto ret = recvmsg(nl_socket_get_fd(ev_sock_), &msg, 0); - if (msg.msg_flags & MSG_TRUNC) { - return -1; - } - return ret; -} - -bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::string name) { +bool waybar::modules::Network::checkInterface(std::string name) { if (config_["interface"].isString()) { return config_["interface"].asString() == name || wildcardMatch(config_["interface"].asString(), name); } - // getExternalInterface may need some delay to detect external interface - for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) { - auto external_iface = getExternalInterface(); - if (external_iface > 0) { - return external_iface == rtif->ifi_index; - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } return false; } -int waybar::modules::Network::getPreferredIface(int skip_idx, bool wait) const { - int ifid = -1; - if (config_["interface"].isString()) { - // Using link dump instead - return -1; - } - // getExternalInterface may need some delay to detect external interface - for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) { - ifid = getExternalInterface(skip_idx); - if (ifid > 0) { - return ifid; - } - if (wait) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - return -1; -} - void waybar::modules::Network::clearIface() { + ifid_ = -1; essid_.clear(); ipaddr_.clear(); netmask_.clear(); @@ -582,22 +408,6 @@ void waybar::modules::Network::clearIface() { frequency_ = 0; } -void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) { - auto new_iface = getPreferredIface(rtif->ifi_index); - if (new_iface != -1) { - ifid_ = new_iface; - char ifname[IF_NAMESIZE]; - if_indextoname(new_iface, ifname); - ifname_ = ifname; - want_addr_dump_ = true; - askForStateDump(); - thread_timer_.wake_up(); - } else { - ifid_ = -1; - dp.emit(); - } -} - int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { auto net = static_cast(data); std::lock_guard lock(net->mutex_); @@ -627,10 +437,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } } - if (!is_del_event && net->ifid_ == -1) { + if (!is_del_event && ifi->ifi_index == net->ifid_) { + // Update inferface information + if (net->ifname_.empty()) { + std::string new_ifname (ifname, ifname_len); + net->ifname_ = new_ifname; + } + } else if (!is_del_event && net->ifid_ == -1) { // Checking if it's an interface we care about. std::string new_ifname (ifname, ifname_len); - if (net->checkInterface(ifi, new_ifname)) { + if (net->checkInterface(new_ifname)) { spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); net->ifname_ = new_ifname; @@ -647,8 +463,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_); net->clearIface(); - // Check for a new interface and get info - net->checkNewInterface(ifi); net->dp.emit(); } break; @@ -720,6 +534,117 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } break; } + + case RTM_DELROUTE: + is_del_event = true; + case RTM_NEWROUTE: { + // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 + // to find the interface used to reach the outside world + + struct rtmsg *rtm = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = RTM_PAYLOAD(nh); + struct rtattr *attr = RTM_RTA(rtm); + bool has_gateway = false; + bool has_destination = false; + int temp_idx = -1; + + /* If we found the correct answer, skip parsing the attributes. */ + if (!is_del_event && net->ifid_ != -1) { + return NL_OK; + } + + /* Find the message(s) concerting the main routing table, each message + * corresponds to a single routing table entry. + */ + if (rtm->rtm_table != RT_TABLE_MAIN) { + return NL_OK; + } + + /* Parse all the attributes for a single routing table entry. */ + 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 = (net->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) { + if (!is_del_event) { + net->ifid_ = temp_idx; + + spdlog::debug("network: new default route via if{}", temp_idx); + + /* Ask ifname associated with temp_idx as well as carrier status */ + struct ifinfomsg ifinfo_hdr = { + .ifi_family = AF_UNSPEC, + .ifi_index = temp_idx, + }; + int err; + err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST, + &ifinfo_hdr, sizeof (ifinfo_hdr)); + if (err < 0) { + spdlog::error("network: failed to ask link info: {}", err); + /* Ask for a dump of all links instead */ + net->want_link_dump_ = true; + } + + /* Also ask for the address. Asking for a addresses of a specific + * interface doesn't seems to work so ask for a dump of all + * addresses. */ + net->want_addr_dump_ = true; + net->askForStateDump(); + net->thread_timer_.wake_up(); + } else if (is_del_event && temp_idx == net->ifid_) { + spdlog::debug("network: default route deleted {}/if{}", + net->ifname_, temp_idx); + + net->ifname_.clear(); + net->clearIface(); + net->dp.emit(); + /* Ask for a dump of all routes in case another one is already + * setup. If there's none, there'll be an event with new one + * later. */ + net->want_route_dump_ = true; + net->askForStateDump(); + } + } + } + break; + } } return NL_OK; @@ -735,7 +660,14 @@ void waybar::modules::Network::askForStateDump(void) { .rtgen_family = AF_UNSPEC, }; - if (want_link_dump_) { + if (want_route_dump_) { + rt_hdr.rtgen_family = family_; + nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_route_dump_ = false; + dump_in_progress_ = true; + + } else if (want_link_dump_) { nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP, &rt_hdr, sizeof (rt_hdr)); want_link_dump_ = false; From c1427ff8072aa9573a09941fbaa1c45af5005d89 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Sat, 15 May 2021 16:38:00 +0100 Subject: [PATCH 7/7] network: Handle carrier information IFLA_CARRIER allows to know when a cable is plugged to the Ethernet card or when the WiFi is connected. If there's no carrier, the interface will be considered disconnected. --- include/modules/network.hpp | 1 + src/modules/network.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 964597ab..2523dc22 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -64,6 +64,7 @@ class Network : public ALabel { std::string state_; std::string essid_; + bool carrier_; std::string ifname_; std::string ipaddr_; std::string netmask_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 508af910..6102a43c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -292,6 +292,7 @@ const std::string waybar::modules::Network::getNetworkState() const { #endif return "disconnected"; } + if (!carrier_) return "disconnected"; if (ipaddr_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; @@ -402,6 +403,7 @@ void waybar::modules::Network::clearIface() { essid_.clear(); ipaddr_.clear(); netmask_.clear(); + carrier_ = false; cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; @@ -423,6 +425,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { struct rtattr *ifla = IFLA_RTA(ifi); const char *ifname = NULL; size_t ifname_len = 0; + bool carrier = false; if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) { return NL_OK; @@ -434,6 +437,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { ifname = static_cast(RTA_DATA(ifla)); ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0 break; + case IFLA_CARRIER: { + carrier = *(char*)RTA_DATA(ifla) == 1; + break; + } } } @@ -443,6 +450,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { std::string new_ifname (ifname, ifname_len); net->ifname_ = new_ifname; } + net->carrier_ = carrier; } else if (!is_del_event && net->ifid_ == -1) { // Checking if it's an interface we care about. std::string new_ifname (ifname, ifname_len); @@ -451,6 +459,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->ifname_ = new_ifname; net->ifid_ = ifi->ifi_index; + net->carrier_ = carrier; net->thread_timer_.wake_up(); /* An address for this new interface should be received via an * RTM_NEWADDR event either because we ask for a dump of both links