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;

Reply via email to