Last active
October 31, 2025 11:56
-
-
Save arachsys/988d797b2ea364459fbf8f67c5e179e0 to your computer and use it in GitHub Desktop.
Monitor neighbour status
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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