git: 6c5786fd37cb - main - linux(4): Migrate to IfAPI

From: Dmitry Chagin <dchagin_at_FreeBSD.org>
Date: Sat, 04 Mar 2023 09:12:41 UTC
The branch main has been updated by dchagin:

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

commit 6c5786fd37cb588f577c7fa63c56fc2f59c38786
Author:     Dmitry Chagin <dchagin@FreeBSD.org>
AuthorDate: 2023-03-04 09:11:38 +0000
Commit:     Dmitry Chagin <dchagin@FreeBSD.org>
CommitDate: 2023-03-04 09:11:38 +0000

    linux(4): Migrate to IfAPI
    
    Migrate linux and linprocfs to use the IfAPI interfaces instead of
    direct ifnet accesses.
    The code initially writed by jhibbits@, and adapted by me to 3ab3c9c2.
    
    Reviewed by:            jhibbits
    Differential Revision:  https://reviews.freebsd.org/D38735
---
 sys/compat/linprocfs/linprocfs.c |  72 ++++++++++--------
 sys/compat/linux/linux.c         | 160 +++++++++++++++++++++++++--------------
 sys/compat/linux/linux.h         |   4 +-
 sys/compat/linux/linux_ioctl.c   | 124 +++++++++++++++++-------------
 4 files changed, 217 insertions(+), 143 deletions(-)

diff --git a/sys/compat/linprocfs/linprocfs.c b/sys/compat/linprocfs/linprocfs.c
index d57c546eb637..a150019ae5ef 100644
--- a/sys/compat/linprocfs/linprocfs.c
+++ b/sys/compat/linprocfs/linprocfs.c
@@ -1478,12 +1478,49 @@ linprocfs_doprocmem(PFS_FILL_ARGS)
 /*
  * Filler function for proc/net/dev
  */
+static int
+linprocfs_donetdev_cb(if_t ifp, void *arg)
+{
+	char ifname[LINUX_IFNAMSIZ];
+	struct sbuf *sb = arg;
+
+	if (ifname_bsd_to_linux_ifp(ifp, ifname, sizeof(ifname)) <= 0)
+		return (ENODEV);
+
+	sbuf_printf(sb, "%6.6s: ", ifname);
+	sbuf_printf(sb, "%7ju %7ju %4ju %4ju %4lu %5lu %10lu %9ju ",
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_IBYTES),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_IPACKETS),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_IERRORS),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_IQDROPS),
+						/* rx_missed_errors */
+	    0UL,				/* rx_fifo_errors */
+	    0UL,				/* rx_length_errors +
+						 * rx_over_errors +
+						 * rx_crc_errors +
+						 * rx_frame_errors */
+	    0UL,				/* rx_compressed */
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_IMCASTS));
+						/* XXX-BZ rx only? */
+	sbuf_printf(sb, "%8ju %7ju %4ju %4ju %4lu %5ju %7lu %10lu\n",
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_OBYTES),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_OPACKETS),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_OERRORS),
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_OQDROPS),
+	    0UL,				/* tx_fifo_errors */
+	    (uintmax_t)if_getcounter(ifp, IFCOUNTER_COLLISIONS),
+	    0UL,				/* tx_carrier_errors +
+						 * tx_aborted_errors +
+						 * tx_window_errors +
+						 * tx_heartbeat_errors*/
+	    0UL);				/* tx_compressed */
+	return (0);
+}
+
 static int
 linprocfs_donetdev(PFS_FILL_ARGS)
 {
 	struct epoch_tracker et;
-	char ifname[16]; /* XXX LINUX_IFNAMSIZ */
-	struct ifnet *ifp;
 
 	sbuf_printf(sb, "%6s|%58s|%s\n"
 	    "%6s|%58s|%58s\n",
@@ -1494,36 +1531,7 @@ linprocfs_donetdev(PFS_FILL_ARGS)
 
 	CURVNET_SET(TD_TO_VNET(curthread));
 	NET_EPOCH_ENTER(et);
-	CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
-		ifname_bsd_to_linux_ifp(ifp, ifname, sizeof(ifname));
-		sbuf_printf(sb, "%6.6s: ", ifname);
-		sbuf_printf(sb, "%7ju %7ju %4ju %4ju %4lu %5lu %10lu %9ju ",
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_IBYTES),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_IPACKETS),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_IERRORS),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_IQDROPS),
-							/* rx_missed_errors */
-		    0UL,				/* rx_fifo_errors */
-		    0UL,				/* rx_length_errors +
-							 * rx_over_errors +
-							 * rx_crc_errors +
-							 * rx_frame_errors */
-		    0UL,				/* rx_compressed */
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_IMCASTS));
-							/* XXX-BZ rx only? */
-		sbuf_printf(sb, "%8ju %7ju %4ju %4ju %4lu %5ju %7lu %10lu\n",
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_OBYTES),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_OPACKETS),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_OERRORS),
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_OQDROPS),
-		    0UL,				/* tx_fifo_errors */
-		    (uintmax_t )ifp->if_get_counter(ifp, IFCOUNTER_COLLISIONS),
-		    0UL,				/* tx_carrier_errors +
-							 * tx_aborted_errors +
-							 * tx_window_errors +
-							 * tx_heartbeat_errors*/
-		    0UL);				/* tx_compressed */
-	}
+	if_foreach(linprocfs_donetdev_cb, sb);
 	NET_EPOCH_EXIT(et);
 	CURVNET_RESTORE();
 
diff --git a/sys/compat/linux/linux.c b/sys/compat/linux/linux.c
index 2570a5fcc89c..a7844f4bbbcf 100644
--- a/sys/compat/linux/linux.c
+++ b/sys/compat/linux/linux.c
@@ -287,13 +287,37 @@ ifname_bsd_to_linux_idx(u_int idx, char *lxname, size_t len)
 
 /*
  * Translate a FreeBSD interface name to a Linux interface name,
- * and return the number of bytes copied to lxname.
+ * and return the number of bytes copied to lxname, 0 if interface
+ * not found, -1 on error.
  */
+struct ifname_bsd_to_linux_ifp_cb_s {
+	struct ifnet	*ifp;
+	int		ethno;
+	char		*lxname;
+	size_t		len;
+};
+
+static int
+ifname_bsd_to_linux_ifp_cb(if_t ifp, void *arg)
+{
+	struct ifname_bsd_to_linux_ifp_cb_s *cbs = arg;
+
+	if (ifp == cbs->ifp)
+		return (snprintf(cbs->lxname, cbs->len, "eth%d", cbs->ethno));
+	if (IFP_IS_ETH(ifp))
+		cbs->ethno++;
+	return (0);
+}
+
 int
 ifname_bsd_to_linux_ifp(struct ifnet *ifp, char *lxname, size_t len)
 {
-	struct ifnet *ifscan;
-	int unit;
+	struct ifname_bsd_to_linux_ifp_cb_s arg = {
+		.ifp = ifp,
+		.ethno = 0,
+		.lxname = lxname,
+		.len = len,
+	};
 
 	NET_EPOCH_ASSERT();
 
@@ -306,17 +330,10 @@ ifname_bsd_to_linux_ifp(struct ifnet *ifp, char *lxname, size_t len)
 
 	/* Short-circuit non ethernet interfaces. */
 	if (!IFP_IS_ETH(ifp) || linux_use_real_ifname(ifp))
-		return (strlcpy(lxname, ifp->if_xname, len));
-
-	/* Determine the (relative) unit number for ethernet interfaces. */
-	unit = 0;
-	CK_STAILQ_FOREACH(ifscan, &V_ifnet, if_link) {
-		if (ifscan == ifp)
-			return (snprintf(lxname, len, "eth%d", unit));
-		if (IFP_IS_ETH(ifscan))
-			unit++;
-	}
-	return (0);
+		return (strlcpy(lxname, if_name(ifp), len));
+
+ 	/* Determine the (relative) unit number for ethernet interfaces. */
+	return (if_foreach(ifname_bsd_to_linux_ifp_cb, &arg));
 }
 
 /*
@@ -325,14 +342,53 @@ ifname_bsd_to_linux_ifp(struct ifnet *ifp, char *lxname, size_t len)
  * bsdname and lxname need to be least IFNAMSIZ bytes long, but
  * can point to the same buffer.
  */
+struct ifname_linux_to_bsd_cb_s {
+	bool		is_lo;
+	bool		is_eth;
+	int		ethno;
+	int		unit;
+	const char	*lxname;
+	if_t		ifp;
+};
+
+static int
+ifname_linux_to_bsd_cb(if_t ifp, void *arg)
+{
+	struct ifname_linux_to_bsd_cb_s *cbs = arg;
+
+	NET_EPOCH_ASSERT();
+
+	/*
+	 * Allow Linux programs to use FreeBSD names. Don't presume
+	 * we never have an interface named "eth", so don't make
+	 * the test optional based on is_eth.
+	 */
+	if (strncmp(if_name(ifp), cbs->lxname, LINUX_IFNAMSIZ) == 0)
+		goto out;
+	if (cbs->is_eth && IFP_IS_ETH(ifp) && cbs->unit == cbs->ethno)
+		goto out;
+	if (cbs->is_lo && IFP_IS_LOOP(ifp))
+		goto out;
+	if (IFP_IS_ETH(ifp))
+		cbs->ethno++;
+	return (0);
+
+out:
+	cbs->ifp = ifp;
+	return (1);
+}
+
 struct ifnet *
 ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname)
 {
-	struct ifnet *ifp;
-	int len, unit;
+	struct ifname_linux_to_bsd_cb_s arg = {
+		.ethno = 0,
+		.lxname = lxname,
+		.ifp = NULL,
+	};
+	struct epoch_tracker et;
+	int len, ret;
 	char *ep;
-	int index;
-	bool is_eth, is_lo;
 
 	for (len = 0; len < LINUX_IFNAMSIZ; ++len)
 		if (!isalpha(lxname[len]) || lxname[len] == '\0')
@@ -343,34 +399,21 @@ ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname)
 	 * Linux loopback interface name is lo (not lo0),
 	 * we translate lo to lo0, loX to loX.
 	 */
-	is_lo = (len == 2 && strncmp(lxname, "lo", LINUX_IFNAMSIZ) == 0);
-	unit = (int)strtoul(lxname + len, &ep, 10);
+	arg.is_lo = (len == 2 && strncmp(lxname, "lo", LINUX_IFNAMSIZ) == 0);
+	arg.unit = (int)strtoul(lxname + len, &ep, 10);
 	if ((ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) &&
-	    is_lo == 0)
+	    arg.is_lo == 0)
 		return (NULL);
-	index = 0;
-	is_eth = (len == 3 && strncmp(lxname, "eth", len) == 0);
+	arg.is_eth = (len == 3 && strncmp(lxname, "eth", len) == 0);
 
 	CURVNET_SET(TD_TO_VNET(td));
-	IFNET_RLOCK();
-	CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
-		/*
-		 * Allow Linux programs to use FreeBSD names. Don't presume
-		 * we never have an interface named "eth", so don't make
-		 * the test optional based on is_eth.
-		 */
-		if (strncmp(ifp->if_xname, lxname, LINUX_IFNAMSIZ) == 0)
-			break;
-		if (is_eth && IFP_IS_ETH(ifp) && unit == index++)
-			break;
-		if (is_lo && IFP_IS_LOOP(ifp))
-			break;
-	}
-	IFNET_RUNLOCK();
+	NET_EPOCH_ENTER(et);
+	ret = if_foreach(ifname_linux_to_bsd_cb, &arg);
+	NET_EPOCH_EXIT(et);
 	CURVNET_RESTORE();
-	if (ifp != NULL && bsdname != NULL)
-		strlcpy(bsdname, ifp->if_xname, IFNAMSIZ);
-	return (ifp);
+	if (ret > 0 && arg.ifp != NULL && bsdname != NULL)
+		strlcpy(bsdname, if_name(arg.ifp), IFNAMSIZ);
+	return (arg.ifp);
 }
 
 void
@@ -378,7 +421,7 @@ linux_ifflags(struct ifnet *ifp, short *flags)
 {
 	unsigned short fl;
 
-	fl = (ifp->if_flags | ifp->if_drv_flags) & 0xffff;
+	fl = (if_getflags(ifp) | if_getdrvflags(ifp)) & 0xffff;
 	*flags = 0;
 	if (fl & IFF_UP)
 		*flags |= LINUX_IFF_UP;
@@ -402,32 +445,35 @@ linux_ifflags(struct ifnet *ifp, short *flags)
 		*flags |= LINUX_IFF_MULTICAST;
 }
 
+static u_int
+linux_ifhwaddr_cb(void *arg, struct ifaddr *ifa, u_int count)
+{
+	struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+	struct l_sockaddr *lsa = arg;
+
+	if (count > 0)
+		return (0);
+	if (sdl->sdl_type != IFT_ETHER)
+		return (0);
+	bzero(lsa, sizeof(*lsa));
+	lsa->sa_family = LINUX_ARPHRD_ETHER;
+	bcopy(LLADDR(sdl), lsa->sa_data, LINUX_IFHWADDRLEN);
+	return (1);
+}
+
 int
 linux_ifhwaddr(struct ifnet *ifp, struct l_sockaddr *lsa)
 {
-	struct ifaddr *ifa;
-	struct sockaddr_dl *sdl;
 
 	if (IFP_IS_LOOP(ifp)) {
 		bzero(lsa, sizeof(*lsa));
 		lsa->sa_family = LINUX_ARPHRD_LOOPBACK;
 		return (0);
 	}
-
 	if (!IFP_IS_ETH(ifp))
 		return (ENOENT);
-
-	CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
-		sdl = (struct sockaddr_dl*)ifa->ifa_addr;
-		if (sdl != NULL && (sdl->sdl_family == AF_LINK) &&
-		    (sdl->sdl_type == IFT_ETHER)) {
-			bzero(lsa, sizeof(*lsa));
-			lsa->sa_family = LINUX_ARPHRD_ETHER;
-			bcopy(LLADDR(sdl), lsa->sa_data, LINUX_IFHWADDRLEN);
-			return (0);
-		}
-	}
-
+	if (if_foreach_addr_type(ifp, AF_LINK, linux_ifhwaddr_cb, lsa) > 0)
+		return (0);
 	return (ENOENT);
 }
 
diff --git a/sys/compat/linux/linux.h b/sys/compat/linux/linux.h
index 055d8e3b9cf6..e133c35010cf 100644
--- a/sys/compat/linux/linux.h
+++ b/sys/compat/linux/linux.h
@@ -287,8 +287,8 @@ struct l_statx {
 /*
  * Criteria for interface name translation
  */
-#define	IFP_IS_ETH(ifp)		((ifp)->if_type == IFT_ETHER)
-#define	IFP_IS_LOOP(ifp)	((ifp)->if_type == IFT_LOOP)
+#define	IFP_IS_ETH(ifp)		(if_gettype(ifp) == IFT_ETHER)
+#define	IFP_IS_LOOP(ifp)	(if_gettype(ifp) == IFT_LOOP)
 
 struct ifnet;
 
diff --git a/sys/compat/linux/linux_ioctl.c b/sys/compat/linux/linux_ioctl.c
index bfab28f841a0..8900102bbbc7 100644
--- a/sys/compat/linux/linux_ioctl.c
+++ b/sys/compat/linux/linux_ioctl.c
@@ -2108,40 +2108,91 @@ linux_ioctl_ifname(struct thread *td, struct l_ifreq *uifr)
 /*
  * Implement the SIOCGIFCONF ioctl
  */
+static u_int
+linux_ifconf_ifaddr_cb(void *arg, struct ifaddr *ifa, u_int count)
+{
+#ifdef COMPAT_LINUX32
+	struct l_ifconf *ifc;
+#else
+	struct ifconf *ifc;
+#endif
+
+	ifc = arg;
+	ifc->ifc_len += sizeof(struct l_ifreq);
+	return (1);
+}
+
+static int
+linux_ifconf_ifnet_cb(if_t ifp, void *arg)
+{
+
+	if_foreach_addr_type(ifp, AF_INET, linux_ifconf_ifaddr_cb, arg);
+	return (0);
+}
+
+struct linux_ifconfig_ifaddr_cb2_s {
+	struct l_ifreq ifr;
+	struct sbuf *sb;
+	size_t max_len;
+	size_t valid_len;
+};
+
+static u_int
+linux_ifconf_ifaddr_cb2(void *arg, struct ifaddr *ifa, u_int len)
+{
+	struct linux_ifconfig_ifaddr_cb2_s *cbs = arg;
+	struct sockaddr *sa = ifa->ifa_addr;
+
+	cbs->ifr.ifr_addr.sa_family = LINUX_AF_INET;
+	memcpy(cbs->ifr.ifr_addr.sa_data, sa->sa_data,
+	    sizeof(cbs->ifr.ifr_addr.sa_data));
+	sbuf_bcat(cbs->sb, &cbs->ifr, sizeof(cbs->ifr));
+	cbs->max_len += sizeof(cbs->ifr);
+
+	if (sbuf_error(cbs->sb) == 0)
+		cbs->valid_len = sbuf_len(cbs->sb);
+	return (1);
+}
+
+static int
+linux_ifconf_ifnet_cb2(if_t ifp, void *arg)
+{
+	struct linux_ifconfig_ifaddr_cb2_s *cbs = arg;
+
+	bzero(&cbs->ifr, sizeof(cbs->ifr));
+	ifname_bsd_to_linux_ifp(ifp, cbs->ifr.ifr_name,
+	    sizeof(cbs->ifr.ifr_name));
+
+	/* Walk the address list */
+	if_foreach_addr_type(ifp, AF_INET, linux_ifconf_ifaddr_cb2, cbs);
+	return (0);
+}
 
 static int
 linux_ifconf(struct thread *td, struct ifconf *uifc)
 {
+	struct linux_ifconfig_ifaddr_cb2_s cbs;
 	struct epoch_tracker et;
 #ifdef COMPAT_LINUX32
 	struct l_ifconf ifc;
 #else
 	struct ifconf ifc;
 #endif
-	struct l_ifreq ifr;
-	struct ifnet *ifp;
-	struct ifaddr *ifa;
 	struct sbuf *sb;
-	int error, full = 0, valid_len, max_len;
+	int error, full;
 
 	error = copyin(uifc, &ifc, sizeof(ifc));
 	if (error != 0)
 		return (error);
-
-	max_len = maxphys - 1;
+	full = 0;
+	cbs.max_len = maxphys - 1;
 
 	CURVNET_SET(TD_TO_VNET(td));
 	/* handle the 'request buffer size' case */
 	if ((l_uintptr_t)ifc.ifc_buf == PTROUT(NULL)) {
 		ifc.ifc_len = 0;
 		NET_EPOCH_ENTER(et);
-		CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
-			CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
-				struct sockaddr *sa = ifa->ifa_addr;
-				if (sa->sa_family == AF_INET)
-					ifc.ifc_len += sizeof(ifr);
-			}
-		}
+		if_foreach(linux_ifconf_ifnet_cb, &ifc);
 		NET_EPOCH_EXIT(et);
 		error = copyout(&ifc, uifc, sizeof(ifc));
 		CURVNET_RESTORE();
@@ -2154,56 +2205,25 @@ linux_ifconf(struct thread *td, struct ifconf *uifc)
 	}
 
 again:
-	if (ifc.ifc_len <= max_len) {
-		max_len = ifc.ifc_len;
+	if (ifc.ifc_len <= cbs.max_len) {
+		cbs.max_len = ifc.ifc_len;
 		full = 1;
 	}
-	sb = sbuf_new(NULL, NULL, max_len + 1, SBUF_FIXEDLEN);
-	max_len = 0;
-	valid_len = 0;
+	cbs.sb = sb = sbuf_new(NULL, NULL, cbs.max_len + 1, SBUF_FIXEDLEN);
+	cbs.max_len = 0;
+	cbs.valid_len = 0;
 
 	/* Return all AF_INET addresses of all interfaces */
 	NET_EPOCH_ENTER(et);
-	CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
-		int addrs = 0;
-
-		bzero(&ifr, sizeof(ifr));
-		ifname_bsd_to_linux_ifp(ifp, ifr.ifr_name,
-		    sizeof(ifr.ifr_name));
-
-		/* Walk the address list */
-		CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
-			struct sockaddr *sa = ifa->ifa_addr;
-
-			if (sa->sa_family == AF_INET) {
-				ifr.ifr_addr.sa_family = LINUX_AF_INET;
-				memcpy(ifr.ifr_addr.sa_data, sa->sa_data,
-				    sizeof(ifr.ifr_addr.sa_data));
-				sbuf_bcat(sb, &ifr, sizeof(ifr));
-				max_len += sizeof(ifr);
-				addrs++;
-			}
-
-			if (sbuf_error(sb) == 0)
-				valid_len = sbuf_len(sb);
-		}
-		if (addrs == 0) {
-			bzero((caddr_t)&ifr.ifr_addr, sizeof(ifr.ifr_addr));
-			sbuf_bcat(sb, &ifr, sizeof(ifr));
-			max_len += sizeof(ifr);
-
-			if (sbuf_error(sb) == 0)
-				valid_len = sbuf_len(sb);
-		}
-	}
+	if_foreach(linux_ifconf_ifnet_cb2, &cbs);
 	NET_EPOCH_EXIT(et);
 
-	if (valid_len != max_len && !full) {
+	if (cbs.valid_len != cbs.max_len && !full) {
 		sbuf_delete(sb);
 		goto again;
 	}
 
-	ifc.ifc_len = valid_len;
+	ifc.ifc_len = cbs.valid_len;
 	sbuf_finish(sb);
 	error = copyout(sbuf_data(sb), PTRIN(ifc.ifc_buf), ifc.ifc_len);
 	if (error == 0)