2

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:

  1. Is my understanding here incorrect? is it not possible to probe arbitrary tables?
  2. 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.

bananaslug
  • 21
  • 2
  • It may help to add your includes and pick between C and C++. I doubt you'll be compiling your code as both at the same time. – Mast Feb 28 '23 at 06:40
  • 1
    Per request, I updated the original post to include the full source of the example. I'm compiling as C++, but that's not really germane to the question. – bananaslug Feb 28 '23 at 13:55
  • It appears that the rtm_table field is ignored for a RTM_GETROUTE request. It works for table modification, but not table query. Using RTA_MARK is the proper approach here. – bananaslug Mar 02 '23 at 18:36

0 Answers0