aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/core/aio.h6
-rw-r--r--src/core/defs.h6
-rw-r--r--src/core/platform.h9
-rw-r--r--src/platform/posix/posix_config.h1
-rw-r--r--src/platform/posix/posix_epdesc.c29
-rw-r--r--src/platform/posix/posix_resolv_gai.c314
7 files changed, 349 insertions, 17 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f7f87a10..9392e423 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -93,6 +93,7 @@ set (NNG_SOURCES
platform/posix/posix_pipedesc.c
platform/posix/posix_pollq_poll.c
platform/posix/posix_rand.c
+ platform/posix/posix_resolv_gai.c
platform/posix/posix_socket.c
platform/posix/posix_thread.c
diff --git a/src/core/aio.h b/src/core/aio.h
index d3c238f6..ee2b084e 100644
--- a/src/core/aio.h
+++ b/src/core/aio.h
@@ -39,12 +39,12 @@ struct nni_aio {
nni_msg * a_msg;
// Connect/accept operations.
- nni_sockaddr * a_remaddr;
- nni_sockaddr * a_locaddr;
void * a_endpt; // opaque endpoint handle
void * a_pipe; // opaque pipe handle
- // TBD: Resolver operations.
+ // Resolver operations.
+ nni_sockaddr * a_addrs;
+ int a_naddrs;
// Provider-use fields.
void (*a_prov_cancel)(nni_aio *);
diff --git a/src/core/defs.h b/src/core/defs.h
index 1c44e9af..5db68c1c 100644
--- a/src/core/defs.h
+++ b/src/core/defs.h
@@ -72,8 +72,10 @@ typedef struct {
#define NNI_SECOND (1000000)
// Structure allocation conveniences.
-#define NNI_ALLOC_STRUCT(s) nni_alloc(sizeof (*s))
-#define NNI_FREE_STRUCT(s) nni_free((s), sizeof (*s))
+#define NNI_ALLOC_STRUCT(s) nni_alloc(sizeof (*s))
+#define NNI_FREE_STRUCT(s) nni_free((s), sizeof (*s))
+#define NNI_ALLOC_STRUCTS(s, n) nni_alloc(sizeof (*s) * n)
+#define NNI_FREE_STRUCTS(s, n) nni_free(s, sizeof (*s) * n)
#define NNI_PUT16(ptr, u) \
do { \
diff --git a/src/core/platform.h b/src/core/platform.h
index 971c6a5d..0dcab40a 100644
--- a/src/core/platform.h
+++ b/src/core/platform.h
@@ -242,6 +242,15 @@ extern void nni_plat_tcp_aio_send(nni_plat_tcpsock *, nni_aio *);
// full, or an error condition occurs.
extern void nni_plat_tcp_aio_recv(nni_plat_tcpsock *, nni_aio *);
+// nni_plat_tcp_resolv resolves a TCP name asynchronously. The family
+// should be one of NNG_AF_INET, NNG_AF_INET6, or NNG_AF_UNSPEC. The
+// first two constrain the name to those families, while the third will
+// return names of either family. The passive flag indicates that the
+// name will be used for bind(), otherwise the name will be used with
+// connect(). The host part may be NULL only if passive is true.
+extern void nni_plat_tcp_resolv(const char *, const char *, int, int,
+ nni_aio *);
+
//
// IPC (UNIX Domain Sockets & Named Pipes) Support.
//
diff --git a/src/platform/posix/posix_config.h b/src/platform/posix/posix_config.h
index 7fc2f5d5..d2a5eb90 100644
--- a/src/platform/posix/posix_config.h
+++ b/src/platform/posix/posix_config.h
@@ -59,3 +59,4 @@
#endif // CLOCK_REALTIME
#define NNG_USE_POSIX_POLLQ_POLL 1
+#define NNG_USE_POSIX_RESOLV_GAI 1
diff --git a/src/platform/posix/posix_epdesc.c b/src/platform/posix/posix_epdesc.c
index 29420c26..3d703e97 100644
--- a/src/platform/posix/posix_epdesc.c
+++ b/src/platform/posix/posix_epdesc.c
@@ -24,6 +24,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
+#include <netdb.h>
#ifdef SOCK_CLOEXEC
#define NNI_STREAM_SOCKTYPE (SOCK_STREAM | SOCK_CLOEXEC)
@@ -31,19 +32,23 @@
#define NNI_STREAM_SOCKTYPE SOCK_STREAM
#endif
-
struct nni_posix_epdesc {
- int fd;
- nni_list connectq;
- nni_list acceptq;
- nni_posix_pollq_node node;
- nni_posix_pollq * pq;
- struct sockaddr_storage locaddr;
- struct sockaddr_storage remaddr;
- socklen_t loclen;
- socklen_t remlen;
- const char * url;
- nni_mtx mtx;
+ int fd;
+ nni_list connectq;
+ nni_list acceptq;
+ nni_posix_pollq_node node;
+ nni_posix_pollq * pq;
+ struct sockaddr_storage locaddr;
+ struct sockaddr_storage remaddr;
+ socklen_t loclen;
+ socklen_t remlen;
+ const char * url;
+ struct sockaddr_storage * remaddrs;
+ struct sockaddr_storage * locaddrs;
+ int nremaddrs;
+ int nlocaddrs;
+ int server;
+ nni_mtx mtx;
};
diff --git a/src/platform/posix/posix_resolv_gai.c b/src/platform/posix/posix_resolv_gai.c
new file mode 100644
index 00000000..2b2a8436
--- /dev/null
+++ b/src/platform/posix/posix_resolv_gai.c
@@ -0,0 +1,314 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+
+#include "core/nng_impl.h"
+
+#ifdef NNG_USE_POSIX_RESOLV_GAI
+#include "platform/posix/posix_aio.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+
+// We use a single resolver taskq - but we allocate a few threads
+// for it to ensure that names can be looked up concurrently. This isn't
+// as elegant or scaleable as a true asynchronous resolver would be, but
+// it has the advantage of being fairly portable, and concurrent enough for
+// the vast, vast majority of use cases. The total thread count can be
+// changed with this define. Note that some platforms may not have a
+// thread-safe getaddrinfo(). In that case they should set this to 1.
+
+#ifndef NNG_POSIX_RESOLV_CONCURRENCY
+#define NNG_POSIX_RESOLV_CONCURRENCY 4
+#endif
+
+
+static nni_taskq *nni_posix_resolv_tq = NULL;
+static nni_mtx nni_posix_resolv_mtx;
+
+typedef struct nni_posix_resolv_item nni_posix_resolv_item;
+struct nni_posix_resolv_item {
+ int family;
+ int passive;
+ const char * name;
+ const char * serv;
+ int proto;
+ nni_aio * aio;
+ nni_taskq_ent tqe;
+};
+
+
+static void
+nni_posix_resolv_finish(nni_posix_resolv_item *item, int rv)
+{
+ nni_aio *aio = item->aio;
+
+ aio->a_prov_data = NULL;
+ nni_aio_finish(aio, rv, 0);
+ NNI_FREE_STRUCT(item);
+}
+
+
+static void
+nni_posix_resolv_cancel(nni_aio *aio)
+{
+ nni_posix_resolv_item *item;
+
+ nni_mtx_lock(&nni_posix_resolv_mtx);
+ if ((item = aio->a_prov_data) == NULL) {
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+ return;
+ }
+ aio->a_prov_data = NULL;
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+ nni_taskq_cancel(nni_posix_resolv_tq, &item->tqe);
+ NNI_FREE_STRUCT(item);
+}
+
+
+static int
+nni_posix_gai_errno(int rv)
+{
+ switch (rv) {
+ case 0:
+ return (0);
+
+ case EAI_MEMORY:
+ return (NNG_ENOMEM);
+
+ case EAI_SYSTEM:
+ return (nni_plat_errno(errno));
+
+ case EAI_NONAME:
+ case EAI_SERVICE:
+ return (NNG_EADDRINVAL);
+
+ case EAI_BADFLAGS:
+ return (NNG_EINVAL);
+
+ case EAI_SOCKTYPE:
+ return (NNG_ENOTSUP);
+
+ default:
+ return (NNG_ESYSERR);
+ }
+}
+
+
+static void
+nni_posix_resolv_task(void *arg)
+{
+ nni_posix_resolv_item *item = arg;
+ nni_aio *aio = item->aio;
+ struct addrinfo hints;
+ struct addrinfo *results;
+ struct addrinfo *probe;
+ int i, rv;
+
+ results = NULL;
+
+ switch (item->family) {
+ case AF_INET:
+ case AF_INET6:
+ case AF_UNSPEC:
+ // We treat these all as IP addresses. The service and the
+ // host part are split.
+ memset(&hints, 0, sizeof (hints));
+ if (item->passive) {
+ hints.ai_flags |= AI_PASSIVE;
+ }
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ hints.ai_protocol = item->proto;
+ hints.ai_family = item->family;
+ if (item->family == AF_INET6) {
+ // We prefer to have v4mapped addresses if a remote
+ // v4 address isn't avaiable. And we prefer to only
+ // do this if we actually support v6.
+#if defined(AI_V4MAPPED_CFG)
+ hints.ai_flags |= AI_V4MAPPED_CFG;
+#elif defined(AI_V4MAPPED)
+ hints.ai_flags |= AI_V4MAPPED;
+#endif
+ }
+
+ rv = getaddrinfo(item->name, item->serv, &hints, &results);
+ if (rv != 0) {
+ rv = nni_posix_gai_errno(rv);
+ break;
+ }
+
+ // Count the total number of results.
+ aio->a_naddrs = 0;
+ for (probe = results; probe != NULL; probe = probe->ai_next) {
+ // Only count v4 and v6 addresses.
+ switch (probe->ai_addr->sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ aio->a_naddrs++;
+ break;
+ }
+ }
+ // If the only results were not IPv4 or IPv6...
+ if (aio->a_addrs == 0) {
+ rv = NNG_EADDRINVAL;
+ break;
+ }
+ aio->a_addrs = NNI_ALLOC_STRUCTS(aio->a_addrs, aio->a_naddrs);
+ if (aio->a_addrs == NULL) {
+ aio->a_naddrs = 0;
+ rv = NNG_ENOMEM;
+ break;
+ }
+ i = 0;
+ for (probe = results; probe != NULL; probe = probe->ai_next) {
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ nng_sockaddr *sa = &aio->a_addrs[i];
+
+ switch (probe->ai_addr->sa_family) {
+ case AF_INET:
+ sin = (void *) probe->ai_addr;
+ sa->s_un.s_in.sa_family = NNG_AF_INET;
+ sa->s_un.s_in.sa_port = sin->sin_port;
+ sa->s_un.s_in.sa_addr = sin->sin_addr.s_addr;
+ i++;
+ break;
+ case AF_INET6:
+ sin6 = (void *) probe->ai_addr;
+ sa->s_un.s_in6.sa_family = NNG_AF_INET6;
+ sa->s_un.s_in6.sa_port = sin6->sin6_port;
+ memcpy(sa->s_un.s_in6.sa_addr,
+ sin6->sin6_addr.s6_addr, 16);
+ i++;
+ break;
+ default:
+ // Other address types are ignored.
+ break;
+ }
+ }
+ // Resolution complete!
+ rv = 0;
+ break;
+
+ default:
+ // Some other family requested we don't understand.
+ rv = NNG_ENOTSUP;
+ break;
+ }
+
+done:
+ if (results != NULL) {
+ freeaddrinfo(results);
+ }
+ nni_mtx_lock(&nni_posix_resolv_mtx);
+ nni_posix_resolv_finish(item, rv);
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+}
+
+
+static void
+nni_posix_resolv_ip(const char *host, const char *serv, int passive,
+ int family, int proto, nni_aio *aio)
+{
+ nni_posix_resolv_item *item;
+ int rv;
+
+ if ((item = NNI_ALLOC_STRUCT(item)) == NULL) {
+ nni_aio_finish(aio, NNG_ENOMEM, 0);
+ return;
+ }
+
+ nni_taskq_ent_init(&item->tqe, nni_posix_resolv_task, item);
+
+ switch (family) {
+ case NNG_AF_INET:
+ item->family = AF_INET;
+ break;
+ case NNG_AF_INET6:
+ item->family = AF_INET6;
+ break;
+ case NNG_AF_UNSPEC:
+ item->family = AF_UNSPEC;
+ break;
+ }
+ // NB: host and serv must remain valid until this is completed.
+ item->passive = passive;
+ item->name = host;
+ item->serv = serv;
+ item->proto = proto;
+
+ nni_mtx_lock(&nni_posix_resolv_mtx);
+ // If we were stopped, we're done...
+ if ((rv = nni_aio_start(aio, nni_posix_resolv_cancel, item)) != 0) {
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+ NNI_FREE_STRUCT(item);
+ return;
+ }
+ if ((rv = nni_taskq_dispatch(nni_posix_resolv_tq, &item->tqe)) != 0) {
+ nni_posix_resolv_finish(item, rv);
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+ return;
+ }
+ nni_mtx_unlock(&nni_posix_resolv_mtx);
+}
+
+
+void
+nni_plat_resolv_tcp(const char *host, const char *serv, int family,
+ int passive, nni_aio *aio)
+{
+ nni_posix_resolv_ip(host, serv, passive, family, IPPROTO_TCP, aio);
+}
+
+
+int
+nni_posix_resolv_init(void)
+{
+ int rv;
+
+ if ((rv = nni_mtx_init(&nni_posix_resolv_mtx)) != 0) {
+ return (rv);
+ }
+ if ((rv = nni_taskq_init(&nni_posix_resolv_tq, 4)) != 0) {
+ nni_mtx_fini(&nni_posix_resolv_mtx);
+ return (rv);
+ }
+ return (0);
+}
+
+
+void
+nni_posix_resolv_fini(void)
+{
+ if (nni_posix_resolv_tq != NULL) {
+ nni_taskq_fini(nni_posix_resolv_tq);
+ nni_posix_resolv_tq = NULL;
+ }
+ nni_mtx_fini(&nni_posix_resolv_mtx);
+}
+
+
+#else
+
+// Suppress empty symbols warnings in ranlib.
+int nni_posix_resolv_gai_not_used = 0;
+
+#endif // NNG_USE_POSIX_RESOLV_GAI