I have a Linux (Ubuntu 20.04, in this test) system with multiple routing tables.
> sudo cat /etc/iproute2/rt_tables
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
1 mytable
The table mytable has a route:
> ip route show table 1
10.0.0.0/8 via 172.40.50.1 dev ens192
And the main table has a default route:
> ip route show
default via 172.40.60.1 dev ens192 proto static metric 100
...
but no other routes to the 10.0.0.0/8 network.
Using rtnetlink, I would like to query table mytable explicitly. I fill in the rtm_table field in my netlink message as follows:
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <stdlib.h>
#include <asm/types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
class RtNetlink
{
public:
RtNetlink()
{
m_sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
m_message = (struct nlmsghdr *)m_buffer;
}
~RtNetlink()
{
::close(m_sockfd);
}
bool send_message(struct nlmsghdr * msg, int msg_size)
{
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
sendto(m_sockfd, msg, msg_size, 0, (struct sockaddr *)&nladdr, sizeof(nladdr));
m_message_size = 0;
m_is_done = false;
m_message = (struct nlmsghdr *)m_buffer;
return true;
}
const struct nlmsghdr * get_next_message()
{
struct nlmsghdr * ret = NULL;
if (m_is_done)
{
return NULL;
}
if (NLMSG_OK(m_message, m_message_size))
{
if (m_message->nlmsg_type == NLMSG_DONE)
{
m_is_done = true;
return NULL;
}
}
else
{
m_message_size = recv(m_sockfd, m_buffer, sizeof(m_buffer), 0);
m_message = (struct nlmsghdr *)m_buffer;
if (! (m_message->nlmsg_flags & NLM_F_MULTI))
{
m_is_done = true;
}
else if (m_message->nlmsg_type == NLMSG_DONE)
{
m_is_done = true;
return NULL;
}
}
ret = m_message;
m_message = NLMSG_NEXT(m_message, m_message_size);
return ret;
}
private:
char m_buffer[16384];
struct nlmsghdr * m_message;
int m_message_size{0};
bool m_is_done{true};
int m_sockfd{-1};
};
bool get_gateway(const char* destination, int table)
{
RtNetlink nlcomm;
struct {
struct nlmsghdr header;
struct rtmsg message;
unsigned char buffer[256];
} request;
struct rtattr * attributes[RTA_MAX + 1];
in_addr in;
inet_aton(destination, &in);
unsigned char *dest_bytes = (unsigned char*)&in.s_addr;
int dest_len = sizeof(struct in_addr);
memset(&request, 0, sizeof(request));
request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
request.header.nlmsg_flags = NLM_F_REQUEST;
request.header.nlmsg_type = RTM_GETROUTE;
request.header.nlmsg_seq = lrand48();
request.message.rtm_family = AF_INET;
request.message.rtm_table = table;
struct rtattr * rta = (struct rtattr *)(((char *)&request.header) + NLMSG_ALIGN(request.header.nlmsg_len));
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(dest_len);
memcpy(RTA_DATA(rta), dest_bytes, dest_len);
request.header.nlmsg_len = NLMSG_ALIGN(request.header.nlmsg_len) + rta->rta_len;
nlcomm.send_message(&request.header, request.header.nlmsg_len);
const struct nlmsghdr * response;
while ((response = nlcomm.get_next_message()) != NULL)
{
struct rtmsg * rmsg = (struct rtmsg *)NLMSG_DATA(response);
memset(attributes, 0, sizeof(attributes));
int rlen;
int from_table=-1;
for (rta = (struct rtattr *)RTM_RTA(rmsg), rlen = RTM_PAYLOAD(response);
RTA_OK(rta, rlen);
rta = RTA_NEXT(rta, rlen))
{
if (rta->rta_type < RTA_MAX)
{
attributes[rta->rta_type] = rta;
if (rta->rta_type == RTA_TABLE)
{
from_table = *((int *)RTA_DATA(attributes[RTA_TABLE]));
printf("Found table %d\n", from_table);
}
if (rta->rta_type == RTA_GATEWAY)
{
printf("Probed table %d, found answer in table %d\n", table, from_table);
}
}
}
}
return true;
}
int main(int argc, char **argv)
{
get_gateway(argv[1], atoi(argv[2]));
}
The probe works, but RTA_TABLE is always RT_TABLE_MAIN:
> ./a.out 10.2.3.4 1
Found table 254
Probed table 1, found answer in table 254
> ./a.out 10.2.3.4 2
Found table 254
Probed table 2, found answer in table 254
> ./a.out 10.2.3.4 254
Found table 254
Probed table 254, found answer in table 254
> ./a.out 10.2.3.4 255
Found table 254
Probed table 255, found answer in table 254
So, a couple of questions:
- Is my understanding here incorrect? is it not possible to probe arbitrary tables?
- If probing mytable should work, what must I do other than setting rtm_table in the request to the ID of mytable?
I did verify that there is only one valid response from the netlink query, so looping looking for additional answers yields nothing.