diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index bdd0e0ad41e..a165e1fc101 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1117,6 +1117,16 @@ struct in_pktinfo { /** sockopt: Set IPv4 multicast TTL value. */ #define IP_MULTICAST_TTL 33 +/** sockopt: Join IPv4 multicast group. */ +#define IP_ADD_MEMBERSHIP 35 +/** sockopt: Leave IPv4 multicast group. */ +#define IP_DROP_MEMBERSHIP 36 + +struct ip_mreqn { + struct in_addr imr_multiaddr; /* IP multicast group address */ + struct in_addr imr_address; /* IP address of local interface */ + int imr_ifindex; /* interface index */ +}; /* Socket options for IPPROTO_IPV6 level */ /** sockopt: Set the unicast hop limit for the socket. */ diff --git a/subsys/net/lib/sockets/sockets.c b/subsys/net/lib/sockets/sockets.c index 697c94e8808..c9b3affcf90 100644 --- a/subsys/net/lib/sockets/sockets.c +++ b/subsys/net/lib/sockets/sockets.c @@ -29,6 +29,8 @@ LOG_MODULE_REGISTER(net_sock, CONFIG_NET_SOCKETS_LOG_LEVEL); #include "socks.h" #endif +#include + #include "../../ip/net_stats.h" #include "sockets_internal.h" @@ -2642,6 +2644,62 @@ int z_vrfy_zsock_getsockopt(int sock, int level, int optname, #include #endif /* CONFIG_USERSPACE */ +static int ipv4_multicast_group(struct net_context *ctx, const void *optval, + socklen_t optlen, bool do_join) +{ + struct ip_mreqn *mreqn; + struct net_if *iface; + int ifindex, ret; + + if (optval == NULL || optlen != sizeof(struct ip_mreqn)) { + errno = EINVAL; + return -1; + } + + mreqn = (struct ip_mreqn *)optval; + + if (mreqn->imr_multiaddr.s_addr == INADDR_ANY) { + errno = EINVAL; + return -1; + } + + if (mreqn->imr_ifindex != 0) { + iface = net_if_get_by_index(mreqn->imr_ifindex); + } else { + ifindex = net_if_ipv4_addr_lookup_by_index(&mreqn->imr_address); + iface = net_if_get_by_index(ifindex); + } + + if (iface == NULL) { + /* Check if ctx has already an interface and if not, + * then select the default interface. + */ + if (ctx->iface <= 0) { + iface = net_if_get_default(); + } else { + iface = net_if_get_by_index(ctx->iface); + } + + if (iface == NULL) { + errno = EINVAL; + return -1; + } + } + + if (do_join) { + ret = net_ipv4_igmp_join(iface, &mreqn->imr_multiaddr, NULL); + } else { + ret = net_ipv4_igmp_leave(iface, &mreqn->imr_multiaddr); + } + + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; +} + int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, const void *optval, socklen_t optlen) { @@ -2947,6 +3005,22 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, } return 0; + + case IP_ADD_MEMBERSHIP: + if (IS_ENABLED(CONFIG_NET_IPV4)) { + return ipv4_multicast_group(ctx, optval, + optlen, true); + } + + break; + + case IP_DROP_MEMBERSHIP: + if (IS_ENABLED(CONFIG_NET_IPV4)) { + return ipv4_multicast_group(ctx, optval, + optlen, false); + } + + break; } break;