aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental
diff options
context:
space:
mode:
Diffstat (limited to 'src/supplemental')
-rw-r--r--src/supplemental/http/client.c115
-rw-r--r--src/supplemental/http/http.h37
-rw-r--r--src/supplemental/http/server.c146
-rw-r--r--src/supplemental/tls/mbedtls/tls.c132
-rw-r--r--src/supplemental/tls/tls.h6
-rw-r--r--src/supplemental/websocket/websocket.c329
-rw-r--r--src/supplemental/websocket/websocket.h6
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