polybar/src/adapters/net.cpp

450 lines
12 KiB
C++

#include "adapters/net.hpp"
#include <arpa/inet.h>
#include <dirent.h>
#include <linux/ethtool.h>
#include <linux/if_link.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <cassert>
#include <iomanip>
#include "common.hpp"
#include "settings.hpp"
#include "utils/command.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
POLYBAR_NS
namespace net {
enum class NetType {
WIRELESS,
ETHERNET,
OTHER,
};
static const string NO_IP = string("N/A");
static const string NO_MAC = string("N/A");
static const string NET_PATH = "/sys/class/net/";
static const string VIRTUAL_PATH = "/sys/devices/virtual/";
static bool is_virtual(const std::string& ifname) {
char* target = realpath((NET_PATH + ifname).c_str(), nullptr);
if (!target) {
throw system_error("realpath");
}
const std::string real_path{target};
free(target);
return real_path.rfind(VIRTUAL_PATH, 0) == 0;
}
NetType iface_type(const std::string& ifname) {
if (file_util::exists(NET_PATH + ifname + "/wireless")) {
return NetType::WIRELESS;
}
if (is_virtual(ifname)) {
return NetType::OTHER;
}
return NetType::ETHERNET;
}
bool is_interface_valid(const string& ifname) {
return if_nametoindex(ifname.c_str()) != 0;
}
/**
* Returns the canonical name of the given interface and whether that differs
* from the given name.
*
* The canonical name is the name that has an entry in `/sys/class/net`.
*
* This resolves any altnames that were defined (see man ip-link).
*/
std::pair<string, bool> get_canonical_interface(const string& ifname) {
int idx = if_nametoindex(ifname.c_str());
if (idx == 0) {
throw system_error("if_nameindex(" + ifname + ")");
}
char canonical[IF_NAMESIZE];
if (!if_indextoname(idx, canonical)) {
throw system_error("if_indextoname(" + to_string(idx) + ")");
}
string str{canonical};
return {str, str != ifname};
}
/**
* Test if interface with given name is a wireless device
*/
bool is_wireless_interface(const string& ifname) {
return iface_type(ifname) == NetType::WIRELESS;
}
std::string find_interface(NetType type) {
struct ifaddrs* ifaddrs;
getifaddrs(&ifaddrs);
struct ifaddrs* candidate_if = nullptr;
for (struct ifaddrs* i = ifaddrs; i != nullptr; i = i->ifa_next) {
const std::string name{i->ifa_name};
const NetType iftype = iface_type(name);
if (iftype != type) {
continue;
}
if (candidate_if == nullptr) {
candidate_if = i;
} else if (((candidate_if->ifa_flags & IFF_RUNNING) == 0) && ((i->ifa_flags & IFF_RUNNING) > 0)) {
candidate_if = i;
}
}
if (candidate_if) {
const std::string name{candidate_if->ifa_name};
freeifaddrs(ifaddrs);
return name;
}
freeifaddrs(ifaddrs);
return "";
}
std::string find_wireless_interface() {
return find_interface(NetType::WIRELESS);
}
std::string find_wired_interface() {
return find_interface(NetType::ETHERNET);
}
// class : network {{{
/**
* Construct network interface
*/
network::network(string interface) : m_log(logger::make()), m_interface(move(interface)) {
assert(is_interface_valid(m_interface));
m_socketfd = file_util::make_file_descriptor(socket(AF_INET, SOCK_DGRAM, 0));
if (!*m_socketfd) {
throw network_error("Failed to open socket");
}
check_tuntap_or_bridge();
}
/**
* Query device driver for information
*/
bool network::query(bool accumulate) {
m_status.previous = m_status.current;
m_status.current.transmitted = 0;
m_status.current.received = 0;
m_status.current.time = std::chrono::steady_clock::now();
m_status.ip = NO_IP;
m_status.ip6 = NO_IP;
struct ifaddrs* ifaddr;
if (getifaddrs(&ifaddr) == -1 || ifaddr == nullptr) {
return false;
}
m_status.mac = string_util::trim(file_util::contents(NET_PATH + m_interface + "/address"), isspace);
if (m_status.mac == "") {
m_status.mac = NO_MAC;
}
for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr) {
continue;
}
if (m_interface.compare(0, m_interface.length(), ifa->ifa_name) != 0) {
if (!accumulate || (ifa->ifa_data == nullptr && ifa->ifa_addr->sa_family != AF_PACKET)) {
continue;
}
}
struct sockaddr_in6* sa6;
switch (ifa->ifa_addr->sa_family) {
case AF_INET:
char ip_buffer[NI_MAXHOST];
getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), ip_buffer, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
m_status.ip = string{ip_buffer};
break;
case AF_INET6:
char ip6_buffer[INET6_ADDRSTRLEN];
sa6 = reinterpret_cast<decltype(sa6)>(ifa->ifa_addr);
if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) {
continue;
}
if (IN6_IS_ADDR_SITELOCAL(&sa6->sin6_addr)) {
continue;
}
if ((((unsigned char*)sa6->sin6_addr.s6_addr)[0] & 0xFE) == 0xFC) {
/* Skip Unique Local Addresses (fc00::/7) */
continue;
}
if (inet_ntop(AF_INET6, &sa6->sin6_addr, ip6_buffer, INET6_ADDRSTRLEN) == 0) {
m_log.warn("inet_ntop() " + string(strerror(errno)));
continue;
}
m_status.ip6 = string{ip6_buffer};
break;
case AF_PACKET:
if (ifa->ifa_data == nullptr) {
continue;
}
struct rtnl_link_stats* link_state = reinterpret_cast<decltype(link_state)>(ifa->ifa_data);
if (link_state == nullptr) {
continue;
}
m_status.current.transmitted += link_state->tx_bytes;
m_status.current.received += link_state->rx_bytes;
break;
}
}
freeifaddrs(ifaddr);
return true;
}
/**
* Run ping command to test internet connectivity
*/
bool network::ping() const {
try {
auto exec = "ping -c 2 -W 2 -I " + m_interface + " " + string(CONNECTION_TEST_IP);
command<output_policy::IGNORED> ping(m_log, exec);
return ping.exec(true) == EXIT_SUCCESS;
} catch (const std::exception& err) {
return false;
}
}
/**
* Get interface ipv4 address
*/
string network::ip() const {
return m_status.ip;
}
/**
* Get interface mac address
*/
string network::mac() const {
return m_status.mac;
}
/**
* Get interface ipv6 address
*/
string network::ip6() const {
return m_status.ip6;
}
/**
* Get download speed rate
*/
string network::downspeed(int minwidth, const string& unit) const {
float bytes_diff = m_status.current.received - m_status.previous.received;
return format_speedrate(bytes_diff, minwidth, unit);
}
/**
* Get upload speed rate
*/
string network::upspeed(int minwidth, const string& unit) const {
float bytes_diff = m_status.current.transmitted - m_status.previous.transmitted;
return format_speedrate(bytes_diff, minwidth, unit);
}
/**
* Get total net speed rate
*/
string network::netspeed(int minwidth, const string& unit) const {
float bytes_diff = m_status.current.received - m_status.previous.received + m_status.current.transmitted -
m_status.previous.transmitted;
return format_speedrate(bytes_diff, minwidth, unit);
}
/**
* Set if unknown counts as up
*/
void network::set_unknown_up(bool unknown) {
m_unknown_up = unknown;
}
/**
* Query driver info to check if the
* interface is a TUN/TAP device or BRIDGE
*/
void network::check_tuntap_or_bridge() {
struct ethtool_drvinfo driver {};
struct ifreq request {};
driver.cmd = ETHTOOL_GDRVINFO;
memset(&request, 0, sizeof(request));
/*
* 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);
request.ifr_data = reinterpret_cast<char*>(&driver);
if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
return;
}
// Check if it's a TUN/TAP device
if (strncmp(driver.bus_info, "tun", 3) == 0) {
m_tuntap = true;
} else if (strncmp(driver.bus_info, "tap", 3) == 0) {
m_tuntap = true;
} else {
m_tuntap = false;
}
if (strncmp(driver.driver, "wireguard", 9) == 0) {
m_tuntap = true;
m_wireguard = true;
}
if (strncmp(driver.driver, "bridge", 6) == 0) {
m_bridge = true;
}
}
/**
* Test if the network interface is in a valid state
*/
bool network::test_interface() const {
auto operstate = file_util::contents(NET_PATH + m_interface + "/operstate");
bool up = operstate.compare(0, 2, "up") == 0;
return m_unknown_up ? (up || operstate.compare(0, 7, "unknown") == 0) : up;
}
/**
* Format up- and download speed
*/
string network::format_speedrate(float bytes_diff, int minwidth, const string& unit) const {
// Get time difference in seconds as a float
const std::chrono::duration<float> duration = m_status.current.time - m_status.previous.time;
float time_diff = duration.count();
float speedrate = bytes_diff / time_diff;
vector<pair<string, int>> units{make_pair("G", 2), make_pair("M", 1)};
string suffix{"K"};
int precision = 0;
while ((speedrate /= 1000) > 999) {
suffix = units.back().first;
precision = units.back().second;
units.pop_back();
}
return sstream() << std::setw(minwidth) << std::setfill(' ') << std::setprecision(precision) << std::fixed
<< speedrate << " " << suffix << unit;
}
// }}}
// class : wired_network {{{
/**
* Query device driver for information
*/
bool wired_network::query(bool accumulate) {
if (!network::query(accumulate)) {
return false;
}
if (m_tuntap) {
return true;
}
if (m_bridge) {
/* If bridge network then link speed cannot be computed
* TODO: Identify the physical network in bridge and compute the link speed
*/
return true;
}
struct ifreq request {};
struct ethtool_cmd data {};
memset(&request, 0, sizeof(request));
strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
data.cmd = ETHTOOL_GSET;
request.ifr_data = reinterpret_cast<char*>(&data);
if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
m_linkspeed = -1;
} else {
m_linkspeed = data.speed;
}
return true;
}
/**
* Check current connection state
*/
bool wired_network::connected() const {
if (!m_tuntap && !network::test_interface()) {
return false;
}
if (m_wireguard) {
return true;
}
struct ethtool_value data {};
struct ifreq request {};
memset(&request, 0, sizeof(request));
strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
data.cmd = ETHTOOL_GLINK;
request.ifr_data = reinterpret_cast<char*>(&data);
if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
return false;
}
return data.data != 0;
}
/**
*
* about the current connection
*/
string wired_network::linkspeed() const {
return m_linkspeed == -1 ? "N/A"
: (m_linkspeed < 1000 ? (to_string(m_linkspeed) + " Mbit/s")
: (to_string(m_linkspeed / 1000) + " Gbit/s"));
}
// }}}
} // namespace net
POLYBAR_NS_END