diff options
| author | Garrett D'Amore <garrett@damore.org> | 2024-06-02 23:33:27 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-02 23:33:27 -0700 |
| commit | 603282f28e6f2e1b32d3a587d8de761f9f94ad45 (patch) | |
| tree | b90b5f4c057979524e6b4a12a74742c7da25c484 /src | |
| parent | 890d4899138ff497a48ba4aaa2385b3ed2b84ac4 (diff) | |
| download | nng-603282f28e6f2e1b32d3a587d8de761f9f94ad45.tar.gz nng-603282f28e6f2e1b32d3a587d8de761f9f94ad45.tar.bz2 nng-603282f28e6f2e1b32d3a587d8de761f9f94ad45.zip | |
UDP: Introduce an experimental (undocumented for now) public API for UDP. (#1838)
This exposes the UDP methods as nng_ methods, and adds support for Multicast Membership,
which is useful in a variety of situations.
No documentation is provided, and applications should consider thios API experimental.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/platform.h | 4 | ||||
| -rw-r--r-- | src/nng.c | 39 | ||||
| -rw-r--r-- | src/platform/posix/posix_udp.c | 128 | ||||
| -rw-r--r-- | src/platform/udp_test.c | 253 | ||||
| -rw-r--r-- | src/platform/windows/win_udp.c | 95 |
5 files changed, 421 insertions, 98 deletions
diff --git a/src/core/platform.h b/src/core/platform.h index 53ef53af..d7a88238 100644 --- a/src/core/platform.h +++ b/src/core/platform.h @@ -407,6 +407,10 @@ extern void nni_plat_udp_send(nni_plat_udp *, nni_aio *); // NNG_EMSGSIZE results. extern void nni_plat_udp_recv(nni_plat_udp *, nni_aio *); +// nni_plat_udp_membership provides for joining or leaving multicast groups. +extern int nni_plat_udp_multicast_membership( + nni_plat_udp *udp, nni_sockaddr *sa, bool join); + // // Notification Pipe Pairs // @@ -10,6 +10,7 @@ #include "nng/nng.h" #include "core/nng_impl.h" +#include "core/platform.h" // This file provides the "public" API. This is a thin wrapper around // internal API functions. We use the public prefix instead of internal, @@ -2174,3 +2175,41 @@ nng_socket_pair(int fds[2]) { return (nni_socket_pair(fds)); } + +int +nng_udp_open(nng_udp **udp, nng_sockaddr *sa) +{ + (void) nni_init(); + return (nni_plat_udp_open((nni_plat_udp **) udp, sa)); +} + +void +nng_udp_close(nng_udp *udp) +{ + nni_plat_udp_close((nni_plat_udp *) udp); +} + +int +nng_udp_sockname(nng_udp *udp, nng_sockaddr *sa) +{ + return (nni_plat_udp_sockname((nni_plat_udp *) udp, sa)); +} + +void +nng_udp_send(nng_udp *udp, nng_aio *aio) +{ + nni_plat_udp_send((nni_plat_udp *) udp, aio); +} + +void +nng_udp_recv(nng_udp *udp, nng_aio *aio) +{ + nni_plat_udp_recv((nni_plat_udp *) udp, aio); +} + +int +nng_udp_multicast_membership(nng_udp *udp, nng_sockaddr *sa, bool join) +{ + return ( + nni_plat_udp_multicast_membership((nni_plat_udp *) udp, sa, join)); +} diff --git a/src/platform/posix/posix_udp.c b/src/platform/posix/posix_udp.c index 735953fa..4ef4c68c 100644 --- a/src/platform/posix/posix_udp.c +++ b/src/platform/posix/posix_udp.c @@ -1,5 +1,5 @@ // -// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a @@ -9,6 +9,9 @@ // #include "core/nng_impl.h" +#include "nng/nng.h" +#include "platform/posix/posix_impl.h" +#include <sys/errno.h> #ifdef NNG_PLATFORM_POSIX #include "platform/posix/posix_pollq.h" @@ -27,6 +30,22 @@ #define MSG_NOSIGNAL 0 #endif +#ifndef NNG_HAVE_INET6 +#undef NNG_ENABLE_IPV6 +#endif + +// Linux has IPV6_ADD_MEMBERSHIP and IPV6_DROP_MEMBERSHIP +#ifndef IPV6_JOIN_GROUP +#ifdef IPV6_ADD_MEMBERSHIP +#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP +#endif +#endif +#ifndef IPV6_LEAVE_GROUP +#ifdef IPV6_DROP_MEMBERSHIP +#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP +#endif +#endif + struct nni_plat_udp { nni_posix_pfd *udp_pfd; int udp_fd; @@ -56,15 +75,15 @@ nni_posix_udp_doclose(nni_plat_udp *udp) static void nni_posix_udp_dorecv(nni_plat_udp *udp) { - nni_aio * aio; + nni_aio *aio; nni_list *q = &udp->udp_recvq; // While we're able to recv, do so. while ((aio = nni_list_first(q)) != NULL) { struct iovec iov[4]; unsigned niov; - nni_iov * aiov; + nni_iov *aiov; struct sockaddr_storage ss; - nng_sockaddr * sa; + nng_sockaddr *sa; struct msghdr hdr = { .msg_name = NULL }; int rv = 0; int cnt = 0; @@ -102,7 +121,7 @@ nni_posix_udp_dorecv(nni_plat_udp *udp) static void nni_posix_udp_dosend(nni_plat_udp *udp) { - nni_aio * aio; + nni_aio *aio; nni_list *q = &udp->udp_sendq; // While we're able to send, do so. @@ -118,7 +137,7 @@ nni_posix_udp_dosend(nni_plat_udp *udp) rv = NNG_EADDRINVAL; } else { unsigned niov; - nni_iov * aiov; + nni_iov *aiov; struct iovec iov[16]; nni_aio_get_iov(aio, &niov, &aiov); @@ -192,7 +211,7 @@ nni_posix_udp_cb(nni_posix_pfd *pfd, unsigned events, void *arg) int nni_plat_udp_open(nni_plat_udp **upp, nni_sockaddr *bindaddr) { - nni_plat_udp * udp; + nni_plat_udp *udp; int salen; struct sockaddr_storage sa; int rv; @@ -323,4 +342,99 @@ nni_plat_udp_sockname(nni_plat_udp *udp, nni_sockaddr *sa) return (nni_posix_sockaddr2nn(sa, &ss, sz)); } +// Joining a multicast group is different than binding to a multicast +// group. This allows to receive both unicast and multicast at the given +// address. +static int +ip4_multicast_member(nni_plat_udp *udp, struct sockaddr *sa, bool join) +{ + struct ip_mreq mreq; + struct sockaddr_in *sin; + struct sockaddr_storage local; + socklen_t sz = sizeof(local); + + if (getsockname(udp->udp_fd, (struct sockaddr *) &local, &sz) >= 0) { + if (local.ss_family != AF_INET) { + // address families have to match + return (NNG_EADDRINVAL); + } + sin = (struct sockaddr_in *) &local; + mreq.imr_interface.s_addr = sin->sin_addr.s_addr; + } else { + mreq.imr_interface.s_addr = INADDR_ANY; + } + + // Determine our local interface + sin = (struct sockaddr_in *) sa; + + mreq.imr_multiaddr.s_addr = sin->sin_addr.s_addr; + if (setsockopt(udp->udp_fd, IPPROTO_IP, + join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, + sizeof(mreq)) == 0) { + return (0); + } + return (nni_plat_errno(errno)); +} + +#ifdef NNG_ENABLE_IPV6 +static int +ip6_multicast_member(nni_plat_udp *udp, struct sockaddr *sa, bool join) +{ + struct ipv6_mreq mreq; + struct sockaddr_in6 *sin6; + struct sockaddr_storage local; + socklen_t sz = sizeof(local); + + if (getsockname(udp->udp_fd, (struct sockaddr *) &local, &sz) >= 0) { + if (local.ss_family != AF_INET6) { + // address families have to match + return (NNG_EADDRINVAL); + } + sin6 = (struct sockaddr_in6 *) &local; + mreq.ipv6mr_interface = sin6->sin6_scope_id; + } else { + mreq.ipv6mr_interface = 0; + } + + // Determine our local interface + sin6 = (struct sockaddr_in6 *) sa; + + mreq.ipv6mr_multiaddr = sin6->sin6_addr; + if (setsockopt(udp->udp_fd, IPPROTO_IPV6, + join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &mreq, + sizeof(mreq)) == 0) { + return (0); + } + return (nni_plat_errno(errno)); +} +#endif + +int +nni_plat_udp_multicast_membership( + nni_plat_udp *udp, nni_sockaddr *sa, bool join) +{ + struct sockaddr_storage ss; + socklen_t sz; + int rv; + + sz = nni_posix_nn2sockaddr(&ss, sa); + if (sz < 1) { + return (NNG_EADDRINVAL); + } + switch (ss.ss_family) { + case AF_INET: + rv = ip4_multicast_member(udp, (struct sockaddr *) &ss, join); + break; +#ifdef NNG_ENABLE_IPV6 + case AF_INET6: + rv = ip6_multicast_member(udp, (struct sockaddr *) &ss, join); + break; +#endif + default: + rv = NNG_EADDRINVAL; + } + + return (rv); +} + #endif // NNG_PLATFORM_POSIX diff --git a/src/platform/udp_test.c b/src/platform/udp_test.c index 070b3eea..b6b31c9b 100644 --- a/src/platform/udp_test.c +++ b/src/platform/udp_test.c @@ -23,20 +23,18 @@ void test_udp_pair(void) { - nng_sockaddr sa1; - nng_sockaddr sa2; - nni_plat_udp *u1; - nni_plat_udp *u2; - uint32_t loopback; - nng_aio *aio1; - nng_aio *aio2; - nng_iov iov1, iov2; - char msg[] = "hello"; - char rbuf[1024]; - nng_sockaddr to; - nng_sockaddr from; - - NUTS_PASS(nni_init()); + nng_sockaddr sa1; + nng_sockaddr sa2; + nng_udp *u1; + nng_udp *u2; + uint32_t loopback; + nng_aio *aio1; + nng_aio *aio2; + nng_iov iov1, iov2; + char msg[] = "hello"; + char rbuf[1024]; + nng_sockaddr to; + nng_sockaddr from; loopback = htonl(0x7f000001); // 127.0.0.1 @@ -48,11 +46,11 @@ test_udp_pair(void) sa2.s_in.sa_addr = loopback; sa2.s_in.sa_port = 0; - NUTS_PASS(nni_plat_udp_open(&u1, &sa1)); - NUTS_PASS(nni_plat_udp_open(&u2, &sa2)); + NUTS_PASS(nng_udp_open(&u1, &sa1)); + NUTS_PASS(nng_udp_open(&u2, &sa2)); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa1)); - NUTS_PASS(nni_plat_udp_sockname(u2, &sa2)); + NUTS_PASS(nng_udp_sockname(u1, &sa1)); + NUTS_PASS(nng_udp_sockname(u2, &sa2)); NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL)); @@ -68,8 +66,8 @@ test_udp_pair(void) NUTS_PASS(nng_aio_set_iov(aio2, 1, &iov2)); NUTS_PASS(nng_aio_set_input(aio2, 0, &from)); - nni_plat_udp_recv(u2, aio2); - nni_plat_udp_send(u1, aio1); + nng_udp_recv(u2, aio2); + nng_udp_send(u1, aio1); nng_aio_wait(aio1); nng_aio_wait(aio2); @@ -83,26 +81,24 @@ test_udp_pair(void) nng_aio_free(aio1); nng_aio_free(aio2); - nni_plat_udp_close(u1); - nni_plat_udp_close(u2); + nng_udp_close(u1); + nng_udp_close(u2); } void test_udp_multi_send_recv(void) { - nng_sockaddr sa1, sa2, sa3, sa4; - nni_plat_udp *u1; - nni_plat_udp *u2; - uint32_t loopback; - nng_aio *aio1, *aio2, *aio3, *aio4; - nng_iov iov1, iov2, iov3, iov4; - char msg1[] = "hello"; - char msg2[] = "there"; - char rbuf1[32]; - char rbuf2[32]; - nng_sockaddr to; - - NUTS_PASS(nni_init()); + nng_sockaddr sa1, sa2, sa3, sa4; + nng_udp *u1; + nng_udp *u2; + uint32_t loopback; + nng_aio *aio1, *aio2, *aio3, *aio4; + nng_iov iov1, iov2, iov3, iov4; + char msg1[] = "hello"; + char msg2[] = "there"; + char rbuf1[32]; + char rbuf2[32]; + nng_sockaddr to; loopback = htonl(0x7f000001); // 127.0.0.1 @@ -114,11 +110,11 @@ test_udp_multi_send_recv(void) sa2.s_in.sa_addr = loopback; sa2.s_in.sa_port = 0; - NUTS_PASS(nni_plat_udp_open(&u1, &sa1)); - NUTS_PASS(nni_plat_udp_open(&u2, &sa2)); + NUTS_PASS(nng_udp_open(&u1, &sa1)); + NUTS_PASS(nng_udp_open(&u2, &sa2)); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa1)); - NUTS_PASS(nni_plat_udp_sockname(u2, &sa2)); + NUTS_PASS(nng_udp_sockname(u1, &sa1)); + NUTS_PASS(nng_udp_sockname(u2, &sa2)); NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL)); @@ -147,11 +143,11 @@ test_udp_multi_send_recv(void) NUTS_PASS(nng_aio_set_iov(aio4, 1, &iov4)); NUTS_PASS(nng_aio_set_input(aio4, 0, &sa4)); - nni_plat_udp_recv(u2, aio4); - nni_plat_udp_recv(u2, aio3); - nni_plat_udp_send(u1, aio2); + nng_udp_recv(u2, aio4); + nng_udp_recv(u2, aio3); + nng_udp_send(u1, aio2); nng_msleep(100); // to keep order clear - nni_plat_udp_send(u1, aio1); + nng_udp_send(u1, aio1); nng_aio_wait(aio1); nng_aio_wait(aio2); nng_aio_wait(aio3); @@ -166,7 +162,7 @@ test_udp_multi_send_recv(void) NUTS_ASSERT(strcmp(rbuf1, msg1) == 0); NUTS_ASSERT(strcmp(rbuf2, msg2) == 0); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa2)); + NUTS_PASS(nng_udp_sockname(u1, &sa2)); NUTS_ASSERT(sa2.s_in.sa_family == sa3.s_in.sa_family); NUTS_ASSERT(sa2.s_in.sa_addr == sa3.s_in.sa_addr); NUTS_ASSERT(sa2.s_in.sa_port == sa3.s_in.sa_port); @@ -179,21 +175,19 @@ test_udp_multi_send_recv(void) nng_aio_free(aio2); nng_aio_free(aio3); nng_aio_free(aio4); - nni_plat_udp_close(u1); - nni_plat_udp_close(u2); + nng_udp_close(u1); + nng_udp_close(u2); } void test_udp_send_no_addr(void) { - nng_sockaddr sa1; - nni_plat_udp *u1; - uint32_t loopback; - nng_aio *aio1; - nng_iov iov1; - char msg[] = "hello"; - - NUTS_PASS(nni_init()); + nng_sockaddr sa1; + nng_udp *u1; + uint32_t loopback; + nng_aio *aio1; + nng_iov iov1; + char msg[] = "hello"; loopback = htonl(0x7f000001); // 127.0.0.1 @@ -201,8 +195,8 @@ test_udp_send_no_addr(void) sa1.s_in.sa_addr = loopback; sa1.s_in.sa_port = 0; // wild card port binding - NUTS_PASS(nni_plat_udp_open(&u1, &sa1)); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa1)); + NUTS_PASS(nng_udp_open(&u1, &sa1)); + NUTS_PASS(nng_udp_sockname(u1, &sa1)); NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); @@ -210,28 +204,26 @@ test_udp_send_no_addr(void) iov1.iov_len = strlen(msg) + 1; NUTS_PASS(nng_aio_set_iov(aio1, 1, &iov1)); - nni_plat_udp_send(u1, aio1); + nng_udp_send(u1, aio1); nng_aio_wait(aio1); NUTS_FAIL(nng_aio_result(aio1), NNG_EADDRINVAL); nng_aio_free(aio1); - nni_plat_udp_close(u1); + nng_udp_close(u1); } void test_udp_send_ipc(void) { - nng_sockaddr sa1; - nng_sockaddr sa2; - nni_plat_udp *u1; - uint32_t loopback; - nng_aio *aio1; - nng_iov iov1; - char msg[] = "hello"; - int rv; - - NUTS_PASS(nni_init()); + nng_sockaddr sa1 = { 0 }; + nng_sockaddr sa2 = { 0 }; + nng_udp *u1; + uint32_t loopback; + nng_aio *aio1; + nng_iov iov1; + char msg[] = "hello"; + int rv; loopback = htonl(0x7f000001); // 127.0.0.1 @@ -242,8 +234,8 @@ test_udp_send_ipc(void) sa2.s_ipc.sa_family = NNG_AF_IPC; strcat(sa2.s_ipc.sa_path, "/tmp/bogus"); - NUTS_PASS(nni_plat_udp_open(&u1, &sa1)); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa1)); + NUTS_PASS(nng_udp_open(&u1, &sa1)); + NUTS_PASS(nng_udp_sockname(u1, &sa1)); NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); @@ -252,49 +244,126 @@ test_udp_send_ipc(void) NUTS_PASS(nng_aio_set_iov(aio1, 1, &iov1)); NUTS_PASS(nng_aio_set_input(aio1, 0, &sa2)); - nni_plat_udp_send(u1, aio1); + nng_udp_send(u1, aio1); nng_aio_wait(aio1); rv = nng_aio_result(aio1); NUTS_ASSERT(rv == NNG_EADDRINVAL || rv == NNG_ENOTSUP); nng_aio_free(aio1); - nni_plat_udp_close(u1); + nng_udp_close(u1); } void test_udp_bogus_bind(void) { - nni_plat_udp *u; - nng_sockaddr sa; - int rv; + nng_udp *u; + nng_sockaddr sa = { 0 }; + int rv; sa.s_ipc.sa_family = NNG_AF_IPC; strcpy(sa.s_ipc.sa_path, "/tmp/t"); - rv = nni_plat_udp_open(&u, &sa); + rv = nng_udp_open(&u, &sa); // Some platforms reject IPC addresses altogether (Windows), // whereas others just say it's not supported with UDP. NUTS_ASSERT((rv == NNG_ENOTSUP) || (rv == NNG_EADDRINVAL)); // NULL address also bad. - NUTS_FAIL(nni_plat_udp_open(&u, NULL), NNG_EADDRINVAL); + NUTS_FAIL(nng_udp_open(&u, NULL), NNG_EADDRINVAL); } void test_udp_duplicate_bind(void) { - nni_plat_udp *u1; - nni_plat_udp *u2; - nng_sockaddr sa; + nng_udp *u1; + nng_udp *u2; + nng_sockaddr sa = { 0 }; + + sa.s_in.sa_family = NNG_AF_INET; + sa.s_in.sa_addr = htonl(0x7f000001); + + NUTS_PASS(nng_udp_open(&u1, &sa)); + NUTS_PASS(nng_udp_sockname(u1, &sa)); + NUTS_FAIL(nng_udp_open(&u2, &sa), NNG_EADDRINUSE); + nng_udp_close(u1); +} + +void +test_udp_multicast_membership(void) +{ + nng_udp *u1; + nng_sockaddr sa = { 0 }; + nng_sockaddr mc = { 0 }; + + mc.s_in.sa_family = NNG_AF_INET; + mc.s_in.sa_addr = htonl(0xe0000001); // 224.0.0.1 ... all hosts sa.s_in.sa_family = NNG_AF_INET; sa.s_in.sa_addr = htonl(0x7f000001); - NUTS_PASS(nni_init()); - NUTS_PASS(nni_plat_udp_open(&u1, &sa)); - NUTS_PASS(nni_plat_udp_sockname(u1, &sa)); - NUTS_FAIL(nni_plat_udp_open(&u2, &sa), NNG_EADDRINUSE); - nni_plat_udp_close(u1); + NUTS_PASS(nng_udp_open(&u1, &sa)); + NUTS_PASS(nng_udp_sockname(u1, &sa)); + NUTS_PASS(nng_udp_multicast_membership(u1, &mc, true)); + NUTS_PASS(nng_udp_multicast_membership(u1, &mc, false)); + nng_udp_close(u1); +} + +void +test_udp_multicast_send_recv(void) +{ + nng_udp *u1; + nng_udp *u2; + nng_sockaddr sa1 = { 0 }; + nng_sockaddr sa2 = { 0 }; + nng_sockaddr ra2 = { 0 }; + nng_sockaddr mc = { 0 }; + char *msg = "multi"; + nng_iov iov1; + nng_iov iov2; + nng_aio *aio1; + nng_aio *aio2; + char rbuf[32]; + + mc.s_in.sa_family = NNG_AF_INET; + mc.s_in.sa_addr = htonl(0xe0000001); // 224.0.0.1 ... all hosts + + sa1.s_in.sa_family = NNG_AF_INET; + sa1.s_in.sa_addr = htonl(0x7f000001); + + sa2.s_in.sa_family = NNG_AF_INET; + sa2.s_in.sa_addr = htonl(0x7f000001); + + NUTS_PASS(nng_udp_open(&u1, &sa1)); + NUTS_PASS(nng_udp_sockname(u1, &sa1)); + NUTS_PASS(nng_udp_open(&u2, &sa2)); + NUTS_PASS(nng_udp_sockname(u2, &sa2)); + NUTS_PASS(nng_udp_multicast_membership(u1, &mc, true)); + NUTS_PASS(nng_udp_multicast_membership(u2, &mc, true)); + NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); + NUTS_PASS(nng_aio_alloc(&aio2, NULL, NULL)); + + iov1.iov_buf = msg; + iov1.iov_len = strlen(msg) + 1; + NUTS_PASS(nng_aio_set_iov(aio1, 1, &iov1)); + NUTS_PASS(nng_aio_set_input(aio1, 0, &sa2)); + + iov2.iov_buf = rbuf; + iov2.iov_len = sizeof(rbuf); + NUTS_PASS(nng_aio_set_iov(aio2, 1, &iov2)); + NUTS_PASS(nng_aio_set_input(aio2, 0, &ra2)); + + nng_udp_recv(u2, aio2); + nng_udp_send(u1, aio1); + nng_aio_wait(aio1); + nng_aio_wait(aio2); + NUTS_PASS(nng_aio_result(aio1)); + NUTS_PASS(nng_aio_result(aio2)); + + NUTS_MATCH(msg, rbuf); + nng_udp_close(u1); + nng_udp_close(u2); + nng_aio_free(aio1); + nng_aio_free(aio2); } #ifdef NNG_ENABLE_IPV6 @@ -302,20 +371,20 @@ void test_udp_send_v6_from_v4(void) { int rv; - nni_plat_udp *u1; - nng_sockaddr sa; + nng_udp *u1; + nng_sockaddr sa = { 0 }; nng_aio *aio1; nng_iov iov1; char *msg = "nope"; const uint8_t google[16] = { 0x26, 0x07, 0xf8, 0xb0, 0x40, 0x07, 0x40, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e }; + memset(&sa, 0, sizeof(sa)); sa.s_in.sa_family = NNG_AF_INET; sa.s_in.sa_addr = htonl(0x7f000001); - NUTS_PASS(nni_init()); NUTS_PASS(nng_aio_alloc(&aio1, NULL, NULL)); - NUTS_PASS(nni_plat_udp_open(&u1, &sa)); + NUTS_PASS(nng_udp_open(&u1, &sa)); sa.s_in6.sa_family = NNG_AF_INET6; memcpy(sa.s_in6.sa_addr, google, 16); @@ -326,14 +395,14 @@ test_udp_send_v6_from_v4(void) nng_aio_set_iov(aio1, 1, &iov1); nng_aio_set_input(aio1, 0, &sa); - nni_plat_udp_send(u1, aio1); + nng_udp_send(u1, aio1); nng_aio_wait(aio1); rv = nng_aio_result(aio1); NUTS_ASSERT((rv == NNG_EADDRINVAL) || (rv == NNG_ENOTSUP) || (rv == NNG_EUNREACHABLE)); nng_aio_free(aio1); - nni_plat_udp_close(u1); + nng_udp_close(u1); } #endif // NNG_ENABLE_IPV6 @@ -344,6 +413,8 @@ NUTS_TESTS = { { "udp send ipc address", test_udp_send_ipc }, { "udp bogus bind", test_udp_bogus_bind }, { "udp duplicate bind", test_udp_duplicate_bind }, + { "udp multicast membership", test_udp_multicast_membership }, + { "udp multicast send recv", test_udp_multicast_send_recv }, #ifdef NNG_ENABLE_IPV6 { "udp send v6 from v4", test_udp_send_v6_from_v4 }, #endif diff --git a/src/platform/windows/win_udp.c b/src/platform/windows/win_udp.c index 09064e2f..c1c7ef21 100644 --- a/src/platform/windows/win_udp.c +++ b/src/platform/windows/win_udp.c @@ -315,6 +315,101 @@ nni_plat_udp_sockname(nni_plat_udp *udp, nni_sockaddr *sa) return (nni_win_sockaddr2nn(sa, &ss)); } +// Joining a multicast group is different than binding to a multicast +// group. This allows to receive both unicast and multicast at the given +// address. +static int +ip4_multicast_member(nni_plat_udp *udp, SOCKADDR *sa, bool join) +{ + IP_MREQ mreq; + SOCKADDR_IN *sin; + SOCKADDR_STORAGE local; + int sz = sizeof(local); + + if (getsockname(udp->s, (SOCKADDR *) &local, &sz) >= 0) { + if (local.ss_family != AF_INET) { + // address families have to match + return (NNG_EADDRINVAL); + } + sin = (SOCKADDR_IN *) &local; + mreq.imr_interface.s_addr = sin->sin_addr.s_addr; + } else { + mreq.imr_interface.s_addr = INADDR_ANY; + } + + // Determine our local interface + sin = (SOCKADDR_IN *) sa; + + mreq.imr_multiaddr.s_addr = sin->sin_addr.s_addr; + if (setsockopt(udp->s, IPPROTO_IP, + join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, + (const char *) &mreq, sizeof(mreq)) == 0) { + return (0); + } + return (nni_win_error(GetLastError())); +} + +#ifdef NNG_ENABLE_IPV6 +static int +ip6_multicast_member(nni_plat_udp *udp, SOCKADDR *sa, bool join) +{ + IPV6_MREQ mreq; + SOCKADDR_IN6 *sin6; + SOCKADDR_STORAGE local; + int sz = sizeof(local); + + if (getsockname(udp->s, (SOCKADDR *) &local, &sz) >= 0) { + if (local.ss_family != AF_INET6) { + // address families have to match + return (NNG_EADDRINVAL); + } + sin6 = (SOCKADDR_IN6 *) &local; + mreq.ipv6mr_interface = sin6->sin6_scope_id; + } else { + mreq.ipv6mr_interface = 0; + } + + // Determine our local interface + sin6 = (SOCKADDR_IN6 *) sa; + + mreq.ipv6mr_multiaddr = sin6->sin6_addr; + if (setsockopt(udp->s, IPPROTO_IPV6, + join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, + (const char *) &mreq, sizeof(mreq)) == 0) { + return (0); + } + return (nni_win_error(GetLastError())); +} +#endif + +int +nni_plat_udp_multicast_membership( + nni_plat_udp *udp, nni_sockaddr *sa, bool join) +{ + SOCKADDR_STORAGE ss; + socklen_t sz; + int rv; + + sz = nni_win_nn2sockaddr(&ss, sa); + if (sz < 1) { + return (NNG_EADDRINVAL); + } + switch (ss.ss_family) { + case AF_INET: + rv = ip4_multicast_member(udp, (struct sockaddr *) &ss, join); + break; +#ifdef NNG_ENABLE_IPV6 + case AF_INET6: + rv = ip6_multicast_member(udp, (struct sockaddr *) &ss, join); + break; +#endif + default: + rv = NNG_EADDRINVAL; + } + + return (rv); +} + int nni_win_udp_sysinit(void) { |
