Support struct ip_mreqn as argument for IP_ADD_MEMBERSHIP. Legacy support

for struct ip_mreq remains in place.

The struct ip_mreqn is Linux extension to classic BSD multicast API. It
has extra field allowing to specify the interface index explicitly. In
Linux it used as argument for IP_MULTICAST_IF and IP_ADD_MEMBERSHIP.
FreeBSD kernel also declares this structure and supports it as argument
to IP_MULTICAST_IF since r170613. So, we have structure declared but
not fully supported, this confused third party application configure
scripts.

Code handling IP_ADD_MEMBERSHIP was mixed together with code for
IP_ADD_SOURCE_MEMBERSHIP.  Bringing legacy and new structure support
into the mess would made the "argument switcharoo" intolerable, so
code was separated into its own switch case clause.

MFC after:	3 months
Differential Revision:	https://reviews.freebsd.org/D19276
This commit is contained in:
Gleb Smirnoff 2019-02-23 06:03:18 +00:00
parent e806165bee
commit 0dfc145abe
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=344481
2 changed files with 74 additions and 42 deletions

View File

@ -28,7 +28,7 @@
.\" @(#)ip.4 8.2 (Berkeley) 11/30/93
.\" $FreeBSD$
.\"
.Dd August 19, 2018
.Dd February 22, 2019
.Dt IP 4
.Os
.Sh NAME
@ -571,32 +571,55 @@ To join a multicast group, use the
.Dv IP_ADD_MEMBERSHIP
option:
.Bd -literal
struct ip_mreq mreq;
setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
struct ip_mreqn mreqn;
setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn));
.Ed
.Pp
where
.Fa mreq
.Fa mreqn
is the following structure:
.Bd -literal
struct ip_mreq {
struct ip_mreqn {
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
int imr_ifindex; /* interface index */
}
.Ed
.Pp
.Va imr_interface
should be set to the
.Tn IP
address of a particular multicast-capable interface if
.Va imr_ifindex
should be set to the index of a particular multicast-capable interface if
the host is multihomed.
It may be set to
.Dv INADDR_ANY
to choose the default interface, although this is not recommended;
this is considered to be the first interface corresponding
to the default route.
Otherwise, the first multicast-capable interface
configured in the system will be used.
If
.Va imr_ifindex
is non-zero, value of
.Va imr_interface
is ignored.
Otherwise, if
.Va imr_ifindex
is 0, kernel will use IP address from
.Va imr_interface
to lookup the interface.
Value of
.Va imr_interface
may be set to
.Va INADDR_ANY
to choose the default interface, although this is not recommended; this is
considered to be the first interface corresponding to the default route.
Otherwise, the first multicast-capable interface configured in the system
will be used.
.Pp
Legacy
.Vt "struct ip_mreq" ,
that lacks
.Va imr_ifindex
field is also supported by
.Dv IP_ADD_MEMBERSHIP
setsockopt.
In this case kernel would behave as if
.Va imr_ifindex
was set to zero:
.Va imr_interface
will be used to lookup interface.
.Pp
Prior to
.Fx 7.0 ,

View File

@ -2049,41 +2049,50 @@ inp_join_group(struct inpcb *inp, struct sockopt *sopt)
ssa->ss.ss_family = AF_UNSPEC;
switch (sopt->sopt_name) {
case IP_ADD_MEMBERSHIP:
case IP_ADD_SOURCE_MEMBERSHIP: {
struct ip_mreq_source mreqs;
case IP_ADD_MEMBERSHIP: {
struct ip_mreqn mreqn;
if (sopt->sopt_name == IP_ADD_MEMBERSHIP) {
error = sooptcopyin(sopt, &mreqs,
sizeof(struct ip_mreq),
sizeof(struct ip_mreq));
/*
* Do argument switcharoo from ip_mreq into
* ip_mreq_source to avoid using two instances.
*/
mreqs.imr_interface = mreqs.imr_sourceaddr;
mreqs.imr_sourceaddr.s_addr = INADDR_ANY;
} else if (sopt->sopt_name == IP_ADD_SOURCE_MEMBERSHIP) {
error = sooptcopyin(sopt, &mreqs,
sizeof(struct ip_mreq_source),
sizeof(struct ip_mreq_source));
}
if (sopt->sopt_valsize == sizeof(struct ip_mreqn))
error = sooptcopyin(sopt, &mreqn,
sizeof(struct ip_mreqn), sizeof(struct ip_mreqn));
else
error = sooptcopyin(sopt, &mreqn,
sizeof(struct ip_mreq), sizeof(struct ip_mreq));
if (error)
return (error);
gsa->sin.sin_family = AF_INET;
gsa->sin.sin_len = sizeof(struct sockaddr_in);
gsa->sin.sin_addr = mreqs.imr_multiaddr;
if (sopt->sopt_name == IP_ADD_SOURCE_MEMBERSHIP) {
ssa->sin.sin_family = AF_INET;
ssa->sin.sin_len = sizeof(struct sockaddr_in);
ssa->sin.sin_addr = mreqs.imr_sourceaddr;
}
gsa->sin.sin_addr = mreqn.imr_multiaddr;
if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
return (EINVAL);
if (sopt->sopt_valsize == sizeof(struct ip_mreqn) &&
mreqn.imr_ifindex != 0)
ifp = ifnet_byindex(mreqn.imr_ifindex);
else
ifp = inp_lookup_mcast_ifp(inp, &gsa->sin,
mreqn.imr_address);
break;
}
case IP_ADD_SOURCE_MEMBERSHIP: {
struct ip_mreq_source mreqs;
error = sooptcopyin(sopt, &mreqs, sizeof(struct ip_mreq_source),
sizeof(struct ip_mreq_source));
if (error)
return (error);
gsa->sin.sin_family = ssa->sin.sin_family = AF_INET;
gsa->sin.sin_len = ssa->sin.sin_len =
sizeof(struct sockaddr_in);
gsa->sin.sin_addr = mreqs.imr_multiaddr;
if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
return (EINVAL);
ssa->sin.sin_addr = mreqs.imr_sourceaddr;
ifp = inp_lookup_mcast_ifp(inp, &gsa->sin,
mreqs.imr_interface);
CTR3(KTR_IGMPV3, "%s: imr_interface = 0x%08x, ifp = %p",