From 4c4f0ce7a080744d38ee3342f27d3cc0edb7311f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 30 Dec 2018 20:40:10 +0100 Subject: [PATCH] module: network: monitor ethernet interfaces State exposed (tag names): * name: the interface name ("eth0" etc) * index: the interface index, "ifindex" * state: operational state (down, up etc) * mac: the Ethernet hardware address * ipv4: the *last* IPv4 address added to the interface * ipv6: the *last* IPv6 address added to the interface State we monitor (for changes): * state * ipv4 addresses * ipv6 addresses --- CMakeLists.txt | 5 + config.c | 16 ++ modules/network.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++ modules/network.h | 6 + 4 files changed, 413 insertions(+) create mode 100644 modules/network.c create mode 100644 modules/network.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 50eec95..5643ddd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ pkg_check_modules(XCB_XKB REQUIRED xcb-xkb) # Module/xkb pkg_check_modules(JSON REQUIRED json-c) # Module/i3 pkg_check_modules(UDEV REQUIRED libudev) # Module/battery pkg_check_modules(MPD REQUIRED libmpdclient) # Module/mpd +pkg_check_modules(NL REQUIRED libnl-3.0 libnl-route-3.0) # Module/network add_executable(f00bar bar.c bar.h @@ -54,6 +55,7 @@ add_executable(f00bar modules/i3.c modules/i3.h modules/label.c modules/label.h modules/mpd.c modules/mpd.h + modules/network.c modules/network.h modules/xkb.c modules/xkb.h modules/xwindow.c modules/xwindow.h ) @@ -68,6 +70,7 @@ target_compile_options(f00bar PRIVATE ${JSON_CFLAGS_OTHER} ${UDEV_CFLAGS_OTHER} ${MPD_CFLAGS_OTHER} + ${NL_CFLAGS_OTHER} ) target_include_directories(f00bar PRIVATE @@ -78,6 +81,7 @@ target_include_directories(f00bar PRIVATE ${JSON_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS} ${MPD_INCLUDE_DIRS} + ${NL_INCLUDE_DIRS} ) target_link_libraries(f00bar @@ -89,4 +93,5 @@ target_link_libraries(f00bar ${JSON_LIBRARIES} ${UDEV_LIBRARIES} ${MPD_LIBRARIES} + ${NL_LIBRARIES} ) diff --git a/config.c b/config.c index ef80475..eb5dbb5 100644 --- a/config.c +++ b/config.c @@ -27,6 +27,7 @@ #include "modules/i3.h" #include "modules/label.h" #include "modules/mpd.h" +#include "modules/network.h" #include "modules/xkb.h" #include "modules/xwindow.h" @@ -505,6 +506,19 @@ module_mpd_from_config(const struct yml_node *node, particle_from_config(c, parent_font)); } +static struct module * +module_network_from_config(const struct yml_node *node, + const struct font *parent_font) +{ + const struct yml_node *name = yml_get_value(node, "name"); + const struct yml_node *content = yml_get_value(node, "content"); + + assert(yml_is_scalar(name)); + + return module_network( + yml_value_as_string(name), particle_from_config(content, parent_font)); +} + struct bar * conf_to_bar(const struct yml_node *bar) { @@ -615,6 +629,8 @@ conf_to_bar(const struct yml_node *bar) mods[idx] = module_backlight_from_config(it.node, font); else if (strcmp(mod_name, "mpd") == 0) mods[idx] = module_mpd_from_config(it.node, font); + else if (strcmp(mod_name, "network") == 0) + mods[idx] = module_network_from_config(it.node, font); else assert(false); } diff --git a/modules/network.c b/modules/network.c new file mode 100644 index 0000000..43f8291 --- /dev/null +++ b/modules/network.c @@ -0,0 +1,386 @@ +#include "network.h" + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#define LOG_MODULE "network" +#define LOG_ENABLE_DBG 1 +#include "../log.h" +#include "../module.h" +#include "../bar.h" +#include "../tllist.h" + +struct af_addr { + int family; + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } u; +}; + +struct private { + char *iface; + struct particle *label; + + int ifindex; + uint8_t mac[6]; + uint8_t state; /* IFLA_OPERSTATE */ + + /* IPv4 and IPv6 addresses */ + tll(struct af_addr) addrs; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + + m->label->destroy(m->label); + + tll_free(m->addrs); + + free(m->iface); + free(m); + + module_default_destroy(mod); +} + +static struct exposable * +content(struct module *mod) +{ + struct private *m = mod->private; + + mtx_lock(&mod->lock); + + const char *state = NULL; + switch (m->state) { + case IF_OPER_UNKNOWN: state = "unknown"; break; + case IF_OPER_NOTPRESENT: state = "not present"; break; + case IF_OPER_DOWN: state = "down"; break; + case IF_OPER_LOWERLAYERDOWN: state = "lower layers down"; break; + case IF_OPER_TESTING: state = "testing"; break; + case IF_OPER_DORMANT: state = "dormant"; break; + case IF_OPER_UP: state = "up"; break; + default: state = "unknown"; break; + } + + char mac_str[6 * 2 + 5 + 1]; + char ipv4_str[INET_ADDRSTRLEN] = {0}; + char ipv6_str[INET6_ADDRSTRLEN] = {0}; + + snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", + m->mac[0], m->mac[1], m->mac[2], m->mac[3], m->mac[4], m->mac[5]); + + /* TODO: this exposes the *last* added address of each kind. Can + * we expose all in some way? */ + tll_foreach(m->addrs, it) { + if (it->item.family == AF_INET) + inet_ntop(AF_INET, &it->item.u.ipv4, ipv4_str, sizeof(ipv4_str)); + else if (it->item.family == AF_INET6) + inet_ntop(AF_INET6, &it->item.u.ipv6, ipv6_str, sizeof(ipv6_str)); + } + + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_string(mod, "name", m->iface), + tag_new_int(mod, "index", m->ifindex), + tag_new_string(mod, "state", state), + tag_new_string(mod, "mac", mac_str), + tag_new_string(mod, "ipv4", ipv4_str), + tag_new_string(mod, "ipv6", ipv6_str), + }, + .count = 6, + }; + + mtx_unlock(&mod->lock); + + struct exposable *exposable = m->label->instantiate(m->label, &tags); + tag_set_destroy(&tags); + return exposable; +} + +static int +nl_event(struct nl_msg *msg, void *arg) +{ + struct module *mod = arg; + struct private *m = mod->private; + + struct nlmsghdr *hdr = nlmsg_hdr(msg); + + switch (hdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: { + /* First, ignore if this isn't for us */ + const struct ifinfomsg *info = nlmsg_data(hdr); + if (info->ifi_index != m->ifindex) + break; + + /* Parse attributes */ + struct nlattr *attrs[IFLA_MAX + 1]; + int r = nlmsg_parse(hdr, sizeof(*info), attrs, IFLA_MAX, NULL); + if (r < 0) { + LOG_ERR("failed to parse attributes"); + break; + } + + assert(strcmp(nla_get_string(attrs[IFLA_IFNAME]), m->iface) == 0); + + mtx_lock(&mod->lock); + + uint8_t old_state = m->state; + uint8_t new_state = attrs[IFLA_OPERSTATE] != NULL + ? nla_get_u8(attrs[IFLA_OPERSTATE]) + : IF_OPER_DOWN; + + if (old_state != new_state) { + LOG_DBG( + "%s: %s: state: %hhu -> %hhu", + hdr->nlmsg_type == RTM_NEWLINK ? "RTM_NEWLINK" : "RTM_DELLINK", + m->iface, old_state, new_state); + + m->state = new_state; + mod->bar->refresh(mod->bar); + } + + mtx_unlock(&mod->lock); + break; + } + + case RTM_NEWADDR: + case RTM_DELADDR: { + /* Ignore if not for us */ + const struct ifaddrmsg *addr = nlmsg_data(hdr); + if (addr->ifa_index != m->ifindex) + break; + + /* We only support IPv4 and IPv6 */ + if (addr->ifa_family != AF_INET && addr->ifa_family != AF_INET6) + break; + + /* Parse attributes */ + struct nlattr *attrs[IFA_MAX + 1]; + int r = nlmsg_parse(hdr, sizeof(*addr), attrs, IFA_MAX, NULL); + if (r < 0) { + LOG_ERR("failed to parse attributes"); + break; + } + + /* raw_addr is now a "struct in_addr" or a "struct in6_addr" */ + const void *raw_addr = nla_data(attrs[IFA_ADDRESS]); + size_t addr_len = nla_len(attrs[IFA_ADDRESS]); + +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG + char s[INET6_ADDRSTRLEN]; + inet_ntop(addr->ifa_family, raw_addr, s, sizeof(s)); +#endif + + LOG_DBG( + "%s: %s: family=%d, %s", + hdr->nlmsg_type == RTM_NEWADDR ? "RTM_NEWADDR" : "RTM_DELADDR", + m->iface, addr->ifa_family, s); + + mtx_lock(&mod->lock); + + if (hdr->nlmsg_type == RTM_DELADDR) { + /* Find address in our list and remove it */ + tll_foreach(m->addrs, it) { + if (it->item.family != addr->ifa_family) + continue; + + if (memcmp(&it->item.u, raw_addr, addr_len) != 0) + continue; + + tll_remove(m->addrs, it); + break; + } + } else { + /* Append address to our list */ + struct af_addr a = {.family = addr->ifa_family}; + memcpy(&a.u, raw_addr, addr_len); + tll_push_back(m->addrs, a); + } + + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); + break; + } + + default: + LOG_WARN("unknown message type: 0x%x", hdr->nlmsg_type); + break; + } + + return NL_OK; +} + +static int +run(struct module_run_context *ctx) +{ + struct module *mod = ctx->module; + struct private *m = mod->private; + + module_signal_ready(ctx); + + struct nl_sock *conn = nl_socket_alloc(); + if (conn == NULL) { + LOG_ERR("failed to allocate netlink socket"); + return 1; + } + + int r = nl_connect(conn, NETLINK_ROUTE); + if (r < 0) { + LOG_ERR("failed to connect to netlink socket"); + nl_socket_free(conn); + return 1; + } + + /* TODO: get just our link */ + struct nl_cache *links = NULL; + r = rtnl_link_alloc_cache(conn, AF_UNSPEC, &links); + if (r < 0) { + LOG_ERR("failed to fill link cache: %s", nl_geterror(r)); + nl_socket_free(conn); + return 1; + } + + struct rtnl_link *link = rtnl_link_get_by_name(links, m->iface); + if (link == 0) { + LOG_ERR("%s: no such link", m->iface); + nl_cache_free(links); + nl_socket_free(conn); + return 1; + } + + m->ifindex = rtnl_link_get_ifindex(link); + LOG_DBG("%s: ifindex: %d", m->iface, m->ifindex); + + /* TODO: expose this in a tag. Need to figure out which event is + * triggered by this */ + uint8_t carrier = rtnl_link_get_carrier(link); + LOG_DBG("%s: carrier: %hhu", m->iface, carrier); + + /* down/up etc */ + m->state = rtnl_link_get_operstate(link); + LOG_DBG("%s: operstate = %hhu", m->iface, m->state); + + struct nl_addr *mac = rtnl_link_get_addr(link); + assert(nl_addr_get_len(mac) == 6); + memcpy(m->mac, nl_addr_get_binary_addr(mac), sizeof(m->mac)); + + char mac_str[2 * 6 + 5 + 1]; + nl_addr2str(mac, mac_str, sizeof(mac_str)); + LOG_DBG("%s: MAC: %s", m->iface, mac_str); + + struct nl_cache *addrs; + r = rtnl_addr_alloc_cache(conn, &addrs); + if (r < 0) { + LOG_ERR("failed to address cache: %s", nl_geterror(r)); + assert(false); + } + + /* Add all IPv4 and IPv6 addresses (that belongs to us) to our list */ + for (struct rtnl_addr *addr = (struct rtnl_addr *)nl_cache_get_first(addrs); + addr != NULL; + addr = (struct rtnl_addr *)nl_cache_get_next((struct nl_object *)addr)) + { + if (rtnl_addr_get_ifindex(addr) != m->ifindex) + continue; + + int family = rtnl_addr_get_family(addr); + if (family != AF_INET && family != AF_INET6) + continue; + + struct nl_addr *local = rtnl_addr_get_local(addr); + struct af_addr a = {.family = family}; + memcpy(&a.u, nl_addr_get_binary_addr(local), nl_addr_get_len(local)); + tll_push_back(m->addrs, a); + + char s[INET6_ADDRSTRLEN]; + inet_ntop(family, &a.u, s, sizeof(s)); + LOG_DBG("%s: address: %s", m->iface, s); + + } + + rtnl_link_put(link); + nl_cache_free(addrs); + nl_cache_free(links); + + /* Configure a callback to handle netlink events */ + r = nl_socket_modify_cb(conn, NL_CB_VALID, NL_CB_CUSTOM, &nl_event, mod); + if (r < 0) + LOG_ERR("falied to set netlink callback: %s", nl_geterror(r)); + + /* Register for link and IPv4/IPv6 address changes */ + r = nl_socket_add_memberships( + conn, + RTNLGRP_LINK, + RTNLGRP_IPV4_IFADDR, + RTNLGRP_IPV6_IFADDR, + RTNLGRP_NONE); + if (r < 0) + LOG_ERR("failed to register for notifications: %s", nl_geterror(r)); + + /* Events have no sequence numbers, thus disable checks */ + nl_socket_disable_seq_check(conn); + + const int nl_fd = nl_socket_get_fd(conn); + while (true) { + struct pollfd fds[] = { + {.fd = ctx->abort_fd, .events = POLLIN}, + {.fd = nl_fd, .events = POLLIN} + }; + + r = poll(fds, 2, -1); + if (r == -1) { + LOG_ERRNO("poll() failed"); + break; + } + + if (fds[0].revents && POLLIN) + break; + + if (fds[1].revents & POLLHUP) { + LOG_ERR("disconnected from netlink"); + break; + } + + assert(fds[1].revents & POLLIN); + + r = nl_recvmsgs_default(conn); + if (r < 0) + LOG_ERR("failed to receive: %s", nl_geterror(r)); + } + + nl_close(conn); + nl_socket_free(conn); + return 0; +} + +struct module * +module_network(const char *iface, struct particle *label) +{ + struct private *priv = malloc(sizeof(*priv)); + priv->iface = strdup(iface); + priv->label = label; + + priv->ifindex = 0; + priv->state = IF_OPER_DOWN; + memset(&priv->addrs, 0, sizeof(priv->addrs)); + + struct module *mod = module_common_new(); + mod->private = priv; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + return mod; +} diff --git a/modules/network.h b/modules/network.h new file mode 100644 index 0000000..6ec57bd --- /dev/null +++ b/modules/network.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../module.h" +#include "../particle.h" + +struct module *module_network(const char *iface, struct particle *label);