git: e6d405e2bad2 - releng/14.0 - arp(8): fix by-interface and by-host filtering when using netlink

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Sun, 01 Oct 2023 07:05:09 UTC
The branch releng/14.0 has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=e6d405e2bad22fd98f6296a793ad0c97776fe03c

commit e6d405e2bad22fd98f6296a793ad0c97776fe03c
Author:     R. Christian McDonald <rcm@rcm.sh>
AuthorDate: 2023-09-14 07:07:24 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2023-10-01 07:04:45 +0000

    arp(8): fix by-interface and by-host filtering when using netlink
    
    arp(8) has traditionally supported filtering by interface via -i and
    by hostname. However, this functionality was omitted from the initial
    netlink-ification of arp. This patch re-introduces this filtering
    functionality.
    
    This patch also improves by-interface filtering by storing and using the
    ifindex of the requested interface for filtering instead of comparing
    interface name strings
    
    Reviewed by:    melifaro
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Approved by:    re (gjb)
    
    (cherry picked from commit 79278872ad966e5f54805efbeb692c8cbc0306c8)
    (cherry picked from commit f21f0d2e16af702c53bc150c6c23d1bc99399bcd)
---
 usr.sbin/arp/arp.c         | 34 +++++++++++++++-------------------
 usr.sbin/arp/arp.h         |  2 ++
 usr.sbin/arp/arp_netlink.c |  8 ++++++++
 3 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/usr.sbin/arp/arp.c b/usr.sbin/arp/arp.c
index 02b2bb1ac4f8..9a19d792f788 100644
--- a/usr.sbin/arp/arp.c
+++ b/usr.sbin/arp/arp.c
@@ -98,8 +98,6 @@ static int get_ether_addr(in_addr_t ipaddr, struct ether_addr *hwaddr);
 static int set_rtsock(struct sockaddr_in *dst, struct sockaddr_dl *sdl_m,
     char *host);
 
-static char *rifname;
-
 struct if_nameindex *ifnameindex;
 
 struct arp_opts opts = {};
@@ -146,7 +144,7 @@ main(int argc, char *argv[])
 			SETFUNC(F_FILESET);
 			break;
 		case 'i':
-			rifname = optarg;
+			opts.rifname = optarg;
 			break;
 		case '?':
 		default:
@@ -157,15 +155,15 @@ main(int argc, char *argv[])
 
 	if (!func)
 		func = F_GET;
-	if (rifname) {
+	if (opts.rifname) {
 		if (func != F_GET && !(func == F_DELETE && opts.aflag))
 			xo_errx(1, "-i not applicable to this operation");
-		if (if_nametoindex(rifname) == 0) {
+		if ((opts.rifindex = if_nametoindex(opts.rifname)) == 0) {
 			if (errno == ENXIO)
 				xo_errx(1, "interface %s does not exist",
-				    rifname);
+				    opts.rifname);
 			else
-				xo_err(1, "if_nametoindex(%s)", rifname);
+				xo_err(1, "if_nametoindex(%s)", opts.rifname);
 		}
 	}
 	switch (func) {
@@ -179,7 +177,7 @@ main(int argc, char *argv[])
 			xo_open_list("arp-cache");
 
 			struct in_addr all_addrs = {};
-			print_entries(0, all_addrs);
+			print_entries(opts.rifindex, all_addrs);
 
 			xo_close_list("arp-cache");
 			xo_close_container("arp");
@@ -448,13 +446,13 @@ get(char *host)
 	xo_open_container("arp");
 	xo_open_list("arp-cache");
 
-	found = print_entries(0, addr->sin_addr);
+	found = print_entries(opts.rifindex, addr->sin_addr);
 
 	if (found == 0) {
 		xo_emit("{d:hostname/%s} ({d:ip-address/%s}) -- no entry",
 		    host, inet_ntoa(addr->sin_addr));
-		if (rifname)
-			xo_emit(" on {d:interface/%s}", rifname);
+		if (opts.rifname)
+			xo_emit(" on {d:interface/%s}", opts.rifname);
 		xo_emit("\n");
 	}
 
@@ -552,7 +550,6 @@ search(u_long addr, action_fn *action)
 	struct rt_msghdr *rtm;
 	struct sockaddr_in *sin2;
 	struct sockaddr_dl *sdl;
-	char ifname[IF_NAMESIZE];
 	int st, found_entry = 0;
 
 	mib[0] = CTL_NET;
@@ -586,14 +583,13 @@ search(u_long addr, action_fn *action)
 		rtm = (struct rt_msghdr *)next;
 		sin2 = (struct sockaddr_in *)(rtm + 1);
 		sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
-		if (rifname && if_indextoname(sdl->sdl_index, ifname) &&
-		    strcmp(ifname, rifname))
+		if (opts.rifindex &&
+		    (opts.rifindex != sdl->sdl_index))
 			continue;
-		if (addr) {
-			if (addr != sin2->sin_addr.s_addr)
-				continue;
-			found_entry = 1;
-		}
+		if (addr &&
+		    (addr != sin2->sin_addr.s_addr))
+			continue;
+		found_entry = 1;
 		(*action)(sdl, sin2, rtm);
 	}
 	free(buf);
diff --git a/usr.sbin/arp/arp.h b/usr.sbin/arp/arp.h
index a7de3a1a3024..487863be43e7 100644
--- a/usr.sbin/arp/arp.h
+++ b/usr.sbin/arp/arp.h
@@ -10,6 +10,8 @@ struct arp_opts {
 	bool nflag;
 	time_t expire_time;
 	int flags;
+	char *rifname;
+	unsigned int rifindex;
 };
 extern struct arp_opts opts;
 
diff --git a/usr.sbin/arp/arp_netlink.c b/usr.sbin/arp/arp_netlink.c
index 4e5c8f3d9940..40b5367f330d 100644
--- a/usr.sbin/arp/arp_netlink.c
+++ b/usr.sbin/arp/arp_netlink.c
@@ -281,6 +281,7 @@ print_entries_nl(uint32_t ifindex, struct in_addr addr)
 	struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg);
 	if (ndmsg != NULL) {
 		ndmsg->ndm_family = AF_INET;
+		/* let kernel filter results by interface if provided */
 		ndmsg->ndm_ifindex = ifindex;
 	}
 
@@ -296,6 +297,7 @@ print_entries_nl(uint32_t ifindex, struct in_addr addr)
 
 	while ((hdr = snl_read_reply_multi(&ss_req, nlmsg_seq, &e)) != NULL) {
 		struct snl_parsed_neigh neigh = {};
+		struct sockaddr_in *neighaddr;
 
 		if (!snl_parse_nlmsg(&ss_req, hdr, &snl_rtm_neigh_parser, &neigh))
 			continue;
@@ -307,6 +309,12 @@ print_entries_nl(uint32_t ifindex, struct in_addr addr)
 				continue;
 		}
 
+		/* filter results based on host if provided */
+		neighaddr = (struct sockaddr_in *)neigh.nda_dst;
+		if (addr.s_addr &&
+		    (addr.s_addr != neighaddr->sin_addr.s_addr))
+			continue;
+
 		print_entry(&neigh, &link);
 		count++;
 		snl_clear_lb(&ss_req);