git: c26d6bf9da01 - main - arp: fix adding proxy entries for P2P interfaces

From: Andrey V. Elsukov <ae_at_FreeBSD.org>
Date: Fri, 17 Oct 2025 08:23:02 UTC
The branch main has been updated by ae:

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

commit c26d6bf9da013e839d9a696746ea1741569e619e
Author:     Andrey V. Elsukov <ae@FreeBSD.org>
AuthorDate: 2025-10-17 07:53:35 +0000
Commit:     Andrey V. Elsukov <ae@FreeBSD.org>
CommitDate: 2025-10-17 08:22:36 +0000

    arp: fix adding proxy entries for P2P interfaces
    
    The old rtsock implementation used in-kernel workaround to do so.
    When route to specified destination address used P2P interface, the
    kernel did the search with ifa_ifwithnet() for most suitable network
    and then add proxy entry to this interface.
    
    Use similar approach with netlink implementation. We already have
    get_ether_addr() function that does almost the same thing as
    ifa_ifwithnet(). Use it when we find that destination route uses
    P2P interface and then try to guess suitable interface. This should
    fix the use of netlink-based arp(8) in mpd5.
    
    Rename get_ether_addr() to get_ifinfo(), since now it is used to find
    only ifindex in case when hwaddr is specified by user.
    Also make set_nl() and delete_nl() prototype similar to rtsock.
    And allow '-i' to be used with '-S', since we already allow the same
    for '-s'.
    
    PR:             290221
    Reported by:    eugen
    Reviewed by:    eugen
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D53113
---
 usr.sbin/arp/arp.c         | 32 ++++++++++++++++++++------------
 usr.sbin/arp/arp.h         |  9 ++++-----
 usr.sbin/arp/arp_netlink.c | 20 ++++++++++++++++----
 3 files changed, 40 insertions(+), 21 deletions(-)

diff --git a/usr.sbin/arp/arp.c b/usr.sbin/arp/arp.c
index ee4236b5299b..055ef2ffe225 100644
--- a/usr.sbin/arp/arp.c
+++ b/usr.sbin/arp/arp.c
@@ -81,7 +81,6 @@ static int get(char *host);
 static int file(char *name);
 static struct rt_msghdr *rtmsg(int cmd,
     struct sockaddr_in *dst, struct sockaddr_dl *sdl);
-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);
 
@@ -143,7 +142,8 @@ main(int argc, char *argv[])
 	if (!func)
 		func = F_GET;
 	if (opts.rifname) {
-		if (func != F_GET && func != F_SET && !(func == F_DELETE && opts.aflag))
+		if (func != F_GET && func != F_SET && func != F_REPLACE &&
+		    !(func == F_DELETE && opts.aflag))
 			xo_errx(1, "-i not applicable to this operation");
 		if ((opts.rifindex = if_nametoindex(opts.rifname)) == 0) {
 			if (errno == ENXIO)
@@ -273,7 +273,6 @@ getaddr(char *host)
 	return (&reply);
 }
 
-int valid_type(int type);
 /*
  * Returns true if the type is a valid one for ARP.
  */
@@ -357,11 +356,14 @@ set(int argc, char **argv)
 	}
 	ea = (struct ether_addr *)LLADDR(&sdl_m);
 	if ((opts.flags & RTF_ANNOUNCE) && !strcmp(eaddr, "auto")) {
-		if (!get_ether_addr(dst->sin_addr.s_addr, ea)) {
+		uint32_t ifindex;
+		if (!get_ifinfo(dst->sin_addr.s_addr, ea, &ifindex)) {
 			xo_warnx("no interface found for %s",
-			       inet_ntoa(dst->sin_addr));
+			    inet_ntoa(dst->sin_addr));
 			return (1);
 		}
+		if (opts.rifindex == 0)
+			opts.rifindex = ifindex;
 		sdl_m.sdl_alen = ETHER_ADDR_LEN;
 	} else {
 		struct ether_addr *ea1 = ether_aton(eaddr);
@@ -375,7 +377,7 @@ set(int argc, char **argv)
 		}
 	}
 #ifndef WITHOUT_NETLINK
-	return (set_nl(opts.rifindex, dst, &sdl_m, host));
+	return (set_nl(dst, &sdl_m, host));
 #else
 	return (set_rtsock(dst, &sdl_m, host));
 #endif
@@ -522,7 +524,7 @@ delete(char *host)
 #ifdef WITHOUT_NETLINK
 	return (delete_rtsock(host));
 #else
-	return (delete_nl(0, host));
+	return (delete_nl(host));
 #endif
 }
 
@@ -819,11 +821,11 @@ doit:
 }
 
 /*
- * get_ether_addr - get the hardware address of an interface on the
- * same subnet as ipaddr.
+ * get_ifinfo - get the hardware address and if_index of an interface
+ * on the same subnet as ipaddr.
  */
-static int
-get_ether_addr(in_addr_t ipaddr, struct ether_addr *hwaddr)
+int
+get_ifinfo(in_addr_t ipaddr, struct ether_addr *hwaddr, uint32_t *pifindex)
 {
 	struct ifaddrs *ifa, *ifd, *ifas = NULL;
 	in_addr_t ina, mask;
@@ -862,7 +864,13 @@ get_ether_addr(in_addr_t ipaddr, struct ether_addr *hwaddr)
 	}
 	if (ifa == NULL)
 		goto done;
-
+	if (pifindex != NULL)
+		*pifindex = if_nametoindex(ifa->ifa_name);
+	if (hwaddr == NULL) {
+		/* ether addr is not required */
+		retval = ETHER_ADDR_LEN;
+		goto done;
+	}
 	/*
 	 * Now scan through again looking for a link-level address
 	 * for this interface.
diff --git a/usr.sbin/arp/arp.h b/usr.sbin/arp/arp.h
index 487863be43e7..512a238df425 100644
--- a/usr.sbin/arp/arp.h
+++ b/usr.sbin/arp/arp.h
@@ -2,8 +2,8 @@
 #define _USR_SBIN_ARP_ARP_H_
 
 int valid_type(int type);
+int get_ifinfo(in_addr_t ipaddr, struct ether_addr *hwaddr, uint32_t *pifindex);
 struct sockaddr_in *getaddr(char *host);
-int print_entries_nl(uint32_t ifindex, struct in_addr addr);
 
 struct arp_opts {
 	bool aflag;
@@ -11,13 +11,12 @@ struct arp_opts {
 	time_t expire_time;
 	int flags;
 	char *rifname;
-	unsigned int rifindex;
+	uint32_t rifindex;
 };
 extern struct arp_opts opts;
 
 int print_entries_nl(uint32_t ifindex, struct in_addr addr);
-int delete_nl(uint32_t ifindex, char *host);
-int set_nl(uint32_t ifindex, struct sockaddr_in *dst, struct sockaddr_dl *sdl,
-    char *host);
+int delete_nl(char *host);
+int set_nl(struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host);
 
 #endif
diff --git a/usr.sbin/arp/arp_netlink.c b/usr.sbin/arp/arp_netlink.c
index db1ef775dea2..34f21cf96f4f 100644
--- a/usr.sbin/arp/arp_netlink.c
+++ b/usr.sbin/arp/arp_netlink.c
@@ -79,13 +79,15 @@ get_link_info(struct snl_state *ss, uint32_t ifindex,
 
 
 static bool
-has_l2(struct snl_state *ss, uint32_t ifindex)
+has_l2(struct snl_state *ss, uint32_t ifindex, uint32_t *pflags)
 {
 	struct snl_parsed_link_simple link = {};
 
+	*pflags = 0;
 	if (!get_link_info(ss, ifindex, &link))
 		return (false);
 
+	*pflags = link.ifi_flags;
 	return (valid_type(link.ifi_type) != 0);
 }
 
@@ -104,6 +106,7 @@ static int
 guess_ifindex(struct snl_state *ss, uint32_t fibnum, struct in_addr addr)
 {
 	struct snl_writer nw;
+	uint32_t ifindex, ifflags;
 
 	snl_init_writer(ss, &nw);
 
@@ -133,9 +136,16 @@ guess_ifindex(struct snl_state *ss, uint32_t fibnum, struct in_addr addr)
 		return (0);
 
 	/* Check if the interface is of supported type */
-	if (has_l2(ss, r.rta_oif))
+	if (has_l2(ss, r.rta_oif, &ifflags))
 		return (r.rta_oif);
 
+	/* Check if we are doing proxy arp for P2P interface */
+	if (ifflags & IFF_POINTOPOINT) {
+		/* Guess interface by dst prefix */
+		if (get_ifinfo(addr.s_addr, NULL, &ifindex))
+			return (ifindex);
+	}
+
 	/* Check the case when we matched the loopback route for P2P */
 	snl_init_writer(ss, &nw);
 	hdr = snl_create_msg_request(&nw, RTM_GETNEXTHOP);
@@ -326,11 +336,12 @@ print_entries_nl(uint32_t ifindex, struct in_addr addr)
 }
 
 int
-delete_nl(uint32_t ifindex, char *host)
+delete_nl(char *host)
 {
 	struct snl_state ss = {};
 	struct snl_writer nw;
 	struct sockaddr_in *dst;
+	uint32_t ifindex = opts.rifindex;
 
 	dst = getaddr(host);
 	if (dst == NULL)
@@ -375,10 +386,11 @@ delete_nl(uint32_t ifindex, char *host)
 }
 
 int
-set_nl(uint32_t ifindex, struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host)
+set_nl(struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host)
 {
 	struct snl_state ss = {};
 	struct snl_writer nw;
+	uint32_t ifindex = opts.rifindex;
 
 	nl_init_socket(&ss);