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