git: f9cdaeeeb9ad - main - netinet6: Pass IPv4-mapped ASM multicast joins/leaves to netinet.
Bruce M Simpson <[email protected]> Mon, 03 Aug 2026 11:28:38 +0000
| Newsgroups | gmane.os.freebsd.devel.cvs.src |
|---|---|
| Message-ID | <6a707b66.1864b.48d16eee__27948.3712207124$1785756567$gmane$org@gitrepo.freebsd.org> |
The branch main has been updated by bms: URL: https://cgit.FreeBSD.org/src/commit/?id=f9cdaeeeb9ad45a5e8c0cd32ff9443d245b64185 commit f9cdaeeeb9ad45a5e8c0cd32ff9443d245b64185 Author: Bruce M Simpson <[email protected]> AuthorDate: 2026-08-01 10:14:57 +0000 Commit: Bruce M Simpson <[email protected]> CommitDate: 2026-08-03 11:27:45 +0000 netinet6: Pass IPv4-mapped ASM multicast joins/leaves to netinet. Add support for allowing IPv4 multicast groups to be joined on IPv6 sockets, as a number of applications began to rely on this over the years, despite it only ever having been a convenience which appeared in Solaris & Linux over the course of the 00s decade. It is limited to any-source joins (ASM). To avoid further quibbling over the meaning of the term "undocumented" as it applies to this change, I have chosen to use the wording "non-IETF-ratified extension" in comments, with reference to the updated ip6(4) man page. PR: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193246 --- sys/netinet/in_mcast.c | 6 +-- sys/netinet/in_var.h | 5 +++ sys/netinet6/in6_mcast.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c index 934cc71e745b..ad908b72c340 100644 --- a/sys/netinet/in_mcast.c +++ b/sys/netinet/in_mcast.c @@ -158,8 +158,6 @@ static void inm_release(struct in_multi *); static struct ip_moptions * inp_findmoptions(struct inpcb *); static int inp_get_source_filters(struct inpcb *, struct sockopt *); -static int inp_join_group(struct inpcb *, struct sockopt *); -static int inp_leave_group(struct inpcb *, struct sockopt *); static int inp_block_unblock_source(struct inpcb *, struct sockopt *); static int inp_set_multicast_if(struct inpcb *, struct sockopt *); static int inp_set_source_filters(struct inpcb *, struct sockopt *); @@ -1884,7 +1882,7 @@ const struct in_addr *ina, const u_int index) /* * Join an IPv4 multicast group, possibly with a source. */ -static int +int inp_join_group(struct inpcb *inp, struct sockopt *sopt) { struct group_source_req gsr; @@ -2208,7 +2206,7 @@ out_inp_unlocked: /* * Leave an IPv4 multicast group on an inpcb, possibly with a source. */ -static int +int inp_leave_group(struct inpcb *inp, struct sockopt *sopt) { struct epoch_tracker et; diff --git a/sys/netinet/in_var.h b/sys/netinet/in_var.h index 99a628477155..e9db1efd9fe9 100644 --- a/sys/netinet/in_var.h +++ b/sys/netinet/in_var.h @@ -459,6 +459,11 @@ void in_ifattach(void *, struct ifnet *); void in_detachhead(struct rib_head *rh); #endif +struct sockopt; + +int inp_join_group(struct inpcb *, struct sockopt *); +int inp_leave_group(struct inpcb *, struct sockopt *); + #endif /* _KERNEL */ /* INET6 stuff */ diff --git a/sys/netinet6/in6_mcast.c b/sys/netinet6/in6_mcast.c index fad47cb0e69b..a204ad88a9a5 100644 --- a/sys/netinet6/in6_mcast.c +++ b/sys/netinet6/in6_mcast.c @@ -1,7 +1,7 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * - * Copyright (c) 2009 Bruce Simpson. + * Copyright (c) 2009-2026 Bruce Simpson. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ * Normative references: RFC 2292, RFC 3492, RFC 3542, RFC 3678, RFC 3810. */ +#include "opt_inet.h" #include "opt_inet6.h" #include <sys/param.h> @@ -162,6 +163,9 @@ static struct ifnet * static int in6p_block_unblock_source(struct inpcb *, struct sockopt *); static int in6p_set_multicast_if(struct inpcb *, struct sockopt *); static int in6p_set_source_filters(struct inpcb *, struct sockopt *); +#ifdef INET +static int in6_v6_mreq_to_v4(struct ipv6_mreq *, struct ip_mreq *); +#endif static int sysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS); SYSCTL_DECL(_net_inet6_ip6); /* XXX Not in any common header. */ @@ -1881,6 +1885,54 @@ in6p_lookup_mcast_ifp(const struct inpcb *inp, const struct sockaddr_in6 *gsin6) return (nh ? nh->nh_ifp : NULL); } +#ifdef INET +/* + * Perform sockopt mreq argument conversion for IPv4-mapped groups. + * + * This function is required to support an extension to the behaviour + * in RFC 3493 Sec 3.7, which was never formally proposed by any + * contemporary IPv6 normative reference, but which is now required + * by much application software using IPv6 sockets as a convenience. + * Refer to manual page ip6(4) for further information. + * + * FUTURE: Use IPv4 source-address selection. + */ +static int +in6_v6_mreq_to_v4(struct ipv6_mreq *mreq, struct ip_mreq *mreq_v4) +{ + int error; + struct epoch_tracker et; + struct ifnet *ifp; + struct in_ifaddr *ia; + + NET_EPOCH_ENTER(et); + + ifp = ifnet_byindex(mreq->ipv6mr_interface); + if (ifp == NULL) { + error = EADDRNOTAVAIL; + goto out; + } + + /* + * Here, we do not compare the ifnet's primary IPv4 address with + * INADDR_ANY, to permit its use during system initialization. + * If this is not required, an appropriate check to screen it out + * should be added, e.g. in_nullhost(ia->ia_addr.sin_addr.s_addr). + */ + ia = in_ifprimaryaddr(ifp); + if (ia == NULL) { + error = EADDRNOTAVAIL; + goto out; + } + mreq_v4->imr_interface.s_addr = IA_SIN(ia)->sin_addr.s_addr; + error = 0; + +out: + NET_EPOCH_EXIT(et); + return (error); +} +#endif /* INET */ + /* * Join an IPv6 multicast group, possibly with a source. * @@ -1929,6 +1981,35 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt) sizeof(struct ipv6_mreq)); if (error) return (error); +#ifdef INET + /* + * Support for the non-IETF-ratified extension to RFC 3493 to + * join IPv4 groups as IPv4 mapped addresses on IPv6 sockets. + */ + if (IN6_IS_ADDR_V4MAPPED(&mreq.ipv6mr_multiaddr)) { + struct ip_mreq mreq_v4; + struct sockopt sopt_v4 = { + .sopt_dir = SOPT_SET, + .sopt_level = sopt->sopt_level, + .sopt_name = IP_ADD_MEMBERSHIP, + .sopt_val = &mreq_v4, + .sopt_valsize = sizeof(mreq_v4), + .sopt_rights = sopt->sopt_rights, + .sopt_td = sopt->sopt_td + }; + + mreq_v4.imr_multiaddr.s_addr = + mreq.ipv6mr_multiaddr.s6_addr32[3]; + if (mreq.ipv6mr_interface == 0) + mreq_v4.imr_interface.s_addr = INADDR_ANY; + else + error = in6_v6_mreq_to_v4(&mreq, &mreq_v4); + if (error) + return error; + + return (inp_join_group(inp, &sopt_v4)); + } +#endif /* INET */ gsa->sin6.sin6_family = AF_INET6; gsa->sin6.sin6_len = sizeof(struct sockaddr_in6); @@ -2242,6 +2323,35 @@ in6p_leave_group(struct inpcb *inp, struct sockopt *sopt) sizeof(struct ipv6_mreq)); if (error) return (error); +#ifdef INET + /* + * Support for the non-IETF-ratified extension to RFC 3493 to + * leave IPv4 groups as IPv4 mapped addresses on IPv6 sockets. + */ + if (IN6_IS_ADDR_V4MAPPED(&mreq.ipv6mr_multiaddr)) { + struct ip_mreq mreq_v4; + struct sockopt sopt_v4 = { + .sopt_dir = SOPT_SET, + .sopt_level = sopt->sopt_level, + .sopt_name = IP_DROP_MEMBERSHIP, + .sopt_val = &mreq_v4, + .sopt_valsize = sizeof(mreq_v4), + .sopt_rights = sopt->sopt_rights, + .sopt_td = sopt->sopt_td + }; + + mreq_v4.imr_multiaddr.s_addr = + mreq.ipv6mr_multiaddr.s6_addr32[3]; + if (mreq.ipv6mr_interface == 0) + mreq_v4.imr_interface.s_addr = INADDR_ANY; + else + error = in6_v6_mreq_to_v4(&mreq, &mreq_v4); + if (error) + return error; + + return (inp_leave_group(inp, &sopt_v4)); + } +#endif /* INET */ gsa->sin6.sin6_family = AF_INET6; gsa->sin6.sin6_len = sizeof(struct sockaddr_in6); gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;