libnetlink
Recently I wanted to refactor several functions, each function opened the special linux file /proc/net/route
, searched for the passed interface-name and returned the queried value in human-readable format.
the file looks like:
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
enp4s0 00000000 0100A8C0 0003 0 0 1024 00000000 0 0 0
The old functions read the file line-by-line “parsed” each line with sscanf
, checked if the line is for the interface and returned the corresponding column.
The values of interest are IFace
(NIC interface name) Gateway
, and Metric
. In most of the code-path all functions were called one after the other with the same interface-name.
The plan was to use the libnetlink
to query the Gateway
and Metric
. I found the blog post getting linux routing table using netlink but I couldn’t get it to work, the metric was never returned.
The next step then was to try the libnetlink
from iproute2
. The command ip route list
showed all the needed information. But trying to find some example code was not that easy. The github repository libnetlink example had some examples but nothing with routes.
but for the rescue the readme has a “More info:” section
If you need information about what other parameters/commands are available, you can always browse the iproute2 repo: https://github.com/shemminger/iproute2
searching the internet for examples or documentation for some hours and i still ended up in reading the original c-source code :(
so here we go with some example
Example in C++
Each entry in the linux routing table is represented as a struct RoutingEntry
and the whole routing table is a std::vector<RoutingEntry>
struct RoutingEntry
{
using IpAddress = boost::asio::ip::address_v4;
struct
{
IpAddress address;
unsigned char netmask{0};
} destination;
std::optional<IpAddress> gateway;
std::uint32_t interfaceIdx{0};
std::optional<std::uint32_t> metric;
};
using RoutingTable = std::vector<RoutingEntry>;
the next code is based on the original iproute2
sourcecode starting in the function iproute_list_flush_or_save:
extern "C"
{
#include <libnetlink.h>
#include <resolv.h>
}
// no filtering needed
int iproute_dump_filter(nlmsghdr*, int)
{
return 0;
}
__u32 rtm_get_table(rtmsg* r, rtattr** tb)
{
__u32 table = r->rtm_table;
if (tb[RTA_TABLE])
table = rta_getattr_u32(tb[RTA_TABLE]);
return table;
}
// callback function used by libnetlink for each routing table entry
int append_route(nlmsghdr* n, void* arg)
{
if (!arg)
{
return -1;
}
auto& arguments = *static_cast<RoutingTable*>(arg);
auto* r = reinterpret_cast<rtmsg*>(NLMSG_DATA(n));
struct rtattr* tb[RTA_MAX + 1];
int len = static_cast<int>(n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
if (len < 0)
{
fmt::print("Wrong message length\n");
return -1;
}
parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
auto table = rtm_get_table(r, tb);
// currently only IPv4 is supported, but IPv6 support can easily be added
if (r->rtm_family != AF_INET && table != RT_TABLE_MAIN)
{
return 0;
}
RoutingEntry entry;
if (tb[RTA_DST] && r->rtm_dst_len)
{
boost::asio::ip::address_v4::bytes_type bytes;
memcpy(bytes.data(), RTA_DATA(tb[RTA_DST]), RTA_PAYLOAD(tb[RTA_DST]));
entry.destination.address = boost::asio::ip::make_address_v4(bytes);
entry.destination.netmask = r->rtm_dst_len;
}
if (tb[RTA_GATEWAY])
{
boost::asio::ip::address_v4::bytes_type bytes;
memcpy(bytes.data(), RTA_DATA(tb[RTA_GATEWAY]), RTA_PAYLOAD(tb[RTA_GATEWAY]));
entry.gateway = boost::asio::ip::make_address_v4(bytes);
}
if (tb[RTA_OIF])
{
entry.interfaceIdx = rta_getattr_u32(tb[RTA_OIF]);
}
if (tb[RTA_PRIORITY])
{
entry.metric = rta_getattr_u32(tb[RTA_PRIORITY]);
}
arguments.push_back(std::move(entry));
return 0;
}
std::vector<RoutingEntry> load_routingtable()
{
rtnl_handle rth{};
rth.fd = -1;
if (rtnl_open(&rth, 0) < 0)
exit(1);
rtnl_set_strict_dump(&rth);
if (rtnl_routedump_req(&rth, AF_INET, &iproute_dump_filter) < 0)
{
rtnl_close(&rth);
return {};
}
std::vector<RoutingEntry> routingTable;
if (rtnl_dump_filter(&rth, append_route, &routingTable) < 0)
{
rtnl_close(&rth);
return {};
}
rtnl_close(&rth);
routingTable.shrink_to_fit();
return routingTable;
}
example usage:
int main()
{
const auto table = load_routingtable();
for (const auto& entry : table)
{
if (entry.destination.address.is_unspecified() && entry.destination.netmask == 0)
{
fmt::print("default");
}
else
{
fmt::print("{}/{}", entry.destination.address.to_string(), entry.destination.netmask);
}
if (entry.gateway)
{
fmt::print(" via {}", entry.gateway->to_string());
}
fmt::print(" deviceIndex {}", entry.interfaceIdx);
if (entry.metric)
{
fmt::print(" metric {}", *entry.metric);
}
fmt::print("\n");
}
}
and compile it with:
g++ -std=c++20 libnetlink_example.cpp -o route -lnetlink -lfmt -lmnl
when the program is executed on a system with above /proc/net/route
file, the output is:
default via 192.168.0.1 deviceIndex 2 metric 1024
source-code: libnetlink_example.cpp