The branch main has been updated by glebius:

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

commit be7bdb1cf0b0f665bcd57c03fde41bd6c01d8ead
Author:     Gleb Smirnoff <[email protected]>
AuthorDate: 2025-10-15 18:54:57 +0000
Commit:     Gleb Smirnoff <[email protected]>
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()
 {

Reply via email to