diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index b577563f98b..f1e5bdbb4bb 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -7,6 +7,7 @@ /* * Copyright (c) 2016 Intel Corporation * Copyright (c) 2021 Nordic Semiconductor + * Copyright (c) 2025 Aerlync Labs Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -390,6 +391,10 @@ __net_socket struct net_context { */ uint8_t ipv4_mcast_ifindex; }; + /** Flag to enable/disable multicast loop */ + union { + bool ipv6_mcast_loop; /**< IPv6 multicast loop */ + }; #endif /* CONFIG_NET_IPV6 || CONFIG_NET_IPV4 */ #if defined(CONFIG_NET_CONTEXT_TIMESTAMPING) @@ -897,6 +902,40 @@ static inline void net_context_set_ipv6_mcast_hop_limit(struct net_context *cont context->ipv6_mcast_hop_limit = hop_limit; } +#if defined(CONFIG_NET_IPV6) + +/** + * @brief Get IPv6 multicast loop value for this context. + * + * @details This function returns the IPv6 multicast loop value + * that is set to this context. + * + * @param context Network context. + * + * @return IPv6 multicast loop value + */ +static inline bool net_context_get_ipv6_mcast_loop(struct net_context *context) +{ + return context->options.ipv6_mcast_loop; +} + +/** + * @brief Set IPv6 multicast loop value for this context. + * + * @details This function sets the IPv6 multicast loop value for + * this context. + * + * @param context Network context. + * @param ipv6_mcast_loop IPv6 multicast loop value. + */ +static inline void net_context_set_ipv6_mcast_loop(struct net_context *context, + bool ipv6_mcast_loop) +{ + context->options.ipv6_mcast_loop = ipv6_mcast_loop; +} + +#endif + /** * @brief Enable or disable socks proxy support for this context. * @@ -1325,6 +1364,7 @@ enum net_context_option { NET_OPT_MCAST_IFINDEX = 19, /**< IPv6 multicast output network interface index */ NET_OPT_MTU = 20, /**< IPv4 socket path MTU */ NET_OPT_LOCAL_PORT_RANGE = 21, /**< Clamp local port range */ + NET_OPT_IPV6_MCAST_LOOP = 22, /**< IPV6 multicast loop */ }; /** diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index 58c3fb0332d..69e15776daa 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -8,6 +8,7 @@ /* * Copyright (c) 2017-2018 Linaro Limited * Copyright (c) 2021 Nordic Semiconductor + * Copyright (c) 2025 Aerlync Labs Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -1005,6 +1006,9 @@ struct ip_mreq { /** Set the multicast hop limit for the socket. */ #define IPV6_MULTICAST_HOPS 18 +/** Set the multicast loop bit for the socket. */ +#define IPV6_MULTICAST_LOOP 19 + /** Join IPv6 multicast group. */ #define IPV6_ADD_MEMBERSHIP 20 diff --git a/subsys/net/ip/Kconfig.ipv6 b/subsys/net/ip/Kconfig.ipv6 index 7ee65cc9375..017061b974a 100644 --- a/subsys/net/ip/Kconfig.ipv6 +++ b/subsys/net/ip/Kconfig.ipv6 @@ -1,6 +1,7 @@ # IPv6 Options # Copyright (c) 2016 Intel Corporation. +# Copyright (c) 2025 Aerlync Labs Inc. # SPDX-License-Identifier: Apache-2.0 menuconfig NET_IPV6 @@ -81,6 +82,13 @@ config NET_INITIAL_MCAST_HOP_LIMIT don't leave the local network unless the application explicitly requests it. +config NET_INITIAL_IPV6_MCAST_LOOP + bool "Control whether the socket sees multicast packets sent by itself" + default y + help + Assign initial value to IPV6_MULTICAST_LOOP in socket options, + if not set by the user using setsockopt(). + config NET_IPV6_MAX_NEIGHBORS int "How many IPv6 neighbors are supported" default 8 diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index d44486eb1b4..c332c9a879c 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -7,6 +7,7 @@ /* * Copyright (c) 2016 Intel Corporation * Copyright (c) 2021 Nordic Semiconductor + * Copyright (c) 2025 Aerlync Labs Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -583,6 +584,10 @@ int net_context_get(sa_family_t family, enum net_sock_type type, uint16_t proto, contexts[i].ipv6_hop_limit = INITIAL_HOP_LIMIT; contexts[i].ipv6_mcast_hop_limit = INITIAL_MCAST_HOP_LIMIT; +#if defined(CONFIG_NET_IPV6) + contexts[i].options.ipv6_mcast_loop = + IS_ENABLED(CONFIG_NET_INITIAL_IPV6_MCAST_LOOP); +#endif } if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)&contexts[i].local; @@ -2033,6 +2038,20 @@ static int get_context_local_port_range(struct net_context *context, #endif } +static int get_context_ipv6_mcast_loop(struct net_context *context, + void *value, size_t *len) +{ +#if defined(CONFIG_NET_IPV6) + return get_bool_option(context->options.ipv6_mcast_loop, value, len); +#else + ARG_UNUSED(context); + ARG_UNUSED(value); + ARG_UNUSED(len); + + return -ENOTSUP; +#endif +} + /* If buf is not NULL, then use it. Otherwise read the data to be written * to net_pkt from msghdr. */ @@ -3329,6 +3348,20 @@ static int set_context_unicast_hop_limit(struct net_context *context, #endif } +static int set_context_ipv6_mcast_loop(struct net_context *context, + const void *value, size_t len) +{ +#if defined(CONFIG_NET_IPV6) + return set_bool_option(&context->options.ipv6_mcast_loop, value, len); +#else + ARG_UNUSED(context); + ARG_UNUSED(value); + ARG_UNUSED(len); + + return -ENOTSUP; +#endif +} + static int set_context_reuseaddr(struct net_context *context, const void *value, size_t len) { @@ -3652,6 +3685,9 @@ int net_context_set_option(struct net_context *context, case NET_OPT_LOCAL_PORT_RANGE: ret = set_context_local_port_range(context, value, len); break; + case NET_OPT_IPV6_MCAST_LOOP: + ret = set_context_ipv6_mcast_loop(context, value, len); + break; } k_mutex_unlock(&context->lock); @@ -3737,6 +3773,9 @@ int net_context_get_option(struct net_context *context, case NET_OPT_LOCAL_PORT_RANGE: ret = get_context_local_port_range(context, value, len); break; + case NET_OPT_IPV6_MCAST_LOOP: + ret = get_context_ipv6_mcast_loop(context, value, len); + break; } k_mutex_unlock(&context->lock); diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index d7698143561..3d657e378e9 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -7,6 +7,7 @@ /* * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2025 Aerlync Labs Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -409,6 +410,30 @@ int net_try_send_data(struct net_pkt *pkt, k_timeout_t timeout) goto err; } +#if defined(CONFIG_NET_IPV6) + if (net_pkt_family(pkt) == AF_INET6) { + const struct in6_addr *dest = (const struct in6_addr *)&NET_IPV6_HDR(pkt)->dst; + struct net_context *ctx = net_pkt_context(pkt); + + if (net_ipv6_is_addr_mcast(dest) && ctx != NULL && + net_context_get_ipv6_mcast_loop(ctx)) { + struct net_pkt *clone = net_pkt_clone(pkt, K_NO_WAIT); + + if (clone != NULL) { + net_pkt_set_iface(clone, net_pkt_iface(pkt)); + if (net_recv_data(net_pkt_iface(clone), clone) < 0) { + if (IS_ENABLED(CONFIG_NET_STATISTICS)) { + net_stats_update_ipv6_drop(net_pkt_iface(pkt)); + } + net_pkt_unref(clone); + } + } else { + NET_DBG("Failed to clone multicast packet"); + } + } + } +#endif + if (net_if_try_send_data(net_pkt_iface(pkt), pkt, timeout) == NET_DROP) { ret = -EIO; goto err; diff --git a/subsys/net/lib/sockets/sockets_inet.c b/subsys/net/lib/sockets/sockets_inet.c index 0c2c7f984ff..bf9d63f997e 100644 --- a/subsys/net/lib/sockets/sockets_inet.c +++ b/subsys/net/lib/sockets/sockets_inet.c @@ -2,6 +2,7 @@ * Copyright (c) 2017 Linaro Limited * Copyright (c) 2021 Nordic Semiconductor * Copyright (c) 2023 Arm Limited (or its affiliates). All rights reserved. + * Copyright (c) 2025 Aerlync Labs Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -2080,6 +2081,18 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, } return 0; + + case IPV6_MULTICAST_LOOP: + ret = net_context_get_option(ctx, + NET_OPT_IPV6_MCAST_LOOP, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } break; @@ -2722,6 +2735,17 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, } break; + + case IPV6_MULTICAST_LOOP: + ret = net_context_set_option(ctx, + NET_OPT_IPV6_MCAST_LOOP, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; } break;