On Tue, Aug 19, 2025 at 11:09:57PM -0400, Jon Maloy wrote:
The solution to bug https://bugs.passt.top/show_bug.cgi?id=120 requires the ability to translate from an IP address to its corresponding MAC address in cases where those are present in the ARP/NDP table.
We add this feature here.
Signed-off-by: Jon Maloy
--- netlink.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ netlink.h | 2 ++ 2 files changed, 81 insertions(+) diff --git a/netlink.c b/netlink.c index 8f82e73..cf7debc 100644 --- a/netlink.c +++ b/netlink.c @@ -800,6 +800,85 @@ int nl_addr_get(int s, unsigned int ifi, sa_family_t af, return status; }
+/** + * nl_neigh_mac_get() - Get neighbor MAC address from the kernel neigh table + * @s: Netlink socket fd + * @addr: IPv4 or IPv6 address + * @ifi: Interface index + * @mac: Buffer for Ethernet MAC, left unchanged if not found/usable + * + * Return: <0 on netlink error; 0 otherwise (drains all replies for @seq). + */ +int nl_neigh_mac_get(int s, const union inany_addr *addr, + int ifi, unsigned char *mac) +{ + const void *ip = inany_v4(addr); + struct req_t { + struct nlmsghdr nlh; + struct ndmsg ndm; + struct rtattr rta; + char ip[RTA_ALIGN(sizeof(struct in6_addr))]; + } req; + struct nlmsghdr *nh; + char buf[NLBUFSIZ]; + ssize_t status; + uint32_t seq; + int msglen; + int iplen; + + memset(&req, 0, sizeof(req)); + req.ndm.ndm_ifindex = ifi; + req.rta.rta_type = NDA_DST; + + if (ip) { + req.ndm.ndm_family = AF_INET; + iplen = sizeof(struct in_addr); + } else { + req.ndm.ndm_family = AF_INET6; + ip = &addr; + iplen = sizeof(struct in6_addr); + } + + req.rta.rta_len = RTA_LENGTH(iplen); + memcpy(RTA_DATA(&req.rta), ip, iplen); + msglen = NLMSG_ALIGN(sizeof(req.nlh) + sizeof(req.ndm) + RTA_LENGTH(iplen)); + seq = nl_send(s, &req, RTM_GETNEIGH, 0, msglen); + + /* Drain all RTM_NEWNEIGH replies for this seq */ + nl_foreach_oftype(nh, status, s, buf, seq, RTM_NEWNEIGH) { + struct ndmsg *ndm = NLMSG_DATA(nh); + struct rtattr *rta = (struct rtattr *)(ndm + 1); + const uint8_t *lladdr = NULL; + size_t na = RTM_PAYLOAD(nh); + const void *dst = NULL; + size_t lladdr_len = 0; + size_t dstlen = 0; + + for (; RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) { + switch (rta->rta_type) { + case NDA_DST: + dst = RTA_DATA(rta); + dstlen = RTA_PAYLOAD(rta); + break; + case NDA_LLADDR: + lladdr = RTA_DATA(rta); + lladdr_len = RTA_PAYLOAD(rta); + break; + default: + break; + } + } + + if (dst && dstlen == (size_t)iplen && memcmp(dst, ip, iplen) == 0) { + /* Only copy Ethernet-style addresses; leave unchanged otherwise */ + if (lladdr && lladdr_len == ETH_ALEN)
You need to return an error code in the case that the MAC is missing or doesn't have the required form; otherwise you'll silently return garbage in @mac.
+ memcpy(mac, lladdr, ETH_ALEN); + } + } + + return status; +} + /** * nl_addr_get_ll() - Get first IPv6 link-local address for a given interface * @s: Netlink socket diff --git a/netlink.h b/netlink.h index b51e99c..026c64f 100644 --- a/netlink.h +++ b/netlink.h @@ -17,6 +17,8 @@ int nl_route_dup(int s_src, unsigned int ifi_src, int s_dst, unsigned int ifi_dst, sa_family_t af); int nl_addr_get(int s, unsigned int ifi, sa_family_t af, void *addr, int *prefix_len, void *addr_l); +int nl_neigh_mac_get(int s, const union inany_addr *addr, int ifi, + unsigned char *mac); int nl_addr_set(int s, unsigned int ifi, sa_family_t af, const void *addr, int prefix_len); int nl_addr_get_ll(int s, unsigned int ifi, struct in6_addr *addr);
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson