Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 15 Oct 2025 18:55:33 GMT
From:      Gleb Smirnoff <glebius@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: be7bdb1cf0b0 - main - netinet: do route lookup when asked to join multicast group on ifindex 0
Message-ID:  <202510151855.59FItXxP072781@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by glebius:

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

commit be7bdb1cf0b0f665bcd57c03fde41bd6c01d8ead
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2025-10-15 18:54:57 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2025-10-15 18:54:57 +0000

    netinet: do route lookup when asked to join multicast group on ifindex 0
    
    The code to do the route lookup was already there, but was used only for
    the legacy IP_ADD_MEMBERSHIP when called without index.  Do same lookup
    for IP_ADD_MEMBERSHIP with index and what is more important for
    MCAST_JOIN_GROUP, if the supplied index is 0.  This is a neat feature and
    Linux does that, so this should make a few applications easier portable to
    FreeBSD.
    
    Differential Revision:  https://reviews.freebsd.org/D52918
---
 sys/netinet/in_mcast.c                | 113 ++++++++++++++--------------------
 tests/sys/netinet/multicast-receive.c |  16 +++--
 tests/sys/netinet/multicast.sh        |  47 ++++++++++++++
 3 files changed, 104 insertions(+), 72 deletions(-)

diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c
index f5b20c49ffd2..ba112afbf002 100644
--- a/sys/netinet/in_mcast.c
+++ b/sys/netinet/in_mcast.c
@@ -159,9 +159,6 @@ static struct ip_moptions *
 static int	inp_get_source_filters(struct inpcb *, struct sockopt *);
 static int	inp_join_group(struct inpcb *, struct sockopt *);
 static int	inp_leave_group(struct inpcb *, struct sockopt *);
-static struct ifnet *
-		inp_lookup_mcast_ifp(const struct inpcb *,
-		    const struct sockaddr_in *, const struct in_addr);
 static int	inp_block_unblock_source(struct inpcb *, struct sockopt *);
 static int	inp_set_multicast_if(struct inpcb *, struct sockopt *);
 static int	inp_set_source_filters(struct inpcb *, struct sockopt *);
@@ -1832,69 +1829,55 @@ inp_getmoptions(struct inpcb *inp, struct sockopt *sopt)
 }
 
 /*
- * Look up the ifnet to use for a multicast group membership,
- * given the IPv4 address of an interface, and the IPv4 group address.
- *
- * This routine exists to support legacy multicast applications
- * which do not understand that multicast memberships are scoped to
- * specific physical links in the networking stack, or which need
- * to join link-scope groups before IPv4 addresses are configured.
- *
- * Use this socket's current FIB number for any required FIB lookup.
- * If ina is INADDR_ANY, look up the group address in the unicast FIB,
- * and use its ifp; usually, this points to the default next-hop.
- *
- * If the FIB lookup fails, attempt to use the first non-loopback
- * interface with multicast capability in the system as a
- * last resort. The legacy IPv4 ASM API requires that we do
- * this in order to allow groups to be joined when the routing
- * table has not yet been populated during boot.
- *
- * Returns NULL if no ifp could be found, otherwise return referenced ifp.
+ * Look up the ifnet to join a multicast group membership via legacy
+ * IP_ADD_MEMBERSHIP or via more modern MCAST_JOIN_GROUP.
  *
- * FUTURE: Implement IPv4 source-address selection.
+ * If the interface index was specified explicitly, just use it.  If the
+ * address was specified (legacy), try to find matching interface.  Else
+ * (index == 0 && no address) do a route lookup.  If that fails for a modern
+ * MCAST_JOIN_GROUP return failure, for legacy IP_ADD_MEMBERSHIP find first
+ * multicast capable interface.
  */
 static struct ifnet *
-inp_lookup_mcast_ifp(const struct inpcb *inp,
-    const struct sockaddr_in *gsin, const struct in_addr ina)
+inp_lookup_mcast_ifp(const struct inpcb *inp, const struct in_addr maddr,
+const struct in_addr *ina, const u_int index)
 {
 	struct ifnet *ifp;
 	struct nhop_object *nh;
 
 	NET_EPOCH_ASSERT();
-	KASSERT(inp != NULL, ("%s: inp must not be NULL", __func__));
-	KASSERT(gsin->sin_family == AF_INET, ("%s: not AF_INET", __func__));
-	KASSERT(IN_MULTICAST(ntohl(gsin->sin_addr.s_addr)),
-	    ("%s: not multicast", __func__));
 
-	ifp = NULL;
-	if (!in_nullhost(ina)) {
-		INADDR_TO_IFP(ina, ifp);
+	if (index != 0)
+		return (ifnet_byindex_ref(index));
+
+	if (ina != NULL && !in_nullhost(*ina)) {
+		INADDR_TO_IFP(*ina, ifp);
 		if (ifp != NULL)
 			if_ref(ifp);
-	} else {
-		nh = fib4_lookup(inp->inp_inc.inc_fibnum, gsin->sin_addr, 0, NHR_NONE, 0);
-		if (nh != NULL) {
-			ifp = nh->nh_ifp;
-			if_ref(ifp);
-		} else {
-			struct in_ifaddr *ia;
-			struct ifnet *mifp;
-
-			mifp = NULL;
-			CK_STAILQ_FOREACH(ia, &V_in_ifaddrhead, ia_link) {
-				mifp = ia->ia_ifp;
-				if (!(mifp->if_flags & IFF_LOOPBACK) &&
-				     (mifp->if_flags & IFF_MULTICAST)) {
-					ifp = mifp;
-					if_ref(ifp);
-					break;
-				}
+		return (ifp);
+	}
+
+	nh = fib4_lookup(inp->inp_inc.inc_fibnum, maddr, 0, NHR_NONE, 0);
+	if (nh != NULL) {
+		ifp = nh->nh_ifp;
+		if_ref(ifp);
+		return (ifp);
+	}
+
+	if (ina != NULL) {
+		struct in_ifaddr *ia;
+
+		CK_STAILQ_FOREACH(ia, &V_in_ifaddrhead, ia_link) {
+			if (!(ia->ia_ifp->if_flags & IFF_LOOPBACK) &&
+			     (ia->ia_ifp->if_flags & IFF_MULTICAST)) {
+				ifp = ia->ia_ifp;
+				if_ref(ifp);
+				return (ifp);
 			}
 		}
 	}
 
-	return (ifp);
+	return (NULL);
 }
 
 /*
@@ -1926,13 +1909,13 @@ inp_join_group(struct inpcb *inp, struct sockopt *sopt)
 	switch (sopt->sopt_name) {
 	case IP_ADD_MEMBERSHIP: {
 		struct ip_mreqn mreqn;
+		bool mreq;
 
-		if (sopt->sopt_valsize == sizeof(struct ip_mreqn))
-			error = sooptcopyin(sopt, &mreqn,
-			    sizeof(struct ip_mreqn), sizeof(struct ip_mreqn));
-		else
-			error = sooptcopyin(sopt, &mreqn,
-			    sizeof(struct ip_mreq), sizeof(struct ip_mreq));
+		mreq = (sopt->sopt_valsize != sizeof(struct ip_mreqn));
+
+		error = sooptcopyin(sopt, &mreqn,
+		    mreq ? sizeof(struct ip_mreq) : sizeof(struct ip_mreqn),
+		    mreq ? sizeof(struct ip_mreq) : sizeof(struct ip_mreqn));
 		if (error)
 			return (error);
 
@@ -1943,12 +1926,9 @@ inp_join_group(struct inpcb *inp, struct sockopt *sopt)
 			return (EINVAL);
 
 		NET_EPOCH_ENTER(et);
-		if (sopt->sopt_valsize == sizeof(struct ip_mreqn) &&
-		    mreqn.imr_ifindex != 0)
-			ifp = ifnet_byindex_ref(mreqn.imr_ifindex);
-		else
-			ifp = inp_lookup_mcast_ifp(inp, &gsa->sin,
-			    mreqn.imr_address);
+		ifp = inp_lookup_mcast_ifp(inp, mreqn.imr_multiaddr,
+		    mreq ? &mreqn.imr_address : NULL,
+		    mreq ? 0 : mreqn.imr_ifindex);
 		NET_EPOCH_EXIT(et);
 		break;
 	}
@@ -1971,8 +1951,8 @@ inp_join_group(struct inpcb *inp, struct sockopt *sopt)
 		ssa->sin.sin_addr = mreqs.imr_sourceaddr;
 
 		NET_EPOCH_ENTER(et);
-		ifp = inp_lookup_mcast_ifp(inp, &gsa->sin,
-		    mreqs.imr_interface);
+		ifp = inp_lookup_mcast_ifp(inp, mreqs.imr_multiaddr,
+		    &mreqs.imr_interface, 0);
 		NET_EPOCH_EXIT(et);
 		CTR3(KTR_IGMPV3, "%s: imr_interface = 0x%08x, ifp = %p",
 		    __func__, ntohl(mreqs.imr_interface.s_addr), ifp);
@@ -2013,7 +1993,8 @@ inp_join_group(struct inpcb *inp, struct sockopt *sopt)
 			return (EINVAL);
 
 		NET_EPOCH_ENTER(et);
-		ifp = ifnet_byindex_ref(gsr.gsr_interface);
+		ifp = inp_lookup_mcast_ifp(inp, gsa->sin.sin_addr, NULL,
+		    gsr.gsr_interface);
 		NET_EPOCH_EXIT(et);
 		if (ifp == NULL)
 			return (EADDRNOTAVAIL);
diff --git a/tests/sys/netinet/multicast-receive.c b/tests/sys/netinet/multicast-receive.c
index 81d0f10f5cfe..62fc68200dd6 100644
--- a/tests/sys/netinet/multicast-receive.c
+++ b/tests/sys/netinet/multicast-receive.c
@@ -36,6 +36,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sysexits.h>
 #include <limits.h>
 #include <err.h>
 
@@ -93,8 +94,9 @@ usage:
 			.imr_multiaddr = maddr,
 			.imr_interface = ifaddr,
 		};
-		assert(setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
-		    sizeof(mreq)) == 0);
+		if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+		    sizeof(mreq)) != 0)
+			err(EX_OSERR, "setsockopt");
 	} else if (strcmp(argv[1], "ip_mreqn") == 0) {
 		/*
 		 * ip_mreqn shall be used with index, but for testing
@@ -105,8 +107,9 @@ usage:
 			.imr_address = index ? (struct in_addr){ 0 } : ifaddr,
 			.imr_ifindex = index ? ifindex : 0,
 		};
-		assert(setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn,
-		    sizeof(mreqn)) == 0);
+		if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn,
+		    sizeof(mreqn)) != 0)
+			err(EX_OSERR, "setsockopt");
 	} else if (strcmp(argv[1], "group_req") == 0) {
 		if (!index)
 			errx(1, "group_req expects index");
@@ -116,8 +119,9 @@ usage:
 		gsa->sin_family = AF_INET;
 		gsa->sin_len = sizeof(struct sockaddr_in);
 		gsa->sin_addr = maddr;
-		assert(setsockopt(s, IPPROTO_IP, MCAST_JOIN_GROUP, &greq,
-		    sizeof(greq)) == 0);
+		if (setsockopt(s, IPPROTO_IP, MCAST_JOIN_GROUP, &greq,
+		    sizeof(greq)) != 0)
+			err(EX_OSERR, "setsockopt");
 	} else
 		goto usage;
 
diff --git a/tests/sys/netinet/multicast.sh b/tests/sys/netinet/multicast.sh
index 273970d0f7ea..34094ff08705 100755
--- a/tests/sys/netinet/multicast.sh
+++ b/tests/sys/netinet/multicast.sh
@@ -79,6 +79,27 @@ IP_ADD_MEMBERSHIP_ip_mreq_body()
 	    0.0.0.0 6676 233.252.0.1 6676 192.0.3.1 hello
 	atf_check -s exit:0 sh -c "wait $pid; exit $?"
 	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
+
+	# join group on the first multicast capable interface (epair1a)
+	multicast_join ip_mreq 0.0.0.0
+	atf_check -s exit:0 -o empty \
+	    jexec mjail1 $(atf_get_srcdir)/multicast-send \
+	    0.0.0.0 6676 233.252.0.1 6676 192.0.2.1 hello
+	atf_check -s exit:0 sh -c "wait $pid; exit $?"
+	atf_check -s exit:0 -o inline:"192.0.2.1:6676 hello\n" cat out
+
+	# Set up the receiving jail so that first multicast capable interface
+	# is epair1a and default route points into epair2a.  This will allow us
+	# to exercise both branches of inp_lookup_mcast_ifp().
+	jexec mjail2 route add default 192.0.3.254
+
+	# join group on the interface determined by the route lookup
+	multicast_join ip_mreq 0.0.0.0
+	atf_check -s exit:0 -o empty \
+	    jexec mjail1 $(atf_get_srcdir)/multicast-send \
+	    0.0.0.0 6676 233.252.0.1 6676 192.0.3.1 hello
+	atf_check -s exit:0 sh -c "wait $pid; exit $?"
+	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
 }
 IP_ADD_MEMBERSHIP_ip_mreq_cleanup()
 {
@@ -111,6 +132,19 @@ IP_ADD_MEMBERSHIP_ip_mreqn_body()
 	    0.0.0.0 6676 233.252.0.1 6676 ${epair2}a hello
 	atf_check -s exit:0 sh -c "wait $pid; exit $?"
 	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
+
+	# try to join group on the interface determined by the route lookup
+	atf_check -s exit:71 -e inline:"multicast-receive: setsockopt: Can't assign requested address\n" \
+	    jexec mjail2 $(atf_get_srcdir)/multicast-receive \
+	    ip_mreqn 233.252.0.1 6676 0
+	# add route and try again
+	jexec mjail2 route add default 192.0.3.254
+        multicast_join ip_mreqn 0
+	atf_check -s exit:0 -o empty \
+	    jexec mjail1 $(atf_get_srcdir)/multicast-send \
+	    0.0.0.0 6676 233.252.0.1 6676 192.0.3.1 hello
+	atf_check -s exit:0 sh -c "wait $pid; exit $?"
+	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
 }
 IP_ADD_MEMBERSHIP_ip_mreqn_cleanup()
 {
@@ -143,6 +177,19 @@ MCAST_JOIN_GROUP_body()
 	    0.0.0.0 6676 233.252.0.1 6676 ${epair2}a hello
 	atf_check -s exit:0 sh -c "wait $pid; exit $?"
 	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
+
+	# try to join group on the interface determined by the route lookup
+	atf_check -s exit:71 -e inline:"multicast-receive: setsockopt: Can't assign requested address\n" \
+	    jexec mjail2 $(atf_get_srcdir)/multicast-receive \
+	    group_req 233.252.0.1 6676 0
+	# add route and try again
+	jexec mjail2 route add default 192.0.3.254
+        multicast_join group_req 0
+	atf_check -s exit:0 -o empty \
+	    jexec mjail1 $(atf_get_srcdir)/multicast-send \
+	    0.0.0.0 6676 233.252.0.1 6676 192.0.3.1 hello
+	atf_check -s exit:0 sh -c "wait $pid; exit $?"
+	atf_check -s exit:0 -o inline:"192.0.3.1:6676 hello\n" cat out
 }
 MCAST_JOIN_GROUP_cleanup()
 {



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202510151855.59FItXxP072781>