From 0dfc145abe244443bb38ee81617462992bf21cf2 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Sat, 23 Feb 2019 06:03:18 +0000 Subject: [PATCH] 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 --- share/man/man4/ip.4 | 55 ++++++++++++++++++++++++++----------- sys/netinet/in_mcast.c | 61 ++++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/share/man/man4/ip.4 b/share/man/man4/ip.4 index 555aca1e36df..bb92dd011c4d 100644 --- a/share/man/man4/ip.4 +++ b/share/man/man4/ip.4 @@ -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 , diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c index 3a38c421b0cc..ab48fed348e6 100644 --- a/sys/netinet/in_mcast.c +++ b/sys/netinet/in_mcast.c @@ -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",