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);
+}

Reply via email to