git: c8d4287e30d6 - main - mtest: Add support for exercising IPv4-mapped groups on IPv6 sockets.

Bruce M Simpson <[email protected]> Mon, 03 Aug 2026 15:47:40 +0000
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a70b81c.3e2d0.a134af1__28045.3677218006$1785772193$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by bms:

URL: https://cgit.FreeBSD.org/src/commit/?id=c8d4287e30d6dc2ed3d4378133758adabc0030a3

commit c8d4287e30d6dc2ed3d4378133758adabc0030a3
Author:     Bruce M Simpson <[email protected]>
AuthorDate: 2026-08-02 08:00:19 +0000
Commit:     Bruce M Simpson <[email protected]>
CommitDate: 2026-08-03 15:47:24 +0000

    mtest: Add support for exercising IPv4-mapped groups on IPv6 sockets.
    
    This is in lieu of a full Kyua/ATF regression test, as this is an optional
    feature that was beyond the scope of IETF's normative references for IPv6
    multicast; support has been strictly on a best-effort basis.
    
    Two new commands are added to mtest(8):
    u mcast-addr ifname - join IPv4-mapped group on IPv6 socket
    v mcast-addr ifname - leave IPv4-mapped group on IPv6 socket
    
    Add an internal helper function __in6_v4_to_v4mapped() to perform the
    converse of the IN6_IS_ADDR_V4MAPPED() check to support this use case.
    Whilst __in6_v4_to_v4mapped() returns its first argument as a convenience,
    avoid the temptation to dereference a pointer to that which we already hold.
    
    Strictly the use of sockunion_t within mtest(8) more generally is a form
    of controlled type punning (aliasing). Use a temporary as we overwrite
    contents of su; the resultant write would overlap memory locations.
    
    PR:             https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193246
    Differential Revision: https://reviews.freebsd.org/D58589
---
 usr.sbin/mtest/mtest.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/usr.sbin/mtest/mtest.c b/usr.sbin/mtest/mtest.c
index 941379b01bc9..e15068644043 100644
--- a/usr.sbin/mtest/mtest.c
+++ b/usr.sbin/mtest/mtest.c
@@ -104,6 +104,10 @@ typedef union mrequnion mrequnion_t;
 #ifdef INET
 static int	__ifindex_to_primary_ip(const uint32_t, struct in_addr *);
 #endif
+#if defined(INET) && defined(INET6)
+static struct in6_addr *
+		__in6_v4_to_v4mapped(struct in6_addr *, struct in_addr *);
+#endif
 static uint32_t	parse_cmd_args(sockunion_t *, sockunion_t *,
 		    const char *, const char *, const char *);
 static void	process_file(char *, int, int);
@@ -143,6 +147,23 @@ su_cmp(const void *a, const void *b)
 	return (memcmp(sua, sub, sua->sa.sa_len));
 }
 
+#if defined(INET) && defined(INET6)
+/*
+ * Internal: Express an IPv4 address within IPv6 as IPv4-mapped.
+ * Converse of IN6_IS_ADDR_V4MAPPED(a).
+ */
+static struct in6_addr *
+__in6_v4_to_v4mapped(struct in6_addr *a6 , struct in_addr *a4)
+{
+
+	a6->s6_addr32[0] = 0;
+	a6->s6_addr32[1] = 0;
+	a6->s6_addr32[2] = ntohl(0x0000ffff);
+	a6->s6_addr32[3] = a4->s_addr;
+	return (a6);
+}
+#endif /* defined(INET) && defined(INET6) */
+
 #ifdef INET
 /*
  * Internal: Map an interface index to primary IPv4 address.
@@ -518,6 +539,55 @@ process_cmd(char *cmd, int s, int s6, FILE *fp __unused)
 		printf("-1\n");
 		break;
 
+	/*
+	 * Exercise the non-IETF-ratified extension to RFC 3493 to
+	 * join IPv4 groups as IPv4 mapped addresses on IPv6 sockets.
+	 * Only ASM joins are supported; further use of the full-state API
+	 * may result in undefined behaviour.
+	 */
+	case 'u':
+	case 'v':
+		toptname = "";
+		sscanf(line, "%s %s", str1, str2);
+		ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
+		if (ifindex == 0) {
+			printf("-1\n");
+			break;
+		}
+		af = su.sa.sa_family;
+#if defined(INET) && defined(INET6)
+		if (af == AF_INET) {
+			struct in_addr	gaddr;
+
+			gaddr = su.sin.sin_addr;
+			__in6_v4_to_v4mapped(&su.sin6.sin6_addr, &gaddr);
+			mr.mr6.ipv6mr_multiaddr = su.sin6.sin6_addr;
+			mr.mr6.ipv6mr_interface = ifindex;
+			optname = (*cmd == 'u') ?
+			    IPV6_JOIN_GROUP :
+			    IPV6_LEAVE_GROUP;
+			toptname = (*cmd == 'u') ?
+			    "IPV6_JOIN_GROUP" :
+			    "IPV6_LEAVE_GROUP";
+			level = IPPROTO_IPV6;
+			optval = (void *)&mr.mr6;
+			optlen = sizeof(mr.mr6);
+			if (s6 < 0) {
+				warnc(EPROTONOSUPPORT, "setsockopt %s",
+				    toptname);
+			} else if (setsockopt(s6, level, optname, optval,
+			    optlen) == 0) {
+				printf("ok\n");
+				break;
+			} else {
+				warn("setsockopt %s", toptname);
+			}
+		}
+#endif /* defined(INET) && defined(INET6) */
+		/* FALLTHROUGH */
+		printf("-1\n");
+		break;
+
 	/*
 	 * Set the socket to include or exclude filter mode, and
 	 * add some sources to the filterlist, using the full-state API.
@@ -833,6 +903,8 @@ usage(void)
 
 	printf("j mcast-addr ifname [src-addr] - join IP multicast group\n");
 	printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n");
+	printf("u mcast-addr ifname - join IPv4-mapped group on IPv6 socket\n");
+	printf("v mcast-addr ifname - leave IPv4-mapped group on IPv6 socket\n");
 	printf(
 "i mcast-addr ifname n          - set n include mode src filter\n");
 	printf(