Skip to content

Instantly share code, notes, and snippets.

@arachsys
Last active October 31, 2025 11:56
Show Gist options
  • Select an option

  • Save arachsys/988d797b2ea364459fbf8f67c5e179e0 to your computer and use it in GitHub Desktop.

Select an option

Save arachsys/988d797b2ea364459fbf8f67c5e179e0 to your computer and use it in GitHub Desktop.
Monitor neighbour status
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/socket.h>
static union {
struct in_addr in;
struct in6_addr in6;
} address;
static int family, interface, netlink;
static const char *state(const struct ndmsg *data) {
switch(data->ndm_state) {
case NUD_INCOMPLETE:
return "INCOMPLETE";
case NUD_REACHABLE:
return "REACHABLE";
case NUD_STALE:
return "STALE";
case NUD_DELAY:
return "DELAY";
case NUD_PROBE:
return "PROBE";
case NUD_FAILED:
return "FAILED";
case NUD_NOARP:
return "NOARP";
case NUD_PERMANENT:
return "PERMANENT";
case NUD_NONE:
return "NONE";
default:
return "UNKNOWN";
}
}
static void parse(const struct nlmsghdr *head) {
const struct ndmsg *data = NLMSG_DATA(head);
const struct rtattr *attr = RTM_RTA(data);
size_t length = head->nlmsg_len - NLMSG_LENGTH(sizeof *data);
const unsigned char *dst = NULL, *mac = NULL;
size_t dstlen = 0, maclen = 0;
if (interface && data->ndm_ifindex != interface)
return;
while (RTA_OK(attr, length)) {
switch (attr->rta_type) {
case NDA_DST:
dst = RTA_DATA(attr);
dstlen = RTA_PAYLOAD(attr);
break;
case NDA_LLADDR:
mac = RTA_DATA(attr);
maclen = RTA_PAYLOAD(attr);
break;
}
attr = RTA_NEXT(attr, length);
}
if (data->ndm_family != family || dst == NULL)
return;
if (family == AF_INET && dstlen != sizeof(struct in_addr))
return;
if (family == AF_INET6 && dstlen != sizeof(struct in6_addr))
return;
if (memcmp(&address, dst, dstlen))
return;
printf("%s", state(data));
for (size_t i = 0; mac && i < maclen; i++)
printf("%s%02x", i ? ":" : " ", mac[i]);
putchar('\n');
}
static void trigger(void) {
const struct {
struct nlmsghdr head;
struct ndmsg data;
} request = {
.head.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
.head.nlmsg_type = RTM_GETNEIGH,
.head.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
.data.ndm_family = family,
};
if (sendto(netlink, &request, request.head.nlmsg_len, 0,
(struct sockaddr *) &(struct sockaddr_nl) {
.nl_family = AF_NETLINK
}, sizeof(struct sockaddr_nl)) < 0)
err(1, "sendto RTM_GETNEIGH");
}
int main(int argc, char **argv) {
char buffer[8192];
ssize_t length;
struct nlmsghdr *head;
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: %s ADDRESS [INTERFACE]\n", argv[0]);
return 64;
}
if (inet_pton(family = AF_INET, argv[1], &address) != 1)
if (inet_pton(family = AF_INET6, argv[1], &address) != 1)
errx(1, "Invalid neighbour address: %s", argv[1]);
if (argc > 2 && !(interface = if_nametoindex(argv[2])))
errx(1, "Invalid interface name: %s", argv[2]);
if ((netlink = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0)
err(1, "socket");
if (bind(netlink, (struct sockaddr *) &(struct sockaddr_nl) {
.nl_family = AF_NETLINK, .nl_groups = RTMGRP_NEIGH
}, sizeof(struct sockaddr_nl)) < 0)
err(1, "bind");
trigger();
while(1) {
length = recv(netlink, buffer, sizeof buffer, 0);
if (length < 0) {
if (errno != EAGAIN && errno != EINTR)
err(1, "recv RTMGRP_NEIGH");
continue;
}
head = (struct nlmsghdr *) buffer;
while (NLMSG_OK(head, length) && head->nlmsg_type != NLMSG_DONE) {
switch (head->nlmsg_type) {
case RTM_NEWNEIGH:
case RTM_DELNEIGH:
parse(head);
}
head = NLMSG_NEXT(head, length);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment