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) {