Linux and FreeBSD both support the use of struct ip_mreqn in
IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP. This struct adds one more field
to pass an interface index to the kernel (instead of using the IP
address).

struct ip_mreqn {
       struct  in_addr imr_multiaddr;  /* IP multicast address of group */
       struct  in_addr imr_address;    /* local IP address of interface */
       int             imr_ifindex;    /* interface index */
};

So if imr_ifindex is not 0 then this value is used to define the outgoing
interface instead of doing a lookup with imr_address.
This is something I want to use in ospfd(8) to support unnumbered
interfaces (or actually point-to-point interfaces using the same source
IP).

-- 
:wq Claudio

Index: net/if_gre.c
===================================================================
RCS file: /cvs/src/sys/net/if_gre.c,v
retrieving revision 1.163
diff -u -p -r1.163 if_gre.c
--- net/if_gre.c        12 Dec 2020 11:49:02 -0000      1.163
+++ net/if_gre.c        6 Jan 2021 08:31:46 -0000
@@ -3640,7 +3640,7 @@ nvgre_up(struct nvgre_softc *sc)
 
        switch (tunnel->t_af) {
        case AF_INET:
-               inm = in_addmulti(&tunnel->t_dst4, ifp0);
+               inm = in_addmulti(&tunnel->t_dst4, ifp0->if_index);
                if (inm == NULL) {
                        error = ECONNABORTED;
                        goto remove_ucast;
Index: net/if_pfsync.c
===================================================================
RCS file: /cvs/src/sys/net/if_pfsync.c,v
retrieving revision 1.280
diff -u -p -r1.280 if_pfsync.c
--- net/if_pfsync.c     4 Jan 2021 12:48:27 -0000       1.280
+++ net/if_pfsync.c     6 Jan 2021 08:31:46 -0000
@@ -1424,7 +1424,7 @@ pfsyncioctl(struct ifnet *ifp, u_long cm
                        addr.s_addr = INADDR_PFSYNC_GROUP;
 
                        if ((imo->imo_membership[0] =
-                           in_addmulti(&addr, sifp)) == NULL) {
+                           in_addmulti(&addr, sifp->if_index)) == NULL) {
                                sc->sc_sync_ifidx = 0;
                                return (ENOBUFS);
                        }
Index: net/if_vxlan.c
===================================================================
RCS file: /cvs/src/sys/net/if_vxlan.c,v
retrieving revision 1.81
diff -u -p -r1.81 if_vxlan.c
--- net/if_vxlan.c      21 Aug 2020 22:59:27 -0000      1.81
+++ net/if_vxlan.c      6 Jan 2021 08:31:46 -0000
@@ -274,7 +274,7 @@ vxlan_multicast_join(struct ifnet *ifp, 
                return (EADDRNOTAVAIL);
 
        if ((imo->imo_membership[0] =
-           in_addmulti(&dst4->sin_addr, mifp)) == NULL)
+           in_addmulti(&dst4->sin_addr, mifp->if_index)) == NULL)
                return (ENOBUFS);
 
        imo->imo_num_memberships++;
Index: netinet/in.c
===================================================================
RCS file: /cvs/src/sys/netinet/in.c,v
retrieving revision 1.170
diff -u -p -r1.170 in.c
--- netinet/in.c        27 May 2020 11:19:28 -0000      1.170
+++ netinet/in.c        6 Jan 2021 08:31:46 -0000
@@ -730,7 +730,7 @@ in_ifinit(struct ifnet *ifp, struct in_i
                struct in_addr addr;
 
                addr.s_addr = INADDR_ALLHOSTS_GROUP;
-               ia->ia_allhosts = in_addmulti(&addr, ifp);
+               ia->ia_allhosts = in_addmulti(&addr, ifp->if_index);
        }
 
 out:
@@ -847,10 +847,15 @@ in_broadcast(struct in_addr in, u_int rt
  * Add an address to the list of IP multicast addresses for a given interface.
  */
 struct in_multi *
-in_addmulti(struct in_addr *ap, struct ifnet *ifp)
+in_addmulti(struct in_addr *ap, unsigned int ifidx)
 {
        struct in_multi *inm;
        struct ifreq ifr;
+       struct ifnet *ifp;
+
+       ifp = if_get(ifidx);
+       if (ifp == NULL)
+               return (NULL);
 
        /*
         * See if address already in list.
@@ -867,14 +872,16 @@ in_addmulti(struct in_addr *ap, struct i
                 * and link it into the interface's multicast list.
                 */
                inm = malloc(sizeof(*inm), M_IPMADDR, M_NOWAIT | M_ZERO);
-               if (inm == NULL)
+               if (inm == NULL) {
+                       if_put(ifp);
                        return (NULL);
+               }
 
                inm->inm_sin.sin_len = sizeof(struct sockaddr_in);
                inm->inm_sin.sin_family = AF_INET;
                inm->inm_sin.sin_addr = *ap;
                inm->inm_refcnt = 1;
-               inm->inm_ifidx = ifp->if_index;
+               inm->inm_ifidx = ifidx;
                inm->inm_ifma.ifma_addr = sintosa(&inm->inm_sin);
 
                /*
@@ -884,6 +891,7 @@ in_addmulti(struct in_addr *ap, struct i
                memset(&ifr, 0, sizeof(ifr));
                memcpy(&ifr.ifr_addr, &inm->inm_sin, sizeof(inm->inm_sin));
                if ((*ifp->if_ioctl)(ifp, SIOCADDMULTI,(caddr_t)&ifr) != 0) {
+                       if_put(ifp);
                        free(inm, M_IPMADDR, sizeof(*inm));
                        return (NULL);
                }
@@ -891,12 +899,14 @@ in_addmulti(struct in_addr *ap, struct i
                TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &inm->inm_ifma,
                    ifma_list);
 
+
                /*
                 * Let IGMP know that we have joined a new IP multicast group.
                 */
                igmp_joingroup(inm);
        }
 
+       if_put(ifp);
        return (inm);
 }
 
Index: netinet/in.h
===================================================================
RCS file: /cvs/src/sys/netinet/in.h,v
retrieving revision 1.138
diff -u -p -r1.138 in.h
--- netinet/in.h        22 Aug 2020 17:55:30 -0000      1.138
+++ netinet/in.h        6 Jan 2021 08:31:46 -0000
@@ -360,6 +360,12 @@ struct ip_mreq {
        struct  in_addr imr_interface;  /* local IP address of interface */
 };
 
+struct ip_mreqn {
+       struct  in_addr imr_multiaddr;  /* IP multicast address of group */
+       struct  in_addr imr_address;    /* local IP address of interface */
+       int             imr_ifindex;    /* interface index */
+};
+
 /*
  * Argument for IP_PORTRANGE:
  * - which range to search when port is unspecified at bind() or connect()
Index: netinet/in_var.h
===================================================================
RCS file: /cvs/src/sys/netinet/in_var.h,v
retrieving revision 1.41
diff -u -p -r1.41 in_var.h
--- netinet/in_var.h    18 Oct 2018 15:23:04 -0000      1.41
+++ netinet/in_var.h    6 Jan 2021 08:31:46 -0000
@@ -154,7 +154,7 @@ do {                                                        
                \
 
 int    in_ifinit(struct ifnet *,
            struct in_ifaddr *, struct sockaddr_in *, int);
-struct in_multi *in_addmulti(struct in_addr *, struct ifnet *);
+struct in_multi *in_addmulti(struct in_addr *, unsigned int);
 void   in_delmulti(struct in_multi *);
 int    in_hasmulti(struct in_addr *, struct ifnet *);
 void   in_ifscrub(struct ifnet *, struct in_ifaddr *);
Index: netinet/ip_carp.c
===================================================================
RCS file: /cvs/src/sys/netinet/ip_carp.c,v
retrieving revision 1.350
diff -u -p -r1.350 ip_carp.c
--- netinet/ip_carp.c   4 Jan 2021 15:02:34 -0000       1.350
+++ netinet/ip_carp.c   6 Jan 2021 08:31:46 -0000
@@ -1877,7 +1877,7 @@ carp_join_multicast(struct carp_softc *s
                return (0);
 
        addr.s_addr = sc->sc_peer.s_addr;
-       if ((imm = in_addmulti(&addr, &sc->sc_if)) == NULL)
+       if ((imm = in_addmulti(&addr, sc->sc_if.if_index)) == NULL)
                return (ENOBUFS);
 
        imo->imo_membership[0] = imm;
Index: netinet/ip_output.c
===================================================================
RCS file: /cvs/src/sys/netinet/ip_output.c,v
retrieving revision 1.358
diff -u -p -r1.358 ip_output.c
--- netinet/ip_output.c 20 Dec 2020 21:15:47 -0000      1.358
+++ netinet/ip_output.c 6 Jan 2021 08:31:46 -0000
@@ -73,6 +73,7 @@
 #endif /* IPSEC */
 
 int ip_pcbopts(struct mbuf **, struct mbuf *);
+int ip_multicast_if(struct ip_mreqn *, u_int, unsigned int *);
 int ip_setmoptions(int, struct ip_moptions **, struct mbuf *, u_int);
 void ip_mloopback(struct ifnet *, struct mbuf *, struct sockaddr_in *);
 static __inline u_int16_t __attribute__((__unused__))
@@ -1337,6 +1338,51 @@ ip_pcbopts(struct mbuf **pcbopt, struct 
 }
 
 /*
+ * Lookup the interface based on the information in the ip_mreqn struct.
+ */
+int
+ip_multicast_if(struct ip_mreqn *mreq, u_int rtableid, unsigned int *ifidx)
+{
+       struct sockaddr_in sin;
+       struct rtentry *rt;
+
+       /*
+        * In case userland provides the imr_ifindex use this as interface.
+        * If no interface address was provided, use the interface of
+        * the route to the given multicast address.
+        */
+       if (mreq->imr_ifindex != 0) {
+               *ifidx = mreq->imr_ifindex;
+       } else if (mreq->imr_address.s_addr == INADDR_ANY) {
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_len = sizeof(sin);
+               sin.sin_family = AF_INET;
+               sin.sin_addr = mreq->imr_multiaddr;
+               rt = rtalloc(sintosa(&sin), RT_RESOLVE, rtableid);
+               if (!rtisvalid(rt)) {
+                       rtfree(rt);
+                       return EADDRNOTAVAIL;
+               }
+               *ifidx = rt->rt_ifidx;
+               rtfree(rt);
+       } else {
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_len = sizeof(sin);
+               sin.sin_family = AF_INET;
+               sin.sin_addr = mreq->imr_address;
+               rt = rtalloc(sintosa(&sin), 0, rtableid);
+               if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) {
+                       rtfree(rt);
+                       return EADDRNOTAVAIL;
+               }
+               *ifidx = rt->rt_ifidx;
+               rtfree(rt);
+       }
+
+       return 0;
+}
+
+/*
  * Set the IP multicast options in response to user setsockopt().
  */
 int
@@ -1345,12 +1391,12 @@ ip_setmoptions(int optname, struct ip_mo
 {
        struct in_addr addr;
        struct in_ifaddr *ia;
-       struct ip_mreq *mreq;
+       struct ip_mreqn mreqn;
        struct ifnet *ifp = NULL;
        struct ip_moptions *imo = *imop;
        struct in_multi **immp;
-       struct rtentry *rt;
        struct sockaddr_in sin;
+       unsigned int ifidx;
        int i, error = 0;
        u_char loop;
 
@@ -1438,77 +1484,56 @@ ip_setmoptions(int optname, struct ip_mo
                 * Add a multicast group membership.
                 * Group must be a valid IP multicast address.
                 */
-               if (m == NULL || m->m_len != sizeof(struct ip_mreq)) {
+               if (m == NULL || !(m->m_len == sizeof(struct ip_mreq) ||
+                   m->m_len == sizeof(struct ip_mreqn))) {
                        error = EINVAL;
                        break;
                }
-               mreq = mtod(m, struct ip_mreq *);
-               if (!IN_MULTICAST(mreq->imr_multiaddr.s_addr)) {
+               memset(&mreqn, 0, sizeof(mreqn));
+               memcpy(&mreqn, mtod(m, void *), m->m_len);
+               if (!IN_MULTICAST(mreqn.imr_multiaddr.s_addr)) {
                        error = EINVAL;
                        break;
                }
-               /*
-                * If no interface address was provided, use the interface of
-                * the route to the given multicast address.
-                */
-               if (mreq->imr_interface.s_addr == INADDR_ANY) {
-                       memset(&sin, 0, sizeof(sin));
-                       sin.sin_len = sizeof(sin);
-                       sin.sin_family = AF_INET;
-                       sin.sin_addr = mreq->imr_multiaddr;
-                       rt = rtalloc(sintosa(&sin), RT_RESOLVE, rtableid);
-                       if (!rtisvalid(rt)) {
-                               rtfree(rt);
-                               error = EADDRNOTAVAIL;
-                               break;
-                       }
-               } else {
-                       memset(&sin, 0, sizeof(sin));
-                       sin.sin_len = sizeof(sin);
-                       sin.sin_family = AF_INET;
-                       sin.sin_addr = mreq->imr_interface;
-                       rt = rtalloc(sintosa(&sin), 0, rtableid);
-                       if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) {
-                               rtfree(rt);
-                               error = EADDRNOTAVAIL;
-                               break;
-                       }
-               }
-               ifp = if_get(rt->rt_ifidx);
-               rtfree(rt);
+
+               error = ip_multicast_if(&mreqn, rtableid, &ifidx);
+               if (error)
+                       break;
 
                /*
                 * See if we found an interface, and confirm that it
                 * supports multicast.
                 */
+               ifp = if_get(ifidx);
                if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
                        error = EADDRNOTAVAIL;
                        if_put(ifp);
                        break;
                }
+               if_put(ifp);
+
                /*
                 * See if the membership already exists or if all the
                 * membership slots are full.
                 */
                for (i = 0; i < imo->imo_num_memberships; ++i) {
-                       if (imo->imo_membership[i]->inm_ifidx
-                                               == ifp->if_index &&
+                       if (imo->imo_membership[i]->inm_ifidx == ifidx &&
                            imo->imo_membership[i]->inm_addr.s_addr
-                                               == mreq->imr_multiaddr.s_addr)
+                                               == mreqn.imr_multiaddr.s_addr)
                                break;
                }
                if (i < imo->imo_num_memberships) {
                        error = EADDRINUSE;
-                       if_put(ifp);
                        break;
                }
                if (imo->imo_num_memberships == imo->imo_max_memberships) {
                        struct in_multi **nmships, **omships;
                        size_t newmax;
                        /*
-                        * Resize the vector to next power-of-two minus 1. If 
the
-                        * size would exceed the maximum then we know we've 
really
-                        * run out of entries. Otherwise, we reallocate the 
vector.
+                        * Resize the vector to next power-of-two minus 1. If
+                        * the size would exceed the maximum then we know we've
+                        * really run out of entries. Otherwise, we reallocate
+                        * the vector.
                         */
                        nmships = NULL;
                        omships = imo->imo_membership;
@@ -1529,7 +1554,6 @@ ip_setmoptions(int optname, struct ip_mo
                        }
                        if (nmships == NULL) {
                                error = ENOBUFS;
-                               if_put(ifp);
                                break;
                        }
                }
@@ -1538,13 +1562,11 @@ ip_setmoptions(int optname, struct ip_mo
                 * address list for the given interface.
                 */
                if ((imo->imo_membership[i] =
-                   in_addmulti(&mreq->imr_multiaddr, ifp)) == NULL) {
+                   in_addmulti(&mreqn.imr_multiaddr, ifidx)) == NULL) {
                        error = ENOBUFS;
-                       if_put(ifp);
                        break;
                }
                ++imo->imo_num_memberships;
-               if_put(ifp);
                break;
 
        case IP_DROP_MEMBERSHIP:
@@ -1552,42 +1574,34 @@ ip_setmoptions(int optname, struct ip_mo
                 * Drop a multicast group membership.
                 * Group must be a valid IP multicast address.
                 */
-               if (m == NULL || m->m_len != sizeof(struct ip_mreq)) {
+               if (m == NULL || !(m->m_len == sizeof(struct ip_mreq) ||
+                   m->m_len == sizeof(struct ip_mreqn))) {
                        error = EINVAL;
                        break;
                }
-               mreq = mtod(m, struct ip_mreq *);
-               if (!IN_MULTICAST(mreq->imr_multiaddr.s_addr)) {
+               memset(&mreqn, 0, sizeof(mreqn));
+               memcpy(&mreqn, mtod(m, void *), m->m_len);
+               if (!IN_MULTICAST(mreqn.imr_multiaddr.s_addr)) {
                        error = EINVAL;
                        break;
                }
+
                /*
                 * If an interface address was specified, get a pointer
                 * to its ifnet structure.
                 */
-               if (mreq->imr_interface.s_addr == INADDR_ANY)
-                       ifp = NULL;
-               else {
-                       memset(&sin, 0, sizeof(sin));
-                       sin.sin_len = sizeof(sin);
-                       sin.sin_family = AF_INET;
-                       sin.sin_addr = mreq->imr_interface;
-                       ia = ifatoia(ifa_ifwithaddr(sintosa(&sin), rtableid));
-                       if (ia == NULL) {
-                               error = EADDRNOTAVAIL;
-                               break;
-                       }
-                       ifp = ia->ia_ifp;
-               }
+               error = ip_multicast_if(&mreqn, rtableid, &ifidx);
+               if (error)
+                       break;
+
                /*
                 * Find the membership in the membership array.
                 */
                for (i = 0; i < imo->imo_num_memberships; ++i) {
-                       if ((ifp == NULL ||
-                           imo->imo_membership[i]->inm_ifidx ==
-                               ifp->if_index) &&
+                       if ((ifidx == 0 ||
+                           imo->imo_membership[i]->inm_ifidx == ifidx) &&
                             imo->imo_membership[i]->inm_addr.s_addr ==
-                            mreq->imr_multiaddr.s_addr)
+                            mreqn.imr_multiaddr.s_addr)
                                break;
                }
                if (i == imo->imo_num_memberships) {

Reply via email to