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