diff options
Diffstat (limited to 'src/supplemental')
| -rw-r--r-- | src/supplemental/http/client.c | 115 | ||||
| -rw-r--r-- | src/supplemental/http/http.h | 37 | ||||
| -rw-r--r-- | src/supplemental/http/server.c | 146 | ||||
| -rw-r--r-- | src/supplemental/tls/mbedtls/tls.c | 132 | ||||
| -rw-r--r-- | src/supplemental/tls/tls.h | 6 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.c | 329 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.h | 6 |
7 files changed, 514 insertions, 257 deletions
diff --git a/src/supplemental/http/client.c b/src/supplemental/http/client.c index b1794f93..7542043d 100644 --- a/src/supplemental/http/client.c +++ b/src/supplemental/http/client.c @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 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 // copy of which should be located in the distribution where this @@ -19,12 +19,12 @@ #include "http.h" struct nni_http_client { - nng_sockaddr addr; nni_list aios; nni_mtx mtx; bool closed; nng_tls_config * tls; nni_aio * connaio; + nni_url * url; nni_plat_tcp_ep *tep; }; @@ -92,26 +92,98 @@ nni_http_client_fini(nni_http_client *c) nni_tls_config_fini(c->tls); } #endif + if (c->url != NULL) { + nni_url_free(c->url); + } NNI_FREE_STRUCT(c); } int -nni_http_client_init(nni_http_client **cp, nng_sockaddr *sa) +nni_http_client_init(nni_http_client **cp, const char *urlstr) { - int rv; - + int rv; + nni_url * url; nni_http_client *c; - if ((c = NNI_ALLOC_STRUCT(c)) == NULL) { - return (NNG_ENOMEM); + nni_aio * aio; + nni_sockaddr sa; + char * host; + char * port; + + if ((rv = nni_url_parse(&url, urlstr)) != 0) { + return (rv); + } + + if (strlen(url->u_hostname) == 0) { + // We require a valid hostname. + nni_url_free(url); + return (NNG_EADDRINVAL); + } + if ((strcmp(url->u_scheme, "http") != 0) && +#ifdef NNG_SUPP_TLS + (strcmp(url->u_scheme, "https") != 0) && + (strcmp(url->u_scheme, "wss") != 0) && +#endif + (strcmp(url->u_scheme, "ws") != 0)) { + return (NNG_EADDRINVAL); } - c->addr = *sa; - rv = nni_plat_tcp_ep_init(&c->tep, NULL, &c->addr, NNI_EP_MODE_DIAL); + + // For now we are looking up the address. We would really like + // to do this later, but we need TcP support for this. One + // imagines the ability to create a tcp dialer that does the + // necessary DNS lookups, etc. all asynchronously. + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + nni_url_free(url); + return (rv); + } + aio->a_addr = &sa; + host = (strlen(url->u_hostname) != 0) ? url->u_hostname : NULL; + port = (strlen(url->u_port) != 0) ? url->u_port : NULL; + nni_plat_tcp_resolv(host, port, NNG_AF_UNSPEC, false, aio); + nni_aio_wait(aio); + rv = nni_aio_result(aio); + nni_aio_fini(aio); if (rv != 0) { - NNI_FREE_STRUCT(c); + nni_url_free(url); return (rv); } + + if ((c = NNI_ALLOC_STRUCT(c)) == NULL) { + return (NNG_ENOMEM); + } nni_mtx_init(&c->mtx); nni_aio_list_init(&c->aios); + c->url = url; + +#ifdef NNG_SUPP_TLS + if ((strcmp(url->u_scheme, "https") == 0) || + (strcmp(url->u_scheme, "wss") == 0)) { + rv = nni_tls_config_init(&c->tls, NNG_TLS_MODE_CLIENT); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } + // Take the server name right from the client URL. We only + // consider the name, as the port is never part of the + // certificate. + rv = nng_tls_config_server_name(c->tls, url->u_hostname); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } + + // Note that the application has to supply the location of + // certificates. We could probably use a default based + // on environment or common locations used by OpenSSL, but + // as there is no way to *unload* the cert file, lets not + // do that. (We might want to consider a mode to reset.) + } +#endif + + rv = nni_plat_tcp_ep_init(&c->tep, NULL, &sa, NNI_EP_MODE_DIAL); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } if ((rv = nni_aio_init(&c->connaio, http_conn_done, c)) != 0) { nni_http_client_fini(c); @@ -121,10 +193,10 @@ nni_http_client_init(nni_http_client **cp, nng_sockaddr *sa) return (0); } -#ifdef NNG_SUPP_TLS int nni_http_client_set_tls(nni_http_client *c, nng_tls_config *tls) { +#ifdef NNG_SUPP_TLS nng_tls_config *old; nni_mtx_lock(&c->mtx); old = c->tls; @@ -137,8 +209,27 @@ nni_http_client_set_tls(nni_http_client *c, nng_tls_config *tls) nni_tls_config_fini(old); } return (0); +#else + return (NNG_EINVAL); +#endif } + +int +nni_http_client_get_tls(nni_http_client *c, nng_tls_config **tlsp) +{ +#ifdef NNG_SUPP_TLS + nni_mtx_lock(&c->mtx); + if (c->tls == NULL) { + nni_mtx_unlock(&c->mtx); + return (NNG_EINVAL); + } + *tlsp = c->tls; + nni_mtx_unlock(&c->mtx); + return (0); +#else + return (NNG_ENOTSUP); #endif +} static void http_connect_cancel(nni_aio *aio, int rv) diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h index 0f3affc0..06394fdd 100644 --- a/src/supplemental/http/http.h +++ b/src/supplemental/http/http.h @@ -222,11 +222,14 @@ typedef struct { } nni_http_handler; // nni_http_server will look for an existing server with the same -// socket address, or create one if one does not exist. The servers +// name and port, or create one if one does not exist. The servers // are reference counted to permit sharing the server object across -// multiple subsystems. The sockaddr matching is very limited though, -// and the addresses must match *exactly*. -extern int nni_http_server_init(nni_http_server **, nng_sockaddr *); +// multiple subsystems. The URL hostname matching is very limited, +// and the names must match *exactly* (without DNS resolution). Unless +// a restricted binding is required, we recommend using a URL consisting +// of an empty host name, such as http:// or https:// -- this would +// convert to binding to the default port on all interfaces on the host. +extern int nni_http_server_init(nni_http_server **, const char *); // nni_http_server_fini drops the reference count on the server, and // if this was the last reference, closes down the server and frees @@ -245,9 +248,17 @@ extern void nni_http_server_del_handler(nni_http_server *, void *); // nni_http_server_set_tls adds a TLS configuration to the server, // and enables the use of it. This returns NNG_EBUSY if the server is -// already started. +// already started. This wipes out the entire TLS configuration on the +// server client, so the caller must have configured it reasonably. +// This API is not recommended unless the caller needs complete control +// over the TLS configuration. extern int nni_http_server_set_tls(nni_http_server *, nng_tls_config *); +// nni_http_server_get_tls obtains the TLS configuration if one is present, +// or returns NNG_EINVAL. The TLS configuration is invalidated if the +// nni_http_server_set_tls function is called, so be careful. +extern int nni_http_server_get_tls(nni_http_server *, nng_tls_config **); + // nni_http_server_start starts listening on the supplied port. extern int nni_http_server_start(nni_http_server *); @@ -279,9 +290,21 @@ extern int nni_http_server_add_file(nni_http_server *, const char *host, typedef struct nni_http_client nni_http_client; -extern int nni_http_client_init(nni_http_client **, nng_sockaddr *); +// https vs. http; would also allow us to defer DNS lookups til later. +extern int nni_http_client_init(nni_http_client **, const char *); extern void nni_http_client_fini(nni_http_client *); -extern int nni_http_client_set_tls(nni_http_client *, nng_tls_config *); + +// nni_http_client_set_tls sets the TLS configuration. This wipes out +// the entire TLS configuration on the client, so the caller must have +// configured it reasonably. This API is not recommended unless the +// caller needs complete control over the TLS configuration. +extern int nni_http_client_set_tls(nni_http_client *, nng_tls_config *); + +// nni_http_client_get_tls obtains the TLS configuration if one is present, +// or returns NNG_EINVAL. The supplied TLS configuration object may +// be invalidated by any future calls to nni_http_client_set_tls. +extern int nni_http_client_get_tls(nni_http_client *, nng_tls_config **); + extern void nni_http_client_connect(nni_http_client *, nni_aio *); #endif // NNG_SUPPLEMENTAL_HTTP_HTTP_H diff --git a/src/supplemental/http/server.c b/src/supplemental/http/server.c index 01f1230d..700ec536 100644 --- a/src/supplemental/http/server.c +++ b/src/supplemental/http/server.c @@ -69,6 +69,7 @@ struct nni_http_server { nng_tls_config * tls; nni_aio * accaio; nni_plat_tcp_ep *tep; + nni_url * url; }; static nni_list http_servers; @@ -78,13 +79,15 @@ static void http_handler_fini(http_handler *); static void http_sconn_reap(void *arg) { - http_sconn *sc = arg; + http_sconn * sc = arg; + nni_http_server *s = sc->server; NNI_ASSERT(!sc->finished); sc->finished = true; nni_aio_stop(sc->rxaio); nni_aio_stop(sc->txaio); nni_aio_stop(sc->txdataio); nni_aio_stop(sc->cbaio); + if (sc->http != NULL) { nni_http_fini(sc->http); } @@ -98,6 +101,17 @@ http_sconn_reap(void *arg) nni_aio_fini(sc->txaio); nni_aio_fini(sc->txdataio); nni_aio_fini(sc->cbaio); + + // Now it is safe to release our reference on the server. + nni_mtx_lock(&s->mtx); + if (nni_list_node_active(&sc->node)) { + nni_list_remove(&s->conns, sc); + if (nni_list_empty(&s->conns)) { + nni_cv_wake(&s->cv); + } + } + nni_mtx_unlock(&s->mtx); + NNI_FREE_STRUCT(sc); } @@ -120,13 +134,6 @@ http_sconn_close_locked(http_sconn *sc) NNI_ASSERT(!sc->finished); sc->closed = true; - // Close the underlying transport. - if (nni_list_node_active(&sc->node)) { - nni_list_remove(&s->conns, sc); - if (nni_list_empty(&s->conns)) { - nni_cv_wake(&s->cv); - } - } nni_aio_cancel(sc->rxaio, NNG_ECLOSED); nni_aio_cancel(sc->txaio, NNG_ECLOSED); nni_aio_cancel(sc->txdataio, NNG_ECLOSED); @@ -144,10 +151,6 @@ http_sconn_close(http_sconn *sc) nni_http_server *s; s = sc->server; - if (sc->closed) { - return; - } - nni_mtx_lock(&s->mtx); http_sconn_close_locked(sc); nni_mtx_unlock(&s->mtx); @@ -631,6 +634,9 @@ http_server_fini(nni_http_server *s) http_handler_fini(h); } nni_mtx_unlock(&s->mtx); + if (s->url != NULL) { + nni_url_free(s->url); + } #ifdef NNG_SUPP_TLS if (s->tls != NULL) { nni_tls_config_fini(s->tls); @@ -655,14 +661,34 @@ nni_http_server_fini(nni_http_server *s) } static int -http_server_init(nni_http_server **serverp, nng_sockaddr *sa) +http_server_init(nni_http_server **serverp, nni_url *url) { nni_http_server *s; int rv; + const char * host; + const char * port; + nni_aio * aio; + + host = url->u_hostname; + if (strlen(host) == 0) { + host = NULL; + } + port = url->u_port; + if ((strcmp(url->u_scheme, "http") != 0) && +#ifdef NNG_SUPP_TLS + (strcmp(url->u_scheme, "https") != 0) && + (strcmp(url->u_scheme, "wss") != 0) && +#endif + (strcmp(url->u_scheme, "ws") != 0)) { + nni_url_free(url); + return (NNG_EADDRINVAL); + } if ((s = NNI_ALLOC_STRUCT(s)) == NULL) { + nni_url_free(url); return (NNG_ENOMEM); } + s->url = url; nni_mtx_init(&s->mtx); nni_cv_init(&s->cv, &s->mtx); NNI_LIST_INIT(&s->handlers, http_handler, node); @@ -671,49 +697,71 @@ http_server_init(nni_http_server **serverp, nng_sockaddr *sa) http_server_fini(s); return (rv); } - s->addr = *sa; - *serverp = s; +#ifdef NNG_SUPP_TLS + if ((strcmp(url->u_scheme, "https") == 0) || + (strcmp(url->u_scheme, "wss") == 0)) { + rv = nni_tls_config_init(&s->tls, NNG_TLS_MODE_SERVER); + if (rv != 0) { + http_server_fini(s); + return (rv); + } + } +#endif + + // Do the DNS lookup *now*. This means that this is synchronous, + // but it should be fast, since it should either resolve as a number, + // or resolve locally, without having to hit up DNS. + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + http_server_fini(s); + return (rv); + } + aio->a_addr = &s->addr; + host = (strlen(url->u_hostname) != 0) ? url->u_hostname : NULL; + port = (strlen(url->u_port) != 0) ? url->u_port : NULL; + nni_plat_tcp_resolv(host, port, NNG_AF_UNSPEC, true, aio); + nni_aio_wait(aio); + rv = nni_aio_result(aio); + nni_aio_fini(aio); + if (rv != 0) { + http_server_fini(s); + return (rv); + } + s->refcnt = 1; + *serverp = s; return (0); } int -nni_http_server_init(nni_http_server **serverp, nng_sockaddr *sa) +nni_http_server_init(nni_http_server **serverp, const char *urlstr) { int rv; nni_http_server *s; + nni_url * url; + + if ((rv = nni_url_parse(&url, urlstr)) != 0) { + return (rv); + } nni_initialize(&http_server_initializer); nni_mtx_lock(&http_servers_lk); NNI_LIST_FOREACH (&http_servers, s) { - switch (sa->s_un.s_family) { - case NNG_AF_INET: - if (memcmp(&s->addr.s_un.s_in, &sa->s_un.s_in, - sizeof(sa->s_un.s_in)) == 0) { - *serverp = s; - s->refcnt++; - nni_mtx_unlock(&http_servers_lk); - return (0); - } - break; - case NNG_AF_INET6: - if (memcmp(&s->addr.s_un.s_in6, &sa->s_un.s_in6, - sizeof(sa->s_un.s_in6)) == 0) { - *serverp = s; - s->refcnt++; - nni_mtx_unlock(&http_servers_lk); - return (0); - } - break; + if ((strcmp(url->u_port, s->url->u_port) == 0) && + (strcmp(url->u_hostname, s->url->u_hostname) == 0)) { + nni_url_free(url); + *serverp = s; + s->refcnt++; + nni_mtx_unlock(&http_servers_lk); + return (0); } } // We didn't find a server, try to make a new one. - if ((rv = http_server_init(&s, sa)) == 0) { - s->addr = *sa; - s->refcnt = 1; + if ((rv = http_server_init(&s, url)) == 0) { nni_list_append(&http_servers, s); *serverp = s; + } else { + nni_url_free(url); } nni_mtx_unlock(&http_servers_lk); @@ -724,7 +772,6 @@ static int http_server_start(nni_http_server *s) { int rv; - rv = nni_plat_tcp_ep_init(&s->tep, &s->addr, NULL, NNI_EP_MODE_LISTEN); if (rv != 0) { return (rv); @@ -1116,10 +1163,10 @@ nni_http_server_add_static(nni_http_server *s, const char *host, return (0); } -#ifdef NNG_SUPP_TLS int nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) { +#ifdef NNG_SUPP_TLS nng_tls_config *old; nni_mtx_lock(&s->mtx); if (s->starts) { @@ -1136,8 +1183,27 @@ nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) nni_tls_config_fini(old); } return (0); +#else + return (NNG_ENOTSUP); +#endif } + +int +nni_http_server_get_tls(nni_http_server *s, nng_tls_config **tp) +{ +#ifdef NNG_SUPP_TLS + nni_mtx_lock(&s->mtx); + if (s->tls == NULL) { + nni_mtx_unlock(&s->mtx); + return (NNG_EINVAL); + } + *tp = s->tls; + nni_mtx_unlock(&s->mtx); + return (0); +#else + return (NNG_ENOTSUP); #endif +} static int http_server_sys_init(void) diff --git a/src/supplemental/tls/mbedtls/tls.c b/src/supplemental/tls/mbedtls/tls.c index c37d3d13..4e846f98 100644 --- a/src/supplemental/tls/mbedtls/tls.c +++ b/src/supplemental/tls/mbedtls/tls.c @@ -92,8 +92,6 @@ struct nng_tls_config { #endif mbedtls_x509_crt ca_certs; mbedtls_x509_crl crl; - bool have_ca_certs; - bool have_crl; int refcnt; // servers increment the reference @@ -275,27 +273,33 @@ nni_tls_fini(nni_tls *tp) NNI_FREE_STRUCT(tp); } -void -nni_tls_strerror(int errnum, char *buf, size_t sz) -{ - if (errnum & NNG_ETRANERR) { - errnum &= ~NNG_ETRANERR; - errnum = -errnum; - - mbedtls_strerror(errnum, buf, sz); - } else { - (void) snprintf(buf, sz, "%s", nng_strerror(errnum)); - } -} - // nni_tls_mkerr converts an mbed error to an NNG error. In all cases // we just encode with NNG_ETRANERR. +static struct { + int tls; + int nng; +} nni_tls_errs[] = { + { MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY, NNG_ECONNREFUSED }, + { MBEDTLS_ERR_SSL_ALLOC_FAILED, NNG_ENOMEM }, + { MBEDTLS_ERR_SSL_TIMEOUT, NNG_ETIMEDOUT }, + { MBEDTLS_ERR_SSL_CONN_EOF, NNG_ECLOSED }, + // terminator + { 0, 0 }, +}; + static int nni_tls_mkerr(int err) { - err = -err; - err |= NNG_ETRANERR; - return (err); + for (int i = 0; nni_tls_errs[i].tls != 0; i++) { + if (nni_tls_errs[i].tls == err) { + return (nni_tls_errs[i].nng); + } + } + return (NNG_ECRYPTO); } int @@ -370,6 +374,23 @@ nni_tls_cancel(nni_aio *aio, int rv) nni_mtx_unlock(&tp->lk); } +static void +nni_tls_fail(nni_tls *tp, int rv) +{ + nni_aio *aio; + tp->tls_closed = true; + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + while ((aio = nni_list_first(&tp->recvs)) != NULL) { + nni_list_remove(&tp->recvs, aio); + nni_aio_finish_error(aio, rv); + } + while ((aio = nni_list_first(&tp->sends)) != NULL) { + nni_list_remove(&tp->recvs, aio); + nni_aio_finish_error(aio, rv); + } +} + // nni_tls_send_cb is called when the underlying TCP send completes. static void nni_tls_send_cb(void *ctx) @@ -602,11 +623,8 @@ nni_tls_do_handshake(nni_tls *tp) return; default: - // Some other error occurred... would be nice to be - // able to diagnose it better. - tp->tls_closed = true; - nni_plat_tcp_pipe_close(tp->tcp); - tp->tcp_closed = true; + // some other error occurred, this causes us to tear it down + nni_tls_fail(tp, nni_tls_mkerr(rv)); } } @@ -723,7 +741,6 @@ nni_tls_close(nni_tls *tp) (void) mbedtls_ssl_close_notify(&tp->ctx); } else { nni_plat_tcp_pipe_close(tp->tcp); - tp->tcp_closed = true; } nni_mtx_unlock(&tp->lk); } @@ -817,8 +834,10 @@ nng_tls_config_ca_chain( rv = nni_tls_mkerr(rv); goto err; } - cfg->have_crl = true; } + + mbedtls_ssl_conf_ca_chain(&cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl); + err: nni_mtx_unlock(&cfg->lk); return (rv); @@ -881,6 +900,69 @@ err: } int +nng_tls_config_ca_file(nng_tls_config *cfg, const char *path) +{ + int rv; + void * fdata; + size_t fsize; + char * pem; + // Note that while mbedTLS supports its own file methods, we want + // to avoid depending on that because it might not have been + // included, so we use our own. We have to read the file, and + // then allocate a buffer that has an extra byte so we can + // ensure NUL termination. The file named by path may contain + // both a ca chain, and crl chain, or just a ca chain. + if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { + return (rv); + } + if ((pem = nni_alloc(fsize + 1)) == NULL) { + nni_free(fdata, fsize); + return (NNG_ENOMEM); + } + memcpy(pem, fdata, fsize); + pem[fsize] = '\0'; + nni_free(fdata, fsize); + if (strstr(pem, "-----BEGIN X509 CRL-----") != NULL) { + rv = nng_tls_config_ca_chain(cfg, pem, pem); + } else { + rv = nng_tls_config_ca_chain(cfg, pem, NULL); + } + nni_free(pem, fsize + 1); + return (rv); +} + +int +nng_tls_config_cert_key_file( + nng_tls_config *cfg, const char *path, const char *pass) +{ + int rv; + void * fdata; + size_t fsize; + char * pem; + + // Note that while mbedTLS supports its own file methods, we want + // to avoid depending on that because it might not have been + // included, so we use our own. We have to read the file, and + // then allocate a buffer that has an extra byte so we can + // ensure NUL termination. The file named by path must contain + // both our certificate, and our private key. The password + // may be NULL if the key is not encrypted. + if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { + return (rv); + } + if ((pem = nni_alloc(fsize + 1)) == NULL) { + nni_free(fdata, fsize); + return (NNG_ENOMEM); + } + memcpy(pem, fdata, fsize); + pem[fsize] = '\0'; + nni_free(fdata, fsize); + rv = nng_tls_config_own_cert(cfg, pem, pem, pass); + nni_free(pem, fsize + 1); + return (rv); +} + +int nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode) { return (nni_tls_config_init(cfgp, mode)); diff --git a/src/supplemental/tls/tls.h b/src/supplemental/tls/tls.h index 8175854d..5fde50b4 100644 --- a/src/supplemental/tls/tls.h +++ b/src/supplemental/tls/tls.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 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 // copy of which should be located in the distribution where this @@ -48,6 +48,4 @@ extern const char *nni_tls_ciphersuite_name(nni_tls *); // TBD: getting additional peer certificate information... -extern void nni_tls_strerror(int, char *, size_t); // review this - #endif // NNG_SUPPLEMENTAL_TLS_TLS_H diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index aca09749..fe4f002f 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -30,28 +30,32 @@ typedef struct ws_header { } ws_header; struct nni_ws { - nni_list_node node; - nni_reap_item reap; - int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN - bool closed; - bool ready; - bool wclose; - nni_mtx mtx; - nni_list txmsgs; - nni_list rxmsgs; - ws_frame * txframe; - ws_frame * rxframe; - nni_aio * txaio; // physical aios - nni_aio * rxaio; - nni_aio * closeaio; - nni_aio * httpaio; // server only, HTTP reply pending - nni_http * http; - nni_http_req *req; - nni_http_res *res; - char * reqhdrs; - char * reshdrs; - size_t maxframe; - size_t fragsize; + nni_list_node node; + nni_reap_item reap; + int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN + bool closed; + bool ready; + bool wclose; + nni_mtx mtx; + nni_list txmsgs; + nni_list rxmsgs; + ws_frame * txframe; + ws_frame * rxframe; + nni_aio * txaio; // physical aios + nni_aio * rxaio; + nni_aio * closeaio; + nni_aio * httpaio; + nni_aio * connaio; // connect aio + nni_aio * useraio; // user aio, during HTTP negotiation + nni_http * http; + nni_http_req * req; + nni_http_res * res; + char * reqhdrs; + char * reshdrs; + size_t maxframe; + size_t fragsize; + nni_ws_listener *listener; + nni_ws_dialer * dialer; }; struct nni_ws_listener { @@ -83,12 +87,9 @@ struct nni_ws_dialer { nni_http_res * res; nni_http_client *client; nni_mtx mtx; - nni_aio * conaio; char * proto; nni_url * url; - nni_list conaios; // user aios waiting for connect. - nni_list httpaios; // user aios waiting for HTTP nego. - bool started; + nni_list wspend; // ws structures still negotiating bool closed; nng_sockaddr sa; nni_list headers; // request headers @@ -139,6 +140,10 @@ struct ws_msg { }; static void ws_send_close(nni_ws *ws, uint16_t code); +static void ws_conn_cb(void *); +static void ws_close_cb(void *); +static void ws_read_cb(void *); +static void ws_write_cb(void *); // This looks, case independently for a word in a list, which is either // space or comma separated. @@ -455,9 +460,23 @@ ws_close(nni_ws *ws, uint16_t code) // If were closing "gracefully", then don't abort in-flight // stuff yet. Note that reads should have stopped already. + // However, we *do* abort any inflight HTTP negotiation, or + // pending connect request. if (!ws->closed) { + // ABORT connection negotiation. + nni_aio_cancel(ws->connaio, NNG_ECLOSED); + nni_aio_cancel(ws->httpaio, NNG_ECLOSED); ws_send_close(ws, code); - return; + } + + if (nni_list_node_active(&ws->node)) { + nni_ws_dialer *d; + + if ((d = ws->dialer) != NULL) { + nni_mtx_lock(&d->mtx); + nni_list_node_remove(&ws->node); + nni_mtx_unlock(&d->mtx); + } } } @@ -514,7 +533,12 @@ ws_write_cb(void *arg) nni_mtx_lock(&ws->mtx); - if (ws->txframe->op == WS_CLOSE) { + if ((frame = ws->txframe) == NULL) { + nni_mtx_unlock(&ws->mtx); + return; + } + ws->txframe = NULL; + if (frame->op == WS_CLOSE) { // If this was a close frame, we are done. // No other messages may succeed.. while ((wm = nni_list_first(&ws->txmsgs)) != NULL) { @@ -533,10 +557,8 @@ ws_write_cb(void *arg) return; } - frame = ws->txframe; - wm = frame->wmsg; - aio = wm->aio; - ws->txframe = NULL; + wm = frame->wmsg; + aio = wm->aio; if ((rv = nni_aio_result(ws->txaio)) != 0) { @@ -603,7 +625,7 @@ ws_send_close(nni_ws *ws, uint16_t code) NNI_PUT16(buf, code); - if (ws->closed) { + if (ws->closed || !ws->ready) { return; } ws->closed = true; @@ -1067,6 +1089,7 @@ ws_fini(void *arg) nni_aio_stop(ws->txaio); nni_aio_stop(ws->closeaio); nni_aio_stop(ws->httpaio); + nni_aio_stop(ws->connaio); nni_mtx_lock(&ws->mtx); while ((wm = nni_list_first(&ws->rxmsgs)) != NULL) { @@ -1090,6 +1113,9 @@ ws_fini(void *arg) } nni_mtx_unlock(&ws->mtx); + if (ws->http) { + nni_http_fini(ws->http); + } if (ws->req) { nni_http_req_fini(ws->req); } @@ -1099,11 +1125,11 @@ ws_fini(void *arg) nni_strfree(ws->reqhdrs); nni_strfree(ws->reshdrs); - nni_http_fini(ws->http); nni_aio_fini(ws->rxaio); nni_aio_fini(ws->txaio); nni_aio_fini(ws->closeaio); nni_aio_fini(ws->httpaio); + nni_aio_fini(ws->connaio); nni_mtx_fini(&ws->mtx); NNI_FREE_STRUCT(ws); } @@ -1150,10 +1176,10 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) char wskey[29]; const char * ptr; - d = nni_aio_get_data(aio, 0); + d = ws->dialer; + uaio = ws->useraio; nni_mtx_lock(&d->mtx); - uaio = nni_list_first(&d->httpaios); NNI_ASSERT(uaio != NULL); // We have two steps. In step 1, we just sent the request, // and need to retrieve the reply. In step two we have @@ -1222,16 +1248,18 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) #undef GETH // At this point, we are in business! - ws->ready = true; - nni_aio_list_remove(uaio); + nni_list_remove(&d->wspend, ws); + ws->ready = true; + ws->useraio = NULL; nni_aio_finish_pipe(uaio, ws); nni_mtx_unlock(&d->mtx); return; err: - nni_aio_list_remove(uaio); + nni_list_remove(&d->wspend, ws); + ws->useraio = NULL; nni_aio_finish_error(uaio, rv); - nni_ws_fini(ws); nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); } static void @@ -1251,7 +1279,7 @@ ws_http_cb(void *arg) } static int -ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) +ws_init(nni_ws **wsp) { nni_ws *ws; int rv; @@ -1266,8 +1294,9 @@ ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) if (((rv = nni_aio_init(&ws->closeaio, ws_close_cb, ws)) != 0) || ((rv = nni_aio_init(&ws->txaio, ws_write_cb, ws)) != 0) || ((rv = nni_aio_init(&ws->rxaio, ws_read_cb, ws)) != 0) || - ((rv = nni_aio_init(&ws->httpaio, ws_http_cb, ws)) != 0)) { - nni_ws_fini(ws); + ((rv = nni_aio_init(&ws->httpaio, ws_http_cb, ws)) != 0) || + ((rv = nni_aio_init(&ws->connaio, ws_conn_cb, ws)) != 0)) { + ws_fini(ws); return (rv); } @@ -1276,9 +1305,6 @@ ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) ws->fragsize = 1 << 20; // we won't send a frame larger than this ws->maxframe = (1 << 20) * 10; // default limit on incoming frame size - ws->http = http; - ws->req = req; - ws->res = res; *wsp = ws; return (0); } @@ -1444,11 +1470,15 @@ ws_handler(nni_aio *aio) // We are good to go, provided we can get the websocket struct, // and send the reply. - if ((rv = ws_init(&ws, http, req, res)) != 0) { + if ((rv = ws_init(&ws)) != 0) { + nni_http_req_fini(req); nni_http_res_fini(res); status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; goto err; } + ws->http = http; + ws->req = req; + ws->res = res; ws->mode = NNI_EP_MODE_LISTEN; // XXX: Inherit fragmentation and message size limits! @@ -1475,8 +1505,6 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) { nni_ws_listener *l; int rv; - nni_aio * aio; - nni_sockaddr sa; char * host; char * serv; @@ -1511,20 +1539,7 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) l->handler.h_host = host; // ignore the port l->handler.h_cb = ws_handler; - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - nni_ws_listener_fini(l); - return (rv); - } - aio->a_addr = &sa; - nni_plat_tcp_resolv(host, serv, NNG_AF_UNSPEC, true, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - nni_ws_listener_fini(l); - return (rv); - } - if ((rv = nni_http_server_init(&l->server, &sa)) != 0) { + if ((rv = nni_http_server_init(&l->server, addr)) != 0) { nni_ws_listener_fini(l); return (rv); } @@ -1666,7 +1681,6 @@ nni_ws_listener_hook( nni_mtx_unlock(&l->mtx); } -#ifdef NNG_SUPP_TLS int nni_ws_listener_set_tls(nni_ws_listener *l, nng_tls_config *tls) { @@ -1676,47 +1690,61 @@ nni_ws_listener_set_tls(nni_ws_listener *l, nng_tls_config *tls) nni_mtx_unlock(&l->mtx); return (rv); } -#endif + +int +nni_ws_listener_get_tls(nni_ws_listener *l, nng_tls_config **tlsp) +{ + int rv; + nni_mtx_lock(&l->mtx); + rv = nni_http_server_get_tls(l->server, tlsp); + nni_mtx_unlock(&l->mtx); + return (rv); +} void ws_conn_cb(void *arg) { - nni_ws_dialer *d = arg; - nni_aio * aio = d->conaio; + nni_ws_dialer *d; + nni_ws * ws; nni_aio * uaio; nni_http * http; nni_http_req * req = NULL; int rv; uint8_t raw[16]; char wskey[25]; - nni_ws * ws; ws_header * hdr; - nni_mtx_lock(&d->mtx); - uaio = nni_list_first(&d->conaios); - rv = nni_aio_result(aio); - http = rv == 0 ? nni_aio_get_output(aio, 0) : NULL; + ws = arg; - if (uaio == NULL) { - if (http) { - // Nobody listening anymore - hard abort. - nni_http_fini(http); + d = ws->dialer; + if ((rv = nni_aio_result(ws->connaio)) != 0) { + nni_mtx_lock(&ws->mtx); + if ((uaio = ws->useraio) != NULL) { + ws->useraio = NULL; + nni_aio_finish_error(uaio, rv); + } + nni_mtx_unlock(&ws->mtx); + nni_mtx_lock(&d->mtx); + if (nni_list_node_active(&ws->node)) { + nni_list_remove(&d->wspend, ws); + nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); + } else { + nni_mtx_unlock(&d->mtx); } - nni_mtx_unlock(&d->mtx); return; } - nni_aio_list_remove(uaio); - nni_aio_set_output(aio, 0, NULL); - - // We are done with this aio, start another connection request while - // we finish up, if we have more clients waiting. - if (!nni_list_empty(&d->conaios)) { - nni_http_client_connect(d->client, aio); - } - - if (rv != 0) { - goto err; + nni_mtx_lock(&ws->mtx); + uaio = ws->useraio; + http = nni_aio_get_output(ws->connaio, 0); + nni_aio_set_output(ws->connaio, 0, NULL); + if (uaio == NULL) { + // This request was canceled for some reason. + nni_http_fini(http); + nni_mtx_unlock(&ws->mtx); + nni_ws_fini(ws); + return; } for (int i = 0; i < 16; i++) { @@ -1738,7 +1766,6 @@ ws_conn_cb(void *arg) goto err; } - // If consumer asked for protocol, pass it on. if ((d->proto != NULL) && ((rv = SETH("Sec-WebSocket-Protocol", d->proto)) != 0)) { goto err; @@ -1749,33 +1776,25 @@ ws_conn_cb(void *arg) goto err; } } - #undef SETH - if ((rv = ws_init(&ws, http, req, NULL)) != 0) { - goto err; - } - ws->mode = NNI_EP_MODE_DIAL; + ws->http = http; + ws->req = req; - // Move this uaio to the http wait list. Note that it is not - // required that the uaio will be completed by this connection. - // If another connection attempt completes first, then the first - // aio queued will get the result. - nni_list_append(&d->httpaios, uaio); - nni_aio_set_data(ws->httpaio, 0, d); nni_http_write_req(http, req, ws->httpaio); - nni_mtx_unlock(&d->mtx); + nni_mtx_unlock(&ws->mtx); return; err: nni_aio_finish_error(uaio, rv); + nni_mtx_unlock(&ws->mtx); if (http != NULL) { nni_http_fini(http); } if (req != NULL) { nni_http_req_fini(req); } - nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); } void @@ -1783,7 +1802,6 @@ nni_ws_dialer_fini(nni_ws_dialer *d) { ws_header *hdr; - nni_aio_fini(d->conaio); nni_strfree(d->proto); while ((hdr = nni_list_first(&d->headers)) != NULL) { nni_list_remove(&d->headers, hdr); @@ -1806,62 +1824,20 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *addr) { nni_ws_dialer *d; int rv; - nni_aio * aio; - char * serv; if ((d = NNI_ALLOC_STRUCT(d)) == NULL) { return (NNG_ENOMEM); } NNI_LIST_INIT(&d->headers, ws_header, node); + NNI_LIST_INIT(&d->wspend, nni_ws, node); nni_mtx_init(&d->mtx); - nni_aio_list_init(&d->conaios); - nni_aio_list_init(&d->httpaios); if ((rv = nni_url_parse(&d->url, addr)) != 0) { nni_ws_dialer_fini(d); return (rv); } - // Dialer requires a valid host. - if ((strlen(d->url->u_hostname) == 0) || - (strcmp(d->url->u_hostname, "*") == 0)) { - nni_ws_dialer_fini(d); - return (NNG_EADDRINVAL); - } - - // Default port is 80 for ws, and 443 for wss. - if ((d->url->u_port == NULL) || (strlen(d->url->u_port) == 0)) { - if (strcmp(d->url->u_scheme, "wss") == 0) { - serv = "443"; - } else { - serv = "80"; - } - } else { - serv = d->url->u_port; - } - - if ((rv = nni_aio_init(&d->conaio, ws_conn_cb, d)) != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - // XXX: this is synchronous. We should fix this in the HTTP layer. - aio->a_addr = &d->sa; - nni_plat_tcp_resolv( - d->url->u_hostname, serv, NNG_AF_UNSPEC, false, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - - if ((rv = nni_http_client_init(&d->client, &d->sa)) != 0) { + if ((rv = nni_http_client_init(&d->client, addr)) != 0) { nni_ws_dialer_fini(d); return (rv); } @@ -1870,7 +1846,6 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *addr) return (0); } -#ifdef NNG_SUPP_TLS int nni_ws_dialer_set_tls(nni_ws_dialer *d, nng_tls_config *tls) { @@ -1880,19 +1855,32 @@ nni_ws_dialer_set_tls(nni_ws_dialer *d, nng_tls_config *tls) nni_mtx_unlock(&d->mtx); return (rv); } -#endif + +int +nni_ws_dialer_get_tls(nni_ws_dialer *d, nng_tls_config **tlsp) +{ + int rv; + nni_mtx_lock(&d->mtx); + rv = nni_http_client_get_tls(d->client, tlsp); + nni_mtx_unlock(&d->mtx); + return (rv); +} void nni_ws_dialer_close(nni_ws_dialer *d) { + nni_ws *ws; nni_mtx_lock(&d->mtx); if (d->closed) { nni_mtx_unlock(&d->mtx); return; } d->closed = true; + while ((ws = nni_list_first(&d->wspend)) != 0) { + nni_list_remove(&d->wspend, ws); + nni_ws_close(ws); + } nni_mtx_unlock(&d->mtx); - nni_aio_cancel(d->conaio, NNG_ECLOSED); } int @@ -1916,38 +1904,45 @@ nni_ws_dialer_proto(nni_ws_dialer *d, const char *proto) static void ws_dial_cancel(nni_aio *aio, int rv) { - nni_ws_dialer *d = aio->a_prov_data; - nni_mtx_lock(&d->mtx); - // If we are waiting, then we can cancel. Otherwise we need - // to abort. - if (nni_aio_list_active(aio)) { - nni_aio_list_remove(aio); + nni_ws *ws = aio->a_prov_data; + + nni_mtx_lock(&ws->mtx); + if (aio == ws->useraio) { + nni_aio_cancel(ws->connaio, rv); + nni_aio_cancel(ws->httpaio, rv); + ws->useraio = NULL; nni_aio_finish_error(aio, rv); } - // This does not cancel in-flight client negotiations with HTTP. - nni_mtx_unlock(&d->mtx); + nni_mtx_unlock(&ws->mtx); } void nni_ws_dialer_dial(nni_ws_dialer *d, nni_aio *aio) { - nni_mtx_lock(&d->mtx); - // First look up the host. - if (nni_aio_start(aio, ws_dial_cancel, d) != 0) { - nni_mtx_unlock(&d->mtx); + nni_ws *ws; + int rv; + + if ((rv = ws_init(&ws)) != 0) { + nni_aio_finish_error(aio, rv); return; } + nni_mtx_lock(&d->mtx); if (d->closed) { nni_aio_finish_error(aio, NNG_ECLOSED); nni_mtx_unlock(&d->mtx); + ws_fini(ws); return; } - nni_list_append(&d->conaios, aio); - - if (!d->started) { - d->started = true; - nni_http_client_connect(d->client, d->conaio); + if (nni_aio_start(aio, ws_dial_cancel, ws) != 0) { + nni_mtx_unlock(&d->mtx); + ws_fini(ws); + return; } + ws->dialer = d; + ws->useraio = aio; + ws->mode = NNI_EP_MODE_DIAL; + nni_list_append(&d->wspend, ws); + nni_http_client_connect(d->client, ws->connaio); nni_mtx_unlock(&d->mtx); } diff --git a/src/supplemental/websocket/websocket.h b/src/supplemental/websocket/websocket.h index ccf549df..9a52f78c 100644 --- a/src/supplemental/websocket/websocket.h +++ b/src/supplemental/websocket/websocket.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 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 // copy of which should be located in the distribution where this @@ -36,6 +36,7 @@ extern void nni_ws_listener_accept(nni_ws_listener *, nni_aio *); extern void nni_ws_listener_hook( nni_ws_listener *, nni_ws_listen_hook, void *); extern int nni_ws_listener_set_tls(nni_ws_listener *, nng_tls_config *); +extern int nni_ws_listener_get_tls(nni_ws_listener *, nng_tls_config **s); extern int nni_ws_dialer_init(nni_ws_dialer **, const char *); extern void nni_ws_dialer_fini(nni_ws_dialer *); @@ -44,6 +45,7 @@ extern int nni_ws_dialer_proto(nni_ws_dialer *, const char *); extern int nni_ws_dialer_header(nni_ws_dialer *, const char *, const char *); extern void nni_ws_dialer_dial(nni_ws_dialer *, nni_aio *); extern int nni_ws_dialer_set_tls(nni_ws_dialer *, nng_tls_config *); +extern int nni_ws_dialer_get_tls(nni_ws_dialer *, nng_tls_config **); // Dialer does not get a hook chance, as it can examine the request and reply // after dial is done; this is not a 3-way handshake, so the dialer does |
