// // Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // // 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" #include #include #include #ifdef NNG_PLATFORM_WINDOWS // Modern Windows has an asynchronous resolver, but there are problems // with it, where looking up names in DNS can poison results for other // uses, because the asynchronous resolver *only* considers DNS -- ignoring // host file, WINS, or other naming services. As a result, we just build // our own limited asynchronous resolver with threads. #ifndef NNG_RESOLV_CONCURRENCY #define NNG_RESOLV_CONCURRENCY 4 #endif static nni_mtx resolv_mtx; static nni_cv resolv_cv; static bool resolv_fini; static nni_list resolv_aios; static nni_thr resolv_thrs[NNG_RESOLV_CONCURRENCY]; typedef struct resolv_item resolv_item; struct resolv_item { int family; bool passive; char * host; char * serv; nni_aio * aio; nng_sockaddr *sa; }; static void resolv_free_item(resolv_item *item) { nni_strfree(item->serv); nni_strfree(item->host); NNI_FREE_STRUCT(item); } static void resolv_cancel(nni_aio *aio, void *arg, int rv) { resolv_item *item = arg; nni_mtx_lock(&resolv_mtx); if (item != nni_aio_get_prov_extra(aio, 0)) { nni_mtx_unlock(&resolv_mtx); return; } nni_aio_set_prov_extra(aio, 0, NULL); if (nni_aio_list_active(aio)) { // We have not been picked up by a resolver thread yet, // so we can just discard everything. nni_aio_list_remove(aio); nni_mtx_unlock(&resolv_mtx); resolv_free_item(item); } else { // Resolver still working, so just unlink our AIO to // discard our interest in the results. item->aio = NULL; item->sa = NULL; nni_mtx_unlock(&resolv_mtx); } nni_aio_finish_error(aio, rv); } static int resolv_errno(int rv) { switch (rv) { case 0: return (0); case WSA_NOT_ENOUGH_MEMORY: return (NNG_ENOMEM); case WSAHOST_NOT_FOUND: case WSATYPE_NOT_FOUND: case WSANO_DATA: return (NNG_EADDRINVAL); case WSAEINVAL: return (NNG_EINVAL); case WSAESOCKTNOSUPPORT: case WSAEAFNOSUPPORT: return (NNG_ENOTSUP); default: return (NNG_ESYSERR + rv); } } static int resolv_task(resolv_item *item) { struct addrinfo hints; struct addrinfo *results; struct addrinfo *probe; int rv; results = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; if (item->passive) { hints.ai_flags |= AI_PASSIVE; } hints.ai_family = item->family; hints.ai_socktype = SOCK_STREAM; // Check to see if this is a numeric port number, and if it is // make sure that it's in the valid range (because Windows may // incorrectly simple do a conversion and mask off upper bits. if (item->serv != NULL) { long port; char *end; port = strtol(item->serv, &end, 10); if (*end == '\0') { // we fully converted it as a number... hints.ai_flags |= AI_NUMERICSERV; // Not a valid port number. Fail. if ((port < 0) || (port > 0xffff)) { rv = NNG_EADDRINVAL; goto done; } } } if ((rv = getaddrinfo(item->host, item->serv, &hints, &results)) != 0) { rv = resolv_errno(rv); goto done; } // We only take the first matching address. Presumably // DNS load balancing is done by the resolver/server. rv = NNG_EADDRINVAL; for (probe = results; probe != NULL; probe = probe->ai_next) { if ((probe->ai_addr->sa_family == AF_INET) || (probe->ai_addr->sa_family == AF_INET6)) { break; } } nni_mtx_lock(&resolv_mtx); if ((probe != NULL) && (item->aio != NULL)) { struct sockaddr_in * sin; struct sockaddr_in6 *sin6; nni_sockaddr * sa; sa = item->sa; switch (probe->ai_addr->sa_family) { case AF_INET: rv = 0; sin = (void *) probe->ai_addr; sa->s_in.sa_family = NNG_AF_INET; sa->s_in.sa_port = sin->sin_port; sa->s_in.sa_addr = sin->sin_addr.s_addr; break; case AF_INET6: rv = 0; sin6 = (void *) probe->ai_addr; sa->s_in6.sa_family = NNG_AF_INET6; sa->s_in6.sa_port = sin6->sin6_port; sa->s_in6.sa_scope = sin6->sin6_scope_id; memcpy(sa->s_in6.sa_addr, sin6->sin6_addr.s6_addr, 16); break; } } nni_mtx_unlock(&resolv_mtx); done: if (results != NULL) { freeaddrinfo(results); } return (rv); } void nni_resolv_ip(const char *host, const char *serv, int family, bool passive, nng_sockaddr *sa, nni_aio *aio) { resolv_item *item; int fam; int rv; if (nni_aio_begin(aio) != 0) { return; } switch (family) { case NNG_AF_INET: fam = AF_INET; break; case NNG_AF_INET6: fam = AF_INET6; break; case NNG_AF_UNSPEC: fam = AF_UNSPEC; break; default: nni_aio_finish_error(aio, NNG_ENOTSUP); return; } if ((item = NNI_ALLOC_STRUCT(item)) == NULL) { nni_aio_finish_error(aio, NNG_ENOMEM); return; } if (host == NULL) { item->host = NULL; } else if ((item->host = nni_strdup(host)) == NULL) { nni_aio_finish_error(aio, NNG_ENOMEM); resolv_free_item(item); return; } if (serv == NULL) { item->serv = NULL; } else if ((item->serv = nni_strdup(serv)) == NULL) { nni_aio_finish_error(aio, NNG_ENOMEM); resolv_free_item(item); return; } item->sa = sa; item->passive = passive; item->aio = aio; item->family = fam; nni_mtx_lock(&resolv_mtx); if (resolv_fini) { rv = NNG_ECLOSED; } else { nni_aio_set_prov_extra(aio, 0, item); rv = nni_aio_schedule(aio, resolv_cancel, item); } if (rv != 0) { nni_mtx_unlock(&resolv_mtx); resolv_free_item(item); nni_aio_finish_error(aio, rv); return; } nni_list_append(&resolv_aios, aio); nni_cv_wake1(&resolv_cv); nni_mtx_unlock(&resolv_mtx); } void resolv_worker(void *notused) { NNI_ARG_UNUSED(notused); nni_mtx_lock(&resolv_mtx); for (;;) { nni_aio * aio; resolv_item *item; int rv; if ((aio = nni_list_first(&resolv_aios)) == NULL) { if (resolv_fini) { break; } nni_cv_wait(&resolv_cv); continue; } item = nni_aio_get_prov_extra(aio, 0); nni_aio_list_remove(aio); // Now attempt to do the work. This runs synchronously. nni_mtx_unlock(&resolv_mtx); rv = resolv_task(item); nni_mtx_lock(&resolv_mtx); // Check to make sure we were not canceled. if ((aio = item->aio) != NULL) { nni_aio_set_prov_extra(aio, 0, NULL); item->aio = NULL; item->sa = NULL; nni_aio_finish(aio, rv, 0); } resolv_free_item(item); } nni_mtx_unlock(&resolv_mtx); } int parse_ip(const char *addr, nng_sockaddr *sa, bool want_port) { struct addrinfo hints; struct addrinfo *results; int rv; bool v6 = false; bool wrapped = false; char * port; char * host; char * buf; size_t buf_len; if (addr == NULL) { addr = ""; } buf_len = strlen(addr) + 1; if ((buf = nni_alloc(buf_len)) == NULL) { return (NNG_ENOMEM); } memcpy(buf, addr, buf_len); host = buf; if (*host == '[') { v6 = true; wrapped = true; host++; } else { char *s; for (s = host; *s != '\0'; s++) { if (*s == '.') { break; } if (*s == ':') { v6 = true; break; } } } for (port = host; *port != '\0'; port++) { if (wrapped) { if (*port == ']') { *port++ = '\0'; wrapped = false; break; } } else if (!v6) { if (*port == ':') { break; } } } if (wrapped) { // Never got the closing bracket. rv = NNG_EADDRINVAL; goto done; } if ((!want_port) && (*port != '\0')) { rv = NNG_EADDRINVAL; goto done; } else if (*port == ':') { *port++ = '\0'; } if (*port == '\0') { port = "0"; } memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_NUMERICHOST | AI_PASSIVE; if (v6) { hints.ai_family = AF_INET6; } rv = getaddrinfo(host, port, &hints, &results); if ((rv != 0) || (results == NULL)) { rv = nni_win_error(rv); goto done; } nni_win_sockaddr2nn(sa, (void *) results->ai_addr); freeaddrinfo(results); done: nni_free(buf, buf_len); return (rv); } int nni_parse_ip(const char *addr, nni_sockaddr *sa) { return (parse_ip(addr, sa, false)); } int nni_parse_ip_port(const char *addr, nni_sockaddr *sa) { return (parse_ip(addr, sa, true)); } int nni_win_resolv_sysinit(void) { nni_mtx_init(&resolv_mtx); nni_cv_init(&resolv_cv, &resolv_mtx); nni_aio_list_init(&resolv_aios); resolv_fini = false; for (int i = 0; i < NNG_RESOLV_CONCURRENCY; i++) { int rv = nni_thr_init(&resolv_thrs[i], resolv_worker, NULL); if (rv != 0) { nni_win_resolv_sysfini(); return (rv); } nni_thr_set_name(&resolv_thrs[i], "nng:resolver"); } for (int i = 0; i < NNG_RESOLV_CONCURRENCY; i++) { nni_thr_run(&resolv_thrs[i]); } return (0); } void nni_win_resolv_sysfini(void) { nni_mtx_lock(&resolv_mtx); resolv_fini = true; nni_cv_wake(&resolv_cv); nni_mtx_unlock(&resolv_mtx); for (int i = 0; i < NNG_RESOLV_CONCURRENCY; i++) { nni_thr_fini(&resolv_thrs[i]); } nni_cv_fini(&resolv_cv); nni_mtx_fini(&resolv_mtx); } #endif // NNG_PLATFORM_WINDOWS