This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new 385ae4294 testing/nettest: Add a cmocka test for the RFC 3678
multicast options.
385ae4294 is described below
commit 385ae4294e6181f9a5d21d2f8593a21676f4c108
Author: Marco Casaroli <[email protected]>
AuthorDate: Thu Oct 29 17:33:55 2026 +0100
testing/nettest: Add a cmocka test for the RFC 3678 multicast options.
Add cmocka_net_mcast. It tests MCAST_JOIN_GROUP and MCAST_LEAVE_GROUP
on the loopback device, for IPv4 (IGMP) and for IPv6 (MLD):
- After the join, a datagram to the group arrives. The sender sets
IP_MULTICAST_LOOP or IPV6_MULTICAST_LOOP.
- After the leave, no datagram to the group arrives. This part is for
IPv4 only: IPv6 input accepts all multicast groups.
- A second leave fails with the same errno as a second
IP_DROP_MEMBERSHIP or IPV6_LEAVE_GROUP.
- A short option length gives EINVAL. A group of the wrong address
family gives EINVAL (IPv4) or EADDRNOTAVAIL (IPv6), as on Linux.
- MCAST_JOIN_SOURCE_GROUP gives ENOSYS.
The test also checks that the MCAST_* values differ from all IP_* and
IPV6_* options.
Enable it with CONFIG_TESTING_NET_MCAST. It needs NET_UDP,
NET_SOCKOPTS, NET_LOOPBACK, and NET_IGMP or NET_MLD.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <[email protected]>
---
testing/nettest/CMakeLists.txt | 27 ++++
testing/nettest/Kconfig | 7 +
testing/nettest/Makefile | 15 ++
testing/nettest/mcast/test_mcast.c | 84 ++++++++++
testing/nettest/mcast/test_mcast.h | 169 ++++++++++++++++++++
testing/nettest/mcast/test_mcast_common.c | 192 ++++++++++++++++++++++
testing/nettest/mcast/test_mcast_ipv4.c | 254 ++++++++++++++++++++++++++++++
testing/nettest/mcast/test_mcast_ipv6.c | 241 ++++++++++++++++++++++++++++
8 files changed, 989 insertions(+)
diff --git a/testing/nettest/CMakeLists.txt b/testing/nettest/CMakeLists.txt
index 87dd889c5..9ee84b486 100644
--- a/testing/nettest/CMakeLists.txt
+++ b/testing/nettest/CMakeLists.txt
@@ -97,6 +97,33 @@ if(CONFIG_TESTING_NET_TEST)
${SRCS})
endif()
+ if(CONFIG_TESTING_NET_MCAST)
+ set(SRCS ${CMAKE_CURRENT_LIST_DIR}/mcast/test_mcast.c
+ ${CMAKE_CURRENT_LIST_DIR}/mcast/test_mcast_common.c)
+
+ if(CONFIG_NET_IGMP)
+ list(APPEND SRCS ${CMAKE_CURRENT_LIST_DIR}/mcast/test_mcast_ipv4.c)
+ endif()
+
+ if(CONFIG_NET_MLD)
+ list(APPEND SRCS ${CMAKE_CURRENT_LIST_DIR}/mcast/test_mcast_ipv6.c)
+ endif()
+
+ nuttx_add_application(
+ NAME
+ cmocka_net_mcast
+ PRIORITY
+ ${CONFIG_TESTING_NET_TEST_PRIORITY}
+ STACKSIZE
+ ${CONFIG_TESTING_NET_TEST_STACKSIZE}
+ MODULE
+ ${CONFIG_TESTING_NET_TEST}
+ DEPENDS
+ cmocka
+ SRCS
+ ${SRCS})
+ endif()
+
if(CONFIG_TESTING_NET_TIMESTAMP)
set(SRCS ${CMAKE_CURRENT_LIST_DIR}/timestamp/test_timestamp.c
${CMAKE_CURRENT_LIST_DIR}/timestamp/test_timestamp_common.c)
diff --git a/testing/nettest/Kconfig b/testing/nettest/Kconfig
index d6d6e6a6d..23171814f 100644
--- a/testing/nettest/Kconfig
+++ b/testing/nettest/Kconfig
@@ -44,6 +44,13 @@ config TESTING_NET_OTHERS
bool "Enable cmocka net other test"
default y
+config TESTING_NET_MCAST
+ bool "Enable cmocka net multicast test"
+ depends on NET_UDP && !NET_UDP_NO_STACK
+ depends on NET_SOCKOPTS && NET_LOOPBACK
+ depends on NET_IGMP || NET_MLD
+ default y
+
config TESTING_NET_TIMESTAMP
bool "Enable cmocka net timestamp test"
depends on NET_TIMESTAMP && NET_PKT
diff --git a/testing/nettest/Makefile b/testing/nettest/Makefile
index 7da23be0e..fb5f5e5f7 100644
--- a/testing/nettest/Makefile
+++ b/testing/nettest/Makefile
@@ -58,6 +58,21 @@ CSRCS += others/test_others_common.c
others/test_others_bufpool.c \
others/test_others_chksum.c
endif
+ifeq ($(CONFIG_TESTING_NET_MCAST),y)
+MAINSRC += mcast/test_mcast.c
+PROGNAME += cmocka_net_mcast
+CSRCS += mcast/test_mcast_common.c
+
+ifeq ($(CONFIG_NET_IGMP),y)
+CSRCS += mcast/test_mcast_ipv4.c
+endif
+
+ifeq ($(CONFIG_NET_MLD),y)
+CSRCS += mcast/test_mcast_ipv6.c
+endif
+
+endif
+
ifeq ($(CONFIG_TESTING_NET_TIMESTAMP),y)
MAINSRC += timestamp/test_timestamp.c
PROGNAME += cmocka_net_timestamp
diff --git a/testing/nettest/mcast/test_mcast.c
b/testing/nettest/mcast/test_mcast.c
new file mode 100644
index 000000000..a0a78aebd
--- /dev/null
+++ b/testing/nettest/mcast/test_mcast.c
@@ -0,0 +1,84 @@
+/****************************************************************************
+ * apps/testing/nettest/mcast/test_mcast.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include "test_mcast.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+ const struct CMUnitTest mcast_tests[] =
+ {
+ cmocka_unit_test(test_mcast_names),
+#ifdef CONFIG_NET_IGMP
+ cmocka_unit_test_setup_teardown(test_mcast_join_ipv4,
+ test_mcast_ipv4_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_leave_ipv4,
+ test_mcast_ipv4_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_optlen_ipv4,
+ test_mcast_ipv4_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_family_ipv4,
+ test_mcast_ipv4_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_source_ipv4,
+ test_mcast_ipv4_setup,
+ test_mcast_common_teardown),
+#endif
+#ifdef CONFIG_NET_MLD
+ cmocka_unit_test_setup_teardown(test_mcast_join_ipv6,
+ test_mcast_ipv6_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_leave_ipv6,
+ test_mcast_ipv6_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_optlen_ipv6,
+ test_mcast_ipv6_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_family_ipv6,
+ test_mcast_ipv6_setup,
+ test_mcast_common_teardown),
+ cmocka_unit_test_setup_teardown(test_mcast_source_ipv6,
+ test_mcast_ipv6_setup,
+ test_mcast_common_teardown),
+#endif
+ };
+
+ return cmocka_run_group_tests(mcast_tests, test_mcast_group_setup,
+ test_mcast_group_teardown);
+}
diff --git a/testing/nettest/mcast/test_mcast.h
b/testing/nettest/mcast/test_mcast.h
new file mode 100644
index 000000000..1b121e377
--- /dev/null
+++ b/testing/nettest/mcast/test_mcast.h
@@ -0,0 +1,169 @@
+/****************************************************************************
+ * apps/testing/nettest/mcast/test_mcast.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_TESTING_NETTEST_MCAST_TEST_MCAST_H
+#define __APPS_TESTING_NETTEST_MCAST_TEST_MCAST_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdbool.h>
+#include <sys/socket.h>
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct nettest_mcast_state_s
+{
+ unsigned int ifindex; /* Index of "lo" */
+ int rfd; /* Receiver, bound to the group port */
+ int sfd; /* Sender, bound to the loopback address */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_group_setup
+ ****************************************************************************/
+
+int test_mcast_group_setup(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_group_teardown
+ ****************************************************************************/
+
+int test_mcast_group_teardown(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_common_teardown
+ ****************************************************************************/
+
+int test_mcast_common_teardown(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_send
+ ****************************************************************************/
+
+void test_mcast_send(int fd, FAR const struct sockaddr *addr,
+ socklen_t addrlen);
+
+/****************************************************************************
+ * Name: test_mcast_recv
+ *
+ * Description:
+ * Return true if the test message arrives on fd within one second.
+ *
+ ****************************************************************************/
+
+bool test_mcast_recv(int fd);
+
+/****************************************************************************
+ * Name: test_mcast_names
+ ****************************************************************************/
+
+void test_mcast_names(FAR void **state);
+
+#ifdef CONFIG_NET_IGMP
+
+/****************************************************************************
+ * Name: test_mcast_ipv4_setup
+ ****************************************************************************/
+
+int test_mcast_ipv4_setup(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_join_ipv4
+ ****************************************************************************/
+
+void test_mcast_join_ipv4(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_leave_ipv4
+ ****************************************************************************/
+
+void test_mcast_leave_ipv4(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_optlen_ipv4
+ ****************************************************************************/
+
+void test_mcast_optlen_ipv4(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_family_ipv4
+ ****************************************************************************/
+
+void test_mcast_family_ipv4(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_source_ipv4
+ ****************************************************************************/
+
+void test_mcast_source_ipv4(FAR void **state);
+#endif
+
+#ifdef CONFIG_NET_MLD
+
+/****************************************************************************
+ * Name: test_mcast_ipv6_setup
+ ****************************************************************************/
+
+int test_mcast_ipv6_setup(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_join_ipv6
+ ****************************************************************************/
+
+void test_mcast_join_ipv6(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_leave_ipv6
+ ****************************************************************************/
+
+void test_mcast_leave_ipv6(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_optlen_ipv6
+ ****************************************************************************/
+
+void test_mcast_optlen_ipv6(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_family_ipv6
+ ****************************************************************************/
+
+void test_mcast_family_ipv6(FAR void **state);
+
+/****************************************************************************
+ * Name: test_mcast_source_ipv6
+ ****************************************************************************/
+
+void test_mcast_source_ipv6(FAR void **state);
+#endif
+
+#endif /* __APPS_TESTING_NETTEST_MCAST_TEST_MCAST_H */
diff --git a/testing/nettest/mcast/test_mcast_common.c
b/testing/nettest/mcast/test_mcast_common.c
new file mode 100644
index 000000000..31bcc6891
--- /dev/null
+++ b/testing/nettest/mcast/test_mcast_common.c
@@ -0,0 +1,192 @@
+/****************************************************************************
+ * apps/testing/nettest/mcast/test_mcast_common.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+#include "test_mcast.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define TEST_MSG "multicast message"
+#define TEST_TIMEOUT 1000 /* Milliseconds */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_group_setup
+ ****************************************************************************/
+
+int test_mcast_group_setup(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state;
+
+ mcast_state = zalloc(sizeof(*mcast_state));
+ assert_non_null(mcast_state);
+
+ mcast_state->ifindex = if_nametoindex("lo");
+ mcast_state->rfd = -1;
+ mcast_state->sfd = -1;
+ assert_int_not_equal(mcast_state->ifindex, 0);
+
+ *state = mcast_state;
+ return 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_group_teardown
+ ****************************************************************************/
+
+int test_mcast_group_teardown(FAR void **state)
+{
+ free(*state);
+ return 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_common_teardown
+ ****************************************************************************/
+
+int test_mcast_common_teardown(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+
+ if (mcast_state->rfd >= 0)
+ {
+ close(mcast_state->rfd);
+ mcast_state->rfd = -1;
+ }
+
+ if (mcast_state->sfd >= 0)
+ {
+ close(mcast_state->sfd);
+ mcast_state->sfd = -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_send
+ ****************************************************************************/
+
+void test_mcast_send(int fd, FAR const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ ssize_t ret;
+
+ ret = sendto(fd, TEST_MSG, strlen(TEST_MSG), 0, addr, addrlen);
+ assert_int_equal(ret, strlen(TEST_MSG));
+}
+
+/****************************************************************************
+ * Name: test_mcast_recv
+ ****************************************************************************/
+
+bool test_mcast_recv(int fd)
+{
+ struct pollfd pfd;
+ char buf[32];
+ ssize_t ret;
+
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if (poll(&pfd, 1, TEST_TIMEOUT) != 1)
+ {
+ return false;
+ }
+
+ ret = recv(fd, buf, sizeof(buf), 0);
+ return ret == (ssize_t)strlen(TEST_MSG) &&
+ memcmp(buf, TEST_MSG, ret) == 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_names
+ *
+ * Description:
+ * The MCAST_* options work at the IPPROTO_IP and the IPPROTO_IPV6 level,
+ * so their values must differ from all IP_* and IPV6_* options.
+ *
+ ****************************************************************************/
+
+void test_mcast_names(FAR void **state)
+{
+ static const int mcast[] =
+ {
+ MCAST_JOIN_GROUP, MCAST_BLOCK_SOURCE, MCAST_UNBLOCK_SOURCE,
+ MCAST_LEAVE_GROUP, MCAST_JOIN_SOURCE_GROUP, MCAST_LEAVE_SOURCE_GROUP
+ };
+
+ static const int other[] =
+ {
+ IP_MULTICAST_IF, IP_MULTICAST_TTL, IP_MULTICAST_LOOP,
+ IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_UNBLOCK_SOURCE,
+ IP_BLOCK_SOURCE, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP,
+ IP_MSFILTER, IP_MULTICAST_ALL, IP_PKTINFO, IP_TOS, IP_TTL,
+ IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP, IPV6_MULTICAST_HOPS,
+ IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS,
+ IPV6_V6ONLY, IPV6_PKTINFO, IPV6_RECVPKTINFO, IPV6_TCLASS,
+ IPV6_RECVHOPLIMIT, IPV6_HOPLIMIT
+ };
+
+ struct group_source_req gsr;
+ struct group_req gr;
+ size_t i;
+ size_t j;
+
+ assert_true(sizeof(gr.gr_group) == sizeof(struct sockaddr_storage));
+ assert_true(sizeof(gsr.gsr_group) == sizeof(struct sockaddr_storage));
+ assert_true(sizeof(gsr.gsr_source) == sizeof(struct sockaddr_storage));
+
+ for (i = 0; i < nitems(mcast); i++)
+ {
+ for (j = i + 1; j < nitems(mcast); j++)
+ {
+ assert_int_not_equal(mcast[i], mcast[j]);
+ }
+
+ for (j = 0; j < nitems(other); j++)
+ {
+ assert_int_not_equal(mcast[i], other[j]);
+ }
+ }
+}
diff --git a/testing/nettest/mcast/test_mcast_ipv4.c
b/testing/nettest/mcast/test_mcast_ipv4.c
new file mode 100644
index 000000000..16bf91d43
--- /dev/null
+++ b/testing/nettest/mcast/test_mcast_ipv4.c
@@ -0,0 +1,254 @@
+/****************************************************************************
+ * apps/testing/nettest/mcast/test_mcast_ipv4.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <cmocka.h>
+
+#include "test_mcast.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define TEST_GROUP "239.1.2.3"
+#define TEST_PORT 5472
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_ipv4_group
+ ****************************************************************************/
+
+static void test_mcast_ipv4_group(FAR struct group_req *gr,
+ unsigned int ifindex)
+{
+ FAR struct sockaddr_in *group = (FAR struct sockaddr_in *)&gr->gr_group;
+
+ memset(gr, 0, sizeof(*gr));
+ gr->gr_interface = ifindex;
+ group->sin_family = AF_INET;
+ inet_pton(AF_INET, TEST_GROUP, &group->sin_addr);
+}
+
+/****************************************************************************
+ * Name: test_mcast_ipv4_send
+ ****************************************************************************/
+
+static void test_mcast_ipv4_send(int fd)
+{
+ struct sockaddr_in addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(TEST_PORT);
+ inet_pton(AF_INET, TEST_GROUP, &addr.sin_addr);
+ test_mcast_send(fd, (FAR struct sockaddr *)&addr, sizeof(addr));
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_ipv4_setup
+ ****************************************************************************/
+
+int test_mcast_ipv4_setup(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct sockaddr_in addr;
+ int one = 1;
+ int ret;
+
+ mcast_state->rfd = socket(AF_INET, SOCK_DGRAM, 0);
+ assert_true(mcast_state->rfd >= 0);
+ mcast_state->sfd = socket(AF_INET, SOCK_DGRAM, 0);
+ assert_true(mcast_state->sfd >= 0);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(TEST_PORT);
+ ret = bind(mcast_state->rfd, (FAR struct sockaddr *)&addr, sizeof(addr));
+ assert_return_code(ret, errno);
+
+ /* Send from 127.0.0.1 on "lo". IP_MULTICAST_LOOP gives the datagram to
+ * the local sockets.
+ */
+
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ ret = bind(mcast_state->sfd, (FAR struct sockaddr *)&addr, sizeof(addr));
+ assert_return_code(ret, errno);
+
+ ret = setsockopt(mcast_state->sfd, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &one, sizeof(one));
+ assert_return_code(ret, errno);
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_join_ipv4
+ *
+ * Description:
+ * MCAST_JOIN_GROUP joins the group: a datagram to the group arrives.
+ *
+ ****************************************************************************/
+
+void test_mcast_join_ipv4(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv4_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+
+ test_mcast_ipv4_send(mcast_state->sfd);
+ assert_true(test_mcast_recv(mcast_state->rfd));
+
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+}
+
+/****************************************************************************
+ * Name: test_mcast_leave_ipv4
+ *
+ * Description:
+ * After MCAST_LEAVE_GROUP no datagram to the group arrives. A second
+ * leave fails as a second IP_DROP_MEMBERSHIP.
+ *
+ ****************************************************************************/
+
+void test_mcast_leave_ipv4(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct ip_mreq mreq;
+ struct group_req gr;
+ int err;
+ int ret;
+
+ test_mcast_ipv4_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+
+ test_mcast_ipv4_send(mcast_state->sfd);
+ assert_false(test_mcast_recv(mcast_state->rfd));
+
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_int_equal(ret, -1);
+ err = errno;
+
+ inet_pton(AF_INET, TEST_GROUP, &mreq.imr_multiaddr);
+ mreq.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, err);
+}
+
+/****************************************************************************
+ * Name: test_mcast_optlen_ipv4
+ ****************************************************************************/
+
+void test_mcast_optlen_ipv4(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv4_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr) - 1);
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, EINVAL);
+}
+
+/****************************************************************************
+ * Name: test_mcast_family_ipv4
+ ****************************************************************************/
+
+void test_mcast_family_ipv4(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv4_group(&gr, mcast_state->ifindex);
+ gr.gr_group.ss_family = AF_INET6;
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, EINVAL);
+}
+
+/****************************************************************************
+ * Name: test_mcast_source_ipv4
+ *
+ * Description:
+ * IGMPv2 has no source filter: MCAST_JOIN_SOURCE_GROUP fails.
+ *
+ ****************************************************************************/
+
+void test_mcast_source_ipv4(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_source_req gsr;
+ int ret;
+
+ memset(&gsr, 0, sizeof(gsr));
+ gsr.gsr_interface = mcast_state->ifindex;
+ gsr.gsr_group.ss_family = AF_INET;
+ gsr.gsr_source.ss_family = AF_INET;
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IP, MCAST_JOIN_SOURCE_GROUP,
+ &gsr, sizeof(gsr));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, ENOSYS);
+}
diff --git a/testing/nettest/mcast/test_mcast_ipv6.c
b/testing/nettest/mcast/test_mcast_ipv6.c
new file mode 100644
index 000000000..b725de60d
--- /dev/null
+++ b/testing/nettest/mcast/test_mcast_ipv6.c
@@ -0,0 +1,241 @@
+/****************************************************************************
+ * apps/testing/nettest/mcast/test_mcast_ipv6.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <cmocka.h>
+
+#include "test_mcast.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define TEST_GROUP "ff02::123"
+#define TEST_PORT 5473
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_ipv6_group
+ ****************************************************************************/
+
+static void test_mcast_ipv6_group(FAR struct group_req *gr,
+ unsigned int ifindex)
+{
+ FAR struct sockaddr_in6 *group = (FAR struct sockaddr_in6 *)&gr->gr_group;
+
+ memset(gr, 0, sizeof(*gr));
+ gr->gr_interface = ifindex;
+ group->sin6_family = AF_INET6;
+ inet_pton(AF_INET6, TEST_GROUP, &group->sin6_addr);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: test_mcast_ipv6_setup
+ ****************************************************************************/
+
+int test_mcast_ipv6_setup(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct sockaddr_in6 addr;
+ int one = 1;
+ int ret;
+
+ mcast_state->rfd = socket(AF_INET6, SOCK_DGRAM, 0);
+ assert_true(mcast_state->rfd >= 0);
+ mcast_state->sfd = socket(AF_INET6, SOCK_DGRAM, 0);
+ assert_true(mcast_state->sfd >= 0);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(TEST_PORT);
+ ret = bind(mcast_state->rfd, (FAR struct sockaddr *)&addr, sizeof(addr));
+ assert_return_code(ret, errno);
+
+ /* Send from ::1 on "lo". IPV6_MULTICAST_LOOP gives the datagram to the
+ * local sockets.
+ */
+
+ addr.sin6_port = 0;
+ addr.sin6_addr = in6addr_loopback;
+ ret = bind(mcast_state->sfd, (FAR struct sockaddr *)&addr, sizeof(addr));
+ assert_return_code(ret, errno);
+
+ ret = setsockopt(mcast_state->sfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &one, sizeof(one));
+ assert_return_code(ret, errno);
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: test_mcast_join_ipv6
+ *
+ * Description:
+ * MCAST_JOIN_GROUP and MCAST_LEAVE_GROUP succeed, and a datagram to the
+ * group arrives.
+ *
+ ****************************************************************************/
+
+void test_mcast_join_ipv6(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct sockaddr_in6 addr;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv6_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(TEST_PORT);
+ inet_pton(AF_INET6, TEST_GROUP, &addr.sin6_addr);
+ test_mcast_send(mcast_state->sfd, (FAR struct sockaddr *)&addr,
+ sizeof(addr));
+ assert_true(test_mcast_recv(mcast_state->rfd));
+
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+}
+
+/****************************************************************************
+ * Name: test_mcast_leave_ipv6
+ *
+ * Description:
+ * A second MCAST_LEAVE_GROUP fails as a second IPV6_LEAVE_GROUP.
+ *
+ ****************************************************************************/
+
+void test_mcast_leave_ipv6(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct ipv6_mreq mreq;
+ struct group_req gr;
+ int err;
+ int ret;
+
+ test_mcast_ipv6_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_LEAVE_GROUP,
+ &gr, sizeof(gr));
+ assert_int_equal(ret, -1);
+ err = errno;
+
+ inet_pton(AF_INET6, TEST_GROUP, &mreq.ipv6mr_multiaddr);
+ mreq.ipv6mr_interface = mcast_state->ifindex;
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
+ &mreq, sizeof(mreq));
+ assert_return_code(ret, errno);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
+ &mreq, sizeof(mreq));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, err);
+}
+
+/****************************************************************************
+ * Name: test_mcast_optlen_ipv6
+ ****************************************************************************/
+
+void test_mcast_optlen_ipv6(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv6_group(&gr, mcast_state->ifindex);
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr) - 1);
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, EINVAL);
+}
+
+/****************************************************************************
+ * Name: test_mcast_family_ipv6
+ ****************************************************************************/
+
+void test_mcast_family_ipv6(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_req gr;
+ int ret;
+
+ test_mcast_ipv6_group(&gr, mcast_state->ifindex);
+ gr.gr_group.ss_family = AF_INET;
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_JOIN_GROUP,
+ &gr, sizeof(gr));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, EADDRNOTAVAIL);
+}
+
+/****************************************************************************
+ * Name: test_mcast_source_ipv6
+ *
+ * Description:
+ * MLD has no source filter: MCAST_JOIN_SOURCE_GROUP fails.
+ *
+ ****************************************************************************/
+
+void test_mcast_source_ipv6(FAR void **state)
+{
+ FAR struct nettest_mcast_state_s *mcast_state = *state;
+ struct group_source_req gsr;
+ int ret;
+
+ memset(&gsr, 0, sizeof(gsr));
+ gsr.gsr_interface = mcast_state->ifindex;
+ gsr.gsr_group.ss_family = AF_INET6;
+ gsr.gsr_source.ss_family = AF_INET6;
+ ret = setsockopt(mcast_state->rfd, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP,
+ &gsr, sizeof(gsr));
+ assert_int_equal(ret, -1);
+ assert_int_equal(errno, ENOSYS);
+}