Merge pull request #1106 from tperard/network-module-reword
Network module rework
This commit is contained in:
		
						commit
						f78a802d11
					
				|  | @ -2,9 +2,7 @@ | |||
| 
 | ||||
| #include <arpa/inet.h> | ||||
| #include <fmt/format.h> | ||||
| #include <ifaddrs.h> | ||||
| #include <linux/nl80211.h> | ||||
| #include <net/if.h> | ||||
| #include <netlink/genl/ctrl.h> | ||||
| #include <netlink/genl/genl.h> | ||||
| #include <netlink/netlink.h> | ||||
|  | @ -28,23 +26,20 @@ 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(); | ||||
|   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**); | ||||
|   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; | ||||
|  | @ -59,11 +54,17 @@ 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_; | ||||
| 
 | ||||
|   unsigned long long bandwidth_down_total_; | ||||
|   unsigned long long bandwidth_up_total_; | ||||
| 
 | ||||
|   std::string state_; | ||||
|   std::string essid_; | ||||
|   bool        carrier_; | ||||
|   std::string ifname_; | ||||
|   std::string ipaddr_; | ||||
|   std::string netmask_; | ||||
|  |  | |||
|  | @ -86,13 +86,24 @@ 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), | ||||
|       want_route_dump_(false), | ||||
|       want_link_dump_(false), | ||||
|       want_addr_dump_(false), | ||||
|       dump_in_progress_(false), | ||||
|       cidr_(0), | ||||
|       signal_strength_dbm_(0), | ||||
|       signal_strength_(0), | ||||
| #ifdef WANT_RFKILL | ||||
|       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("<s></s>"); | ||||
| 
 | ||||
|   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) { | ||||
|  | @ -107,17 +118,25 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf | |||
|     bandwidth_up_total_ = 0; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|     want_addr_dump_ = true; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|     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(); | ||||
| } | ||||
| 
 | ||||
|  | @ -148,17 +167,29 @@ 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) { | ||||
|     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); | ||||
|   } 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"); | ||||
|  | @ -228,7 +259,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; | ||||
|         } | ||||
|  | @ -245,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"; | ||||
|  | @ -342,349 +390,312 @@ 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<struct nlmsghdr *>(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<struct rtmsg *>(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<struct nlmsghdr *>(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<struct rtmsg *>(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<int *>(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; | ||||
| } | ||||
| 
 | ||||
| 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<struct sockaddr_in *>(ifa->ifa_addr)->sin_addr, | ||||
|                             ipaddr, | ||||
|                             INET_ADDRSTRLEN); | ||||
|         auto net_addr = reinterpret_cast<struct sockaddr_in *>(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<struct sockaddr_in6 *>(ifa->ifa_addr)->sin6_addr, | ||||
|                             ipaddr, | ||||
|                             INET6_ADDRSTRLEN); | ||||
|         auto net_addr = reinterpret_cast<struct sockaddr_in6 *>(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; | ||||
|   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()) { | ||||
|     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; | ||||
|     } | ||||
|   } | ||||
|   // 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(); | ||||
|   carrier_ = false; | ||||
|   cidr_ = 0; | ||||
|   signal_strength_dbm_ = 0; | ||||
|   signal_strength_ = 0; | ||||
|   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; | ||||
|     getInterfaceAddress(); | ||||
|     thread_timer_.wake_up(); | ||||
|   } else { | ||||
|     ifid_ = -1; | ||||
|     dp.emit(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | ||||
|   auto                        net = static_cast<waybar::modules::Network *>(data); | ||||
|   std::lock_guard<std::mutex> lock(net->mutex_); | ||||
|   auto                        nh = nlmsg_hdr(msg); | ||||
|   auto                        ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); | ||||
|   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(); | ||||
|       } | ||||
|   bool                        is_del_event = false; | ||||
| 
 | ||||
|   switch (nh->nlmsg_type) { | ||||
|   case RTM_DELLINK: | ||||
|     is_del_event = true; | ||||
|   case RTM_NEWLINK: { | ||||
|     struct ifinfomsg *ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); | ||||
|     ssize_t attrlen = IFLA_PAYLOAD(nh); | ||||
|     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; | ||||
|     } | ||||
|   } 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))) { | ||||
| 
 | ||||
|     for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) { | ||||
|       switch (ifla->rta_type) { | ||||
|       case IFLA_IFNAME: | ||||
|         ifname = static_cast<const char *>(RTA_DATA(ifla)); | ||||
|         ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
 | ||||
|         break; | ||||
|       case IFLA_CARRIER: { | ||||
|         carrier = *(char*)RTA_DATA(ifla) == 1; | ||||
|         break; | ||||
|       } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|       } | ||||
|       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); | ||||
|       if (net->checkInterface(new_ifname)) { | ||||
|         spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); | ||||
| 
 | ||||
|         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 | ||||
|          * 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.
 | ||||
|       spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_); | ||||
| 
 | ||||
|       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); | ||||
|     // 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; | ||||
|       net->dp.emit(); | ||||
|     } | ||||
|     break; | ||||
|   } | ||||
|   return NL_SKIP; | ||||
| 
 | ||||
|   case RTM_DELADDR: | ||||
|     is_del_event = true; | ||||
|   case RTM_NEWADDR: { | ||||
|     struct ifaddrmsg *ifa = static_cast<struct ifaddrmsg *>(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; | ||||
|   } | ||||
| 
 | ||||
|   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<struct rtmsg *>(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<int *>(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; | ||||
| } | ||||
| 
 | ||||
| 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_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; | ||||
|     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; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) { | ||||
|   auto net = static_cast<waybar::modules::Network *>(data); | ||||
|   net->dump_in_progress_ = false; | ||||
|   net->askForStateDump(); | ||||
|   return NL_OK; | ||||
| } | ||||
| 
 | ||||
| int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue