5
\$\begingroup\$
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>


#define ENTRY(x) {x, #x}
#define STRING_M(x) #x

static int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) {
    /* loop over all rtattributes */
    while (RTA_OK(rta, len) && max--) {
        tb[rta->rta_type] = rta; /* store attribute ptr to the tb array */
        rta = RTA_NEXT(rta, len); /* special rtnetlink.h macro to get next netlink route attribute ptr */
    };
    return 0;
}

int main(int argc, char **argv) {
    int status;
    void *p; //just a ptr

    /* open socket */
    int sd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);

    /* contruct arp cache request */
    struct {
        struct nlmsghdr n;
        struct ndmsg ndm;
        char buf[1024];
    } req = {
            .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
            .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT, /* to get all table instead of a single entry */
            .n.nlmsg_type = RTM_GETNEIGH, /* to get arp cache */
            .ndm.ndm_family = AF_INET, /* IP protocol family. AF_INET/AF_INET6 */
    };

    /* send request */
    status = send(sd, &req, req.n.nlmsg_len, 0);

    /* this is buffer to store an answer */
    char buf[16384] = {0};

    /* get an answer */
    status = recv(sd, buf, sizeof(buf), 0);
    int buf_size = status; /* recv will return answer size */
    p = (void *) buf; /* set p to start of an answer */

    while (buf_size > 0) { /* loop while buffer size is more than 0 */
        struct nlmsghdr *answer = (struct nlmsghdr *) p; /* netlink header structure */

        int len = answer->nlmsg_len; /* netlink message length including header */
        struct ndmsg *msg = NLMSG_DATA(answer); /* macro to get a ptr right after header */
        /* Given the payload length, len, this macro returns the aligned
         * length to store in the nlmsg_len field of the nlmsghdr.
         * */
        int msg_len = NLMSG_LENGTH(sizeof(*msg));
        len -= msg_len; /* count message length left */
        p += msg_len; /* move ptr forward */

        /* rtnetlink route netlink attributes buffer */
        struct rtattr *tb[NDA_MAX + 1] = {0};

        /* this is very first rtnetlink attribute */
        struct rtattr *rta = (struct rtattr *) p;
        memset(tb, 0, sizeof(tb)); /* clear attribute buffer */
        parse_rtattr(tb, NDA_MAX, rta, len); /* fill tb attribute buffer */
        if (tb[NDA_DST]) { /* this is destination address */
            char ip[INET6_ADDRSTRLEN] = {0};
            inet_ntop(msg->ndm_family, RTA_DATA(tb[NDA_DST]), ip, INET6_ADDRSTRLEN);
            fprintf(stderr, "%s ", ip);
        }
        if (tb[NDA_LLADDR]) { /* this is hardware mac address */
            const unsigned char *addr = RTA_DATA(tb[NDA_LLADDR]);
            fprintf(stderr, "lladdr: %02X:%02X:%02X:%02X:%02X:%02X\n",
                    addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
        } else {
            fprintf(stderr, "lladdr: \n");
        }

        p += len;
        buf_size -= answer->nlmsg_len;
    }

    return 0;
}

For two days, I was looking for a sample code on how to get the raw ARP cache from the Linux kernel. The code should be without dependencies and as simple as possible. I haven't yet found anything useful so I've made my own ARP utility analog.

Maybe there is something to do better?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Small review.

Code should be ... simple as possible.

Unneeded casts

// p = (void *) buf;
p = buf;
...
// struct rtattr *rta = (struct rtattr *) p;
struct rtattr *rta = p;

Unused

Remove unused code.

// #define ENTRY(x) {x, #x}
// #define STRING_M(x) #x

Unneeded large initialization

char buf[16384] = {0}; zeros a large buffer, yet is not needed for recv(sd, buf, sizeof(buf), 0);

// char buf[16384] = {0};
char buf[16384];

Zero-ing of tb not needed

// memset(tb, 0, sizeof(tb));

Unneeded ()

// recv(sd, buf, sizeof(buf), 0);`
recv(sd, buf, sizeof buf, 0);`

Unused status

status from status = send(sd, &req, req.n.nlmsg_len, 0); is not used. Either omit or better: check status.

Unneeded zeroing - twice

Below, tb is zeo'd twice and then written again. Zero-ings not needed.

    // struct rtattr *tb[NDA_MAX + 1] = {0};
    struct rtattr *tb[NDA_MAX + 1];

    // struct rtattr *rta = (struct rtattr *) p;
    struct rtattr *rta = p;

    // memset(tb, 0, sizeof(tb));

    parse_rtattr(tb, NDA_MAX, rta, len); /* fill tb attribute buffer */

Avoid naked magic numbers

1024 in char buf[1024]; undocumented. Why 1024?

On the edge

inet_ntop(msg->ndm_family, RTA_DATA(tb[NDA_DST]), ip, INET6_ADDRSTRLEN); lacks return value check for errors.

Minor: Sign-ness change

Rather than use int, use the same sign-ness and type as answer->nlmsg_len.

int len = answer->nlmsg_len;
__u32 len = answer->nlmsg_len;
// or
unsigned long len = answer->nlmsg_len;

struct rtattr *RTA_NEXT(struct rtattr *rta, unsigned int rtabuflen); expects an unsigned, not an int.

// static int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) {
static int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, unsigned len) {
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.