Program Listing for File net_info.cpp
↰ Return to documentation for file (src/net_info.cpp
)
#include "net_info.h"
#include <linux/ethtool.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <spdlog/spdlog.h>
#include <sys/ioctl.h>
#include <cstring>
#include <glaze/glaze.hpp>
#include <optional>
#include <string>
#include "expected.h"
#include "expected_helpers.h"
#include "fixed_name.hpp"
#include "spdlog/fmt/bundled/core.h"
#define BUFFER_SIZE 8192
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#endif
tl::expected<EtherAddr, std::string> net_info::get_mac_address(FixedName<IFNAMSIZ> iface,
const InAddr &ip) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
return tl::unexpected("Cannot open socket");
}
struct arpreq req;
memset(&req, 0, sizeof(req));
struct sockaddr_in *sin = (struct sockaddr_in *) &req.arp_pa;
sin->sin_family = AF_INET;
sin->sin_addr = ip;
memcpy(req.arp_dev, iface.buf.data(), IFNAMSIZ);
if (ioctl(sock, SIOCGARP, &req) < 0) {
close(sock);
return tl::unexpected("ioctl error");
}
EtherAddr addr;
memcpy((void *) &addr, (void *) req.arp_ha.sa_data, RTE_ETHER_ADDR_LEN);
close(sock);
return addr;
}
tl::expected<FixedName<IFNAMSIZ>, std::string> get_if_to_use(
std::optional<FixedName<IFNAMSIZ>> user_if,
std::optional<FixedName<IFNAMSIZ>> default_interface) {
if (user_if.has_value())
return user_if.value();
if (default_interface.has_value())
return default_interface.value();
return tl::unexpected(
"Cannot find default interface and user did not provide interface name");
}
tl::expected<net_info::RouteInfo, std::string> net_info::get_route_info(
std::optional<FixedName<IFNAMSIZ>> if_name) {
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1;
uint32_t msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
std::optional<FixedName<IFNAMSIZ>> default_interface;
std::map<FixedName<IFNAMSIZ>, RouteInfo> routes_map;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
return tl::unexpected("cannot open socket");
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *) msgbuf;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *) &tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
close(sock);
return tl::unexpected("send failed");
}
/* receive response */
do {
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
close(sock);
return tl::unexpected("recv failed");
}
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if ((NLMSG_OK(nlmsg, received_bytes) == 0) || (nlmsg->nlmsg_type == NLMSG_ERROR)) {
close(sock);
return tl::unexpected("error in received packet");
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
} while (
(nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != static_cast<uint32_t>(getpid())));
/* parse response */
for (; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) {
RouteInfo route_info{};
InAddr dst_addr{};
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
// spdlog::info("iter");
/* Loop through all attributes */
for (; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len)) {
InAddr addr_buf{};
switch (route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *) RTA_DATA(route_attribute),
route_info.if_name.buf.data());
route_info.if_name.len = std::char_traits<char>::length(
route_info.if_name.buf.data());
// spdlog::info(route_info.if_name.c_str());
break;
case RTA_GATEWAY:
memcpy(&addr_buf, RTA_DATA(route_attribute),
sizeof(InAddr));
route_info.gateway_addr = addr_buf;
// spdlog::info("Gateway: {}", addr_buf.str());
break;
case RTA_PREFSRC:
memcpy(&addr_buf, RTA_DATA(route_attribute),
sizeof(InAddr));
route_info.source_addr = addr_buf;
// spdlog::info("Source: {}", addr_buf.str());
break;
case RTA_DST:
memcpy(&dst_addr, RTA_DATA(route_attribute),
sizeof(InAddr));
// spdlog::info("Dest: {}", dst_addr.str());
default:
break;
}
}
routes_map[route_info.if_name].if_name = route_info.if_name;
if (route_info.source_addr.has_value())
routes_map[route_info.if_name].source_addr = route_info.source_addr;
if (route_info.gateway_addr.has_value())
routes_map[route_info.if_name].gateway_addr = route_info.gateway_addr;
if (dst_addr.s_addr == 0x00 && route_info.if_name.len > 0)
default_interface = route_info.if_name;
}
auto iface = UNWRAP_OR_RETURN(get_if_to_use(if_name, default_interface));
if (!routes_map.contains(iface))
return tl::unexpected(
fmt::format("Cannot find routes for interface {}", default_interface->c_str()));
close(sock);
return routes_map[iface];
}
tl::expected<ethtool_channels, std::string> net_info::get_channel_count(FixedName<IFNAMSIZ> iface) {
int if_idx = if_nametoindex(iface.c_str());
if (if_idx < 0) {
return tl::unexpected(fmt::format("Cannot get index for {}", iface));
}
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd == -1) {
return tl::unexpected("Cannot create ethtool socket");
}
struct ifreq ifr = {};
std::strncpy(ifr.ifr_name, iface.c_str(), IFNAMSIZ);
ifr.ifr_ifindex = if_idx;
struct ethtool_channels ethchannels = {};
ethchannels.cmd = ETHTOOL_GCHANNELS;
ifr.ifr_data = reinterpret_cast<char *>(ðchannels);
if (ioctl(fd, SIOCETHTOOL, &ifr) != 0) {
close(fd);
return tl::unexpected(fmt::format("Error receiving from ethtool socket: {}", strerror(errno)));
}
close(fd);
return ethchannels;
}