diff --git a/docs/src/udp.rst b/docs/src/udp.rst index 8148828522e..9af95f768fa 100644 --- a/docs/src/udp.rst +++ b/docs/src/udp.rst @@ -181,6 +181,25 @@ API :returns: 0 on success, or an error code < 0 on failure. +.. c:function:: int uv_udp_set_source_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, const char* source_addr, uv_membership membership) + + Set membership for a source-specific multicast group. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init`. + + :param multicast_addr: Multicast address to set membership for. + + :param interface_addr: Interface address. + + :param source_addr: Source address. + + :param membership: Should be ``UV_JOIN_GROUP`` or ``UV_LEAVE_GROUP``. + + :returns: 0 on success, or an error code < 0 on failure. + + .. versionadded:: 1.20.0 + .. c:function:: int uv_udp_set_multicast_loop(uv_udp_t* handle, int on) Set IP multicast loop flag. Makes multicast packets loop back to diff --git a/include/uv.h b/include/uv.h index 3a061132cce..26982eeef10 100644 --- a/include/uv.h +++ b/include/uv.h @@ -359,6 +359,17 @@ typedef void (*uv_fs_poll_cb)(uv_fs_poll_t* handle, typedef void (*uv_signal_cb)(uv_signal_t* handle, int signum); +#if defined(MCAST_JOIN_SOURCE_GROUP) && defined(MCAST_LEAVE_SOURCE_GROUP) +# ifndef IPV6_SSM_SUPPORT +# define IPV6_SSM_SUPPORT +# endif +# ifndef IPV6_ADD_SOURCE_MEMBERSHIP +# define IPV6_ADD_SOURCE_MEMBERSHIP MCAST_JOIN_SOURCE_GROUP +# endif +# ifndef IPV6_DROP_SOURCE_MEMBERSHIP +# define IPV6_DROP_SOURCE_MEMBERSHIP MCAST_LEAVE_SOURCE_GROUP +# endif +#endif typedef enum { UV_LEAVE_GROUP = 0, @@ -634,6 +645,11 @@ UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership); +UV_EXTERN int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership); UV_EXTERN int uv_udp_set_multicast_loop(uv_udp_t* handle, int on); UV_EXTERN int uv_udp_set_multicast_ttl(uv_udp_t* handle, int ttl); UV_EXTERN int uv_udp_set_multicast_interface(uv_udp_t* handle, diff --git a/src/unix/udp.c b/src/unix/udp.c index a475bf57416..f2fe32fa40e 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -578,6 +578,100 @@ static int uv__udp_set_membership6(uv_udp_t* handle, } +static int uv__udp_set_source_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + const struct sockaddr_in* source_addr, + uv_membership membership) { + struct ip_mreq_source mreq; + int optname; + int err; + + err = uv__udp_maybe_deferred_bind(handle, AF_INET, UV_UDP_REUSEADDR); + if (err) + return err; + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_inet_pton(AF_INET, interface_addr, &mreq.imr_interface.s_addr); + if (err) + return err; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; + mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else + return -EINVAL; + + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IP, + optname, + &mreq, + sizeof(mreq))) { + return -errno; + } + + return 0; +} + + +static int uv__udp_set_source_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + const struct sockaddr_in6* source_addr, + uv_membership membership) { +#ifdef IPV6_SSM_SUPPORT + struct group_source_req mreq; + struct sockaddr_in6 addr6; + int optname; + int err; + + err = uv__udp_maybe_deferred_bind(handle, AF_INET6, UV_UDP_REUSEADDR); + if (err) + return err; + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_ip6_addr(interface_addr, 0, &addr6); + if (err) + return err; + mreq.gsr_interface = addr6.sin6_scope_id; + } + + memcpy(&mreq.gsr_group, multicast_addr, sizeof(mreq.gsr_group)); + memcpy(&mreq.gsr_source, source_addr, sizeof(mreq.gsr_source)); + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else + return -EINVAL; + + if (setsockopt(handle->io_watcher.fd, + IPPROTO_IPV6, + optname, + &mreq, + sizeof(mreq))) { + return -errno; + } + + return 0; +#else + return -EPROTONOSUPPORT; +#endif /* IPV6_SSM_SUPPORT */ +} + + int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags) { int domain; int err; @@ -660,6 +754,46 @@ int uv_udp_set_membership(uv_udp_t* handle, } } + +int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership) { + int err; + union sockaddr { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + }; + + union sockaddr mcast_addr; + union sockaddr src_addr; + + err = uv_ip4_addr(multicast_addr, 0, &mcast_addr.sa_in); + if (err) { + err = uv_ip6_addr(multicast_addr, 0, &mcast_addr.sa_in6); + if (err) + return err; + err = uv_ip6_addr(source_addr, 0, &src_addr.sa_in6); + if (err) + return err; + return uv__udp_set_source_membership6(handle, + &mcast_addr.sa_in6, + interface_addr, + &src_addr.sa_in6, + membership); + } + err = uv_ip4_addr(source_addr, 0, &src_addr.sa_in); + if (err) + return err; + return uv__udp_set_source_membership4(handle, + &mcast_addr.sa_in, + interface_addr, + &src_addr.sa_in, + membership); +} + + static int uv__setsockopt(uv_udp_t* handle, int option4, int option6, diff --git a/src/win/udp.c b/src/win/udp.c index cd1d0e07b23..954053cc6c2 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -700,6 +700,114 @@ int uv__udp_set_membership6(uv_udp_t* handle, } +static int uv__udp_set_source_membership4(uv_udp_t* handle, + const struct sockaddr_in* multicast_addr, + const char* interface_addr, + const struct sockaddr_in* source_addr, + uv_membership membership) { + struct ip_mreq_source mreq; + int optname; + int err; + + if (handle->flags & UV_HANDLE_IPV6) + return UV_EINVAL; + + /* If the socket is unbound, bind to inaddr_any. */ + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip4_any_, + sizeof(uv_addr_ip4_any_), + UV_UDP_REUSEADDR); + if (err) + return uv_translate_sys_error(err); + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_inet_pton(AF_INET, interface_addr, &mreq.imr_interface.s_addr); + if (err) + return err; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + + mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; + mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else + return -EINVAL; + + if (setsockopt(handle->socket, + IPPROTO_IP, + optname, + (char*) &mreq, + sizeof(mreq)) == SOCKET_ERROR) { + return uv_translate_sys_error(WSAGetLastError()); + } + + return 0; +} + + +int uv__udp_set_source_membership6(uv_udp_t* handle, + const struct sockaddr_in6* multicast_addr, + const char* interface_addr, + const struct sockaddr_in6* source_addr, + uv_membership membership) { +#ifdef IPV6_SSM_SUPPORT + struct group_source_req mreq; + struct sockaddr_in6 addr6; + int optname; + int err; + + if ((handle->flags & UV_HANDLE_BOUND) && !(handle->flags & UV_HANDLE_IPV6)) + return UV_EINVAL; + + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip6_any_, + sizeof(uv_addr_ip6_any_), + UV_UDP_REUSEADDR); + + if (err) + return uv_translate_sys_error(err); + + memset(&mreq, 0, sizeof(mreq)); + + if (interface_addr != NULL) { + err = uv_ip6_addr(interface_addr, 0, &addr6); + if (err) + return err; + mreq.gsr_interface = addr6.sin6_scope_id; + } + + memcpy(&mreq.gsr_group, multicast_addr, sizeof(mreq.gsr_group)); + memcpy(&mreq.gsr_source, source_addr, sizeof(mreq.gsr_source)); + + if (membership == UV_JOIN_GROUP) + optname = IP_ADD_SOURCE_MEMBERSHIP; + else if (membership == UV_LEAVE_GROUP) + optname = IP_DROP_SOURCE_MEMBERSHIP; + else + return -EINVAL; + + if (setsockopt(handle->socket, + IPPROTO_IPV6, + optname, + (char*) &mreq, + sizeof(mreq)) == SOCKET_ERROR) { + return uv_translate_sys_error(WSAGetLastError()); + } + + return 0; +#else + return UV_EPROTONOSUPPORT; +#endif /* IPV6_SSM_SUPPORT */ +} + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, @@ -716,6 +824,45 @@ int uv_udp_set_membership(uv_udp_t* handle, } +int uv_udp_set_source_membership(uv_udp_t* handle, + const char* multicast_addr, + const char* interface_addr, + const char* source_addr, + uv_membership membership) { + int err; + union sockaddr { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + }; + + union sockaddr mcast_addr; + union sockaddr src_addr; + + err = uv_ip4_addr(multicast_addr, 0, &mcast_addr.sa_in); + if (err) { + err = uv_ip6_addr(multicast_addr, 0, &mcast_addr.sa_in6); + if (err) + return err; + err = uv_ip6_addr(source_addr, 0, &src_addr.sa_in6); + if (err) + return err; + return uv__udp_set_source_membership6(handle, + &mcast_addr.sa_in6, + interface_addr, + &src_addr.sa_in6, + membership); + } + err = uv_ip4_addr(source_addr, 0, &src_addr.sa_in); + if (err) + return err; + return uv__udp_set_source_membership4(handle, + &mcast_addr.sa_in, + interface_addr, + &src_addr.sa_in, + membership); +} + + int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) { struct sockaddr_storage addr_st; struct sockaddr_in* addr4;