On Wed, Jan 06, 2021 at 10:27:42AM +0100, Claudio Jeker wrote:
> 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).
> 

This diff is better. I removed the change to in_addmulti() from the
previous version. There is no benefit of passing the interface index to
in_addmulti (instead of the ifp). in_addmulti() needs the ifp (not just
only the index) and it just adds a lot of unneccessary change to the diff.

-- 
:wq Claudio

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/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 7 Jan 2021 11:14:37 -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,63 +1484,41 @@ 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;
                }
+
                /*
                 * 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) {
@@ -1506,9 +1530,10 @@ ip_setmoptions(int optname, struct ip_mo
                        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;
@@ -1538,7 +1563,7 @@ 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, ifp)) == NULL) {
                        error = ENOBUFS;
                        if_put(ifp);
                        break;
@@ -1552,42 +1577,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