summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-01-11 14:58:09 -0800
committerGarrett D'Amore <garrett@damore.org>2018-01-16 08:45:11 -0800
commitbbf012364d9f1482b16c97b8bfd2fd07130446ca (patch)
tree2cb45903b0d5aa756d44f27b39a99c318a99a9a2 /src
parent18229bbb69423d64d0a1b98bcf4bf3e24fba3aa4 (diff)
downloadnng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.tar.gz
nng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.tar.bz2
nng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.zip
fixes #201 TLS configuration should support files for certificates and keys
This adds support for configuration of TLS websockets using the files for keys, certificates, and CRLs. Significant changes to the websocket, TLS, and HTTP layers were made here. We now expect TLS configuration to be tied to the HTTP layer, and the HTTP code creates default configuration objects based on the URL supplied. (HTTP dialers and listeners are now created with a URL rather than a sockaddr, giving them access to the scheme as well.) We fixed several bugs affecting TLS validation, and added a test suite that confirms that validation works as it should. We also fixed an orphaned socket during HTTP negotiation, responsible for an occasional assertion error if the http handshake does not complete successfully. Finally several use-after-free races were closed. TLS layer changes include reporting of handshake failures using newly created "standard" error codes for peer authentication and cryptographic failures. The use of the '*' wild card in URLs at bind time is no longer supported for websocket at least. Documentation updates for all this are in place as well.
Diffstat (limited to 'src')
-rw-r--r--src/nng.c24
-rw-r--r--src/nng.h22
-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
-rw-r--r--src/transport/ws/websocket.c162
-rw-r--r--src/transport/ws/websocket.h42
11 files changed, 733 insertions, 288 deletions
diff --git a/src/nng.c b/src/nng.c
index 67eac2c4..509bb333 100644
--- a/src/nng.c
+++ b/src/nng.c
@@ -1,6 +1,6 @@
//
-// Copyright 2017 Garrett D'Amore <garrett@damore.org>
-// 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
@@ -411,6 +411,12 @@ nng_dialer_setopt_ptr(nng_dialer id, const char *name, void *val)
}
int
+nng_dialer_setopt_string(nng_dialer id, const char *name, const char *val)
+{
+ return (nng_dialer_setopt(id, name, val, strlen(val) + 1));
+}
+
+int
nng_dialer_getopt(nng_dialer id, const char *name, void *val, size_t *szp)
{
return (nng_ep_getopt(id, name, val, szp, NNI_EP_MODE_DIAL));
@@ -489,6 +495,12 @@ nng_listener_setopt_ptr(nng_listener id, const char *name, void *val)
}
int
+nng_listener_setopt_string(nng_listener id, const char *name, const char *val)
+{
+ return (nng_listener_setopt(id, name, val, strlen(val) + 1));
+}
+
+int
nng_listener_getopt(nng_listener id, const char *name, void *val, size_t *szp)
{
return (nng_ep_getopt(id, name, val, szp, NNI_EP_MODE_LISTEN));
@@ -620,6 +632,12 @@ nng_setopt_ptr(nng_socket sid, const char *name, void *val)
}
int
+nng_setopt_string(nng_socket sid, const char *name, const char *val)
+{
+ return (nng_setopt(sid, name, val, strlen(val) + 1));
+}
+
+int
nng_getopt_int(nng_socket sid, const char *name, int *valp)
{
size_t sz = sizeof(*valp);
@@ -714,6 +732,8 @@ static const struct {
{ NNG_EEXIST, "Resource already exists" },
{ NNG_EREADONLY, "Read only resource" },
{ NNG_EWRITEONLY, "Write only resource" },
+ { NNG_ECRYPTO, "Cryptographic error" },
+ { NNG_EPEERAUTH, "Peer could not be authenticated" },
{ NNG_EINTERNAL, "Internal error detected" },
{ 0, NULL },
// clang-format on
diff --git a/src/nng.h b/src/nng.h
index befc369b..6c043e26 100644
--- a/src/nng.h
+++ b/src/nng.h
@@ -84,6 +84,7 @@ NNG_DECL int nng_setopt_int(nng_socket, const char *, int);
NNG_DECL int nng_setopt_ms(nng_socket, const char *, nng_duration);
NNG_DECL int nng_setopt_size(nng_socket, const char *, size_t);
NNG_DECL int nng_setopt_uint64(nng_socket, const char *, uint64_t);
+NNG_DECL int nng_setopt_string(nng_socket, const char *, const char *);
// nng_socket_getopt obtains the option for a socket.
NNG_DECL int nng_getopt(nng_socket, const char *, void *, size_t *);
@@ -141,6 +142,7 @@ NNG_DECL int nng_dialer_setopt_ms(nng_dialer, const char *, nng_duration);
NNG_DECL int nng_dialer_setopt_size(nng_dialer, const char *, size_t);
NNG_DECL int nng_dialer_setopt_uint64(nng_dialer, const char *, uint64_t);
NNG_DECL int nng_dialer_setopt_ptr(nng_dialer, const char *, void *);
+NNG_DECL int nng_dialer_setopt_string(nng_dialer, const char *, const char *);
// nng_dialer_getopt obtains the option for a dialer. This will
// fail for options that a particular dialer is not interested in,
@@ -163,6 +165,8 @@ NNG_DECL int nng_listener_setopt_ms(nng_listener, const char *, nng_duration);
NNG_DECL int nng_listener_setopt_size(nng_listener, const char *, size_t);
NNG_DECL int nng_listener_setopt_uint64(nng_listener, const char *, uint64_t);
NNG_DECL int nng_listener_setopt_ptr(nng_listener, const char *, void *);
+NNG_DECL int nng_listener_setopt_string(
+ nng_listener, const char *, const char *);
// nng_listener_getopt obtains the option for a listener. This will
// fail for options that a particular listener is not interested in,
@@ -507,6 +511,8 @@ enum nng_errno_enum {
NNG_EEXIST = 23,
NNG_EREADONLY = 24,
NNG_EWRITEONLY = 25,
+ NNG_ECRYPTO = 26,
+ NNG_EPEERAUTH = 27,
NNG_EINTERNAL = 1000,
NNG_ESYSERR = 0x10000000,
NNG_ETRANERR = 0x20000000,
@@ -640,6 +646,22 @@ NNG_DECL int nng_tls_config_pass(nng_tls_config *, const char *);
// practice.
NNG_DECL int nng_tls_config_auth_mode(nng_tls_config *, nng_tls_auth_mode);
+// nng_tls_config_ca_file is used to pass a CA chain and optional CRL
+// via the filesystem. If CRL data is present, it must be contained
+// in the file, along with the CA certificate data. The format is PEM.
+// The path name must be a legal file name.
+NNG_DECL int nng_tls_config_ca_file(nng_tls_config *, const char *);
+
+// nng_tls_config_cert_key_file is used to pass our own certificate and
+// private key data via the filesystem. Both the key and certificate
+// must be present as PEM blocks in the same file. A password is used to
+// decrypt the private key if it is encrypted and the password supplied is not
+// NULL. This may be called multiple times on servers, but only once on a
+// client. (Servers can support multiple different certificates and keys for
+// different cryptographic algorithms. Clients only get one.)
+NNG_DECL int nng_tls_config_cert_key_file(
+ nng_tls_config *, const char *, const char *);
+
#ifdef __cplusplus
}
#endif
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
diff --git a/src/transport/ws/websocket.c b/src/transport/ws/websocket.c
index 05cb9513..16cdf47b 100644
--- a/src/transport/ws/websocket.c
+++ b/src/transport/ws/websocket.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
@@ -43,7 +43,6 @@ struct ws_ep {
nni_ws_listener *listener;
nni_ws_dialer * dialer;
nni_list headers; // to send, res or req
- nng_tls_config * tls;
};
struct ws_pipe {
@@ -222,7 +221,6 @@ ws_pipe_init(ws_pipe **pipep, ws_ep *ep, void *ws)
p->mode = ep->mode;
p->rcvmax = ep->rcvmax;
- // p->addr = ep->addr;
p->rproto = ep->rproto;
p->lproto = ep->lproto;
p->ws = ws;
@@ -603,11 +601,6 @@ ws_ep_fini(void *arg)
nni_strfree(ep->addr);
nni_strfree(ep->protoname);
nni_mtx_fini(&ep->mtx);
-#ifdef NNG_TRANSPORT_WSS
- if (ep->tls) {
- nni_tls_config_fini(ep->tls);
- }
-#endif
NNI_FREE_STRUCT(ep);
}
@@ -706,18 +699,6 @@ ws_ep_init(void **epp, const char *url, nni_sock *sock, int mode)
nni_mtx_init(&ep->mtx);
NNI_LIST_INIT(&ep->headers, ws_hdr, node);
-#ifdef NNG_TRANSPORT_WSS
- if (strncmp(url, "wss://", 4) == 0) {
- rv = nni_tls_config_init(&ep->tls,
- mode == NNI_EP_MODE_DIAL ? NNG_TLS_MODE_CLIENT
- : NNG_TLS_MODE_SERVER);
- if (rv != 0) {
- NNI_FREE_STRUCT(ep);
- return (rv);
- }
- }
-#endif
-
// List of pipes (server only).
nni_aio_list_init(&ep->aios);
@@ -798,10 +779,29 @@ nng_ws_register(void)
#ifdef NNG_TRANSPORT_WSS
static int
+wss_get_tls(ws_ep *ep, nng_tls_config **tlsp)
+{
+ switch (ep->mode) {
+ case NNI_EP_MODE_DIAL:
+ return (nni_ws_dialer_get_tls(ep->dialer, tlsp));
+ case NNI_EP_MODE_LISTEN:
+ return (nni_ws_listener_get_tls(ep->listener, tlsp));
+ }
+ return (NNG_EINVAL);
+}
+
+static int
wss_ep_getopt_tlsconfig(void *arg, void *v, size_t *szp)
{
- ws_ep *ep = arg;
- return (nni_getopt_ptr(ep->tls, v, szp));
+ ws_ep * ep = arg;
+ nng_tls_config *tls;
+ int rv;
+
+ if (((rv = wss_get_tls(ep, &tls)) != 0) ||
+ ((rv = nni_getopt_ptr(tls, v, szp)) != 0)) {
+ return (rv);
+ }
+ return (0);
}
static int
@@ -828,13 +828,98 @@ wss_ep_setopt_tlsconfig(void *arg, const void *v, size_t sz)
} else {
rv = nni_ws_dialer_set_tls(ep->dialer, cfg);
}
- if (rv == 0) {
- if (ep->tls != NULL) {
- nni_tls_config_fini(ep->tls);
+ nni_mtx_unlock(&ep->mtx);
+ return (rv);
+}
+
+static int
+wss_ep_setopt_tls_cert_key_file(void *arg, const void *v, size_t sz)
+{
+ ws_ep * ep = arg;
+ int rv;
+ nng_tls_config *tls;
+
+ if (ep == NULL) {
+ if (nni_strnlen(v, sz) >= sz) {
+ return (NNG_EINVAL);
}
- nni_tls_config_hold(cfg);
- ep->tls = cfg;
+ return (0);
+ }
+ nni_mtx_lock(&ep->mtx);
+ if (((rv = wss_get_tls(ep, &tls)) != 0) ||
+ ((rv = nng_tls_config_cert_key_file(tls, v, NULL)) != 0)) {
+ goto done;
}
+done:
+ nni_mtx_unlock(&ep->mtx);
+ return (rv);
+}
+
+static int
+wss_ep_setopt_tls_ca_file(void *arg, const void *v, size_t sz)
+{
+ ws_ep * ep = arg;
+ int rv;
+ nng_tls_config *tls;
+
+ if (ep == NULL) {
+ if (nni_strnlen(v, sz) >= sz) {
+ return (NNG_EINVAL);
+ }
+ return (0);
+ }
+ nni_mtx_lock(&ep->mtx);
+ if (((rv = wss_get_tls(ep, &tls)) != 0) ||
+ ((rv = nng_tls_config_ca_file(tls, v)) != 0)) {
+ goto done;
+ }
+done:
+ nni_mtx_unlock(&ep->mtx);
+ return (rv);
+}
+
+static int
+wss_ep_setopt_tls_auth_mode(void *arg, const void *v, size_t sz)
+{
+ ws_ep * ep = arg;
+ int rv;
+ nng_tls_config *tls;
+ int mode;
+
+ rv = nni_setopt_int(
+ &mode, v, sz, NNG_TLS_AUTH_MODE_NONE, NNG_TLS_AUTH_MODE_REQUIRED);
+ if ((rv != 0) || (ep == NULL)) {
+ return (rv);
+ }
+ nni_mtx_lock(&ep->mtx);
+ if (((rv = wss_get_tls(ep, &tls)) != 0) ||
+ ((rv = nng_tls_config_auth_mode(tls, mode)) != 0)) {
+ goto done;
+ }
+done:
+ nni_mtx_unlock(&ep->mtx);
+ return (rv);
+}
+
+static int
+wss_ep_setopt_tls_server_name(void *arg, const void *v, size_t sz)
+{
+ ws_ep * ep = arg;
+ int rv;
+ nng_tls_config *tls;
+
+ if (ep == NULL) {
+ if (nni_strnlen(v, sz) >= sz) {
+ return (NNG_EINVAL);
+ }
+ return (0);
+ }
+ nni_mtx_lock(&ep->mtx);
+ if (((rv = wss_get_tls(ep, &tls)) != 0) ||
+ ((rv = nng_tls_config_server_name(tls, v)) != 0)) {
+ goto done;
+ }
+done:
nni_mtx_unlock(&ep->mtx);
return (rv);
}
@@ -860,7 +945,26 @@ static nni_tran_ep_option wss_ep_options[] = {
.eo_getopt = wss_ep_getopt_tlsconfig,
.eo_setopt = wss_ep_setopt_tlsconfig,
},
-
+ {
+ .eo_name = NNG_OPT_WSS_TLS_CERT_KEY_FILE,
+ .eo_getopt = NULL,
+ .eo_setopt = wss_ep_setopt_tls_cert_key_file,
+ },
+ {
+ .eo_name = NNG_OPT_WSS_TLS_CA_FILE,
+ .eo_getopt = NULL,
+ .eo_setopt = wss_ep_setopt_tls_ca_file,
+ },
+ {
+ .eo_name = NNG_OPT_WSS_TLS_AUTH_MODE,
+ .eo_getopt = NULL,
+ .eo_setopt = wss_ep_setopt_tls_auth_mode,
+ },
+ {
+ .eo_name = NNG_OPT_WSS_TLS_SERVER_NAME,
+ .eo_getopt = NULL,
+ .eo_setopt = wss_ep_setopt_tls_server_name,
+ },
// terminate list
{ NULL, NULL, NULL },
};
diff --git a/src/transport/ws/websocket.h b/src/transport/ws/websocket.h
index a0d8a6cc..1f261067 100644
--- a/src/transport/ws/websocket.h
+++ b/src/transport/ws/websocket.h
@@ -35,6 +35,48 @@ NNG_DECL int nng_ws_register(void);
// behavior.
#define NNG_OPT_WSS_TLS_CONFIG "wss:tls-config"
+// NNG_OPT_WSS_TLS_CERT_KEY_FILE names a single file that
+// contains a certificate and key identifying ourself. This
+// is a write-only value. Listeners can call this multiple
+// times for different keys/certs corresponding to different
+// algorithms, whereas clients only get one. The file must
+// contain both cert and key as PEM blocks, and the key must
+// not be encrypted. (If more flexibility is needed, use the
+// TLS configuration directly.) Note that TLS configuration
+// cannot be changed if the listener, or any other from the same
+// server and port, is already started.
+#define NNG_OPT_WSS_TLS_CERT_KEY_FILE "wss:tls-cert-key-file"
+
+// NNG_OPT_WSS_TLS_CA_FILE names a single file that
+// contains certificate(s) for a CA, and optinally CRLs. This
+// is a write-only value. Listeners can call this multiple
+// times for different keys/certs corresponding to different
+// algorithms, whereas clients only get one. The file must
+// contain certs as PEM blocks, and may contain CRLs as PEM
+// as well. (If more flexibility is needed, use the
+// TLS configuration directly.) Note that TLS configuration
+// cannot be changed if the listener, or any other from the same
+// server and port, is already started.
+#define NNG_OPT_WSS_TLS_CA_FILE "wss:tls-ca-file"
+
+// NNG_OPT_WSS_TLS_AUTH_MODE is a write-only integer (int) option
+// that specifies whether the peer is verified or not. The option
+// can take one of the values of NNG_TLS_AUTH_MODE_NONE,
+// NNG_TLS_AUTH_MODE_OPTIONAL, or NNG_TLS_AUTH_MODE_REQUIRED.
+// The default is NNG_TLS_AUTH_MODE_NONE for listeners, and
+// NNG_TLS_AUTH_MODE_REQUIRED for dialers.
+#define NNG_OPT_WSS_TLS_AUTH_MODE "wss:tls-auth-mode"
+
+// NNG_OPT_WSS_TLS_SERVER_NAME is a write-only string that can be
+// set on dialers to check the CN of the server for a match. This
+// can also affect SNI (server name indication).
+#define NNG_OPT_WSS_TLS_SERVER_NAME "wss:tls-server-name"
+
+// NNG_OPT_WSS_TLS_VERIFIED returns a single integer, indicating
+// whether the peer was verified or not. This is a read-only value
+// available only on pipes.
+#define NNT_OPT_WSS_TLS_VERIFIED "wss:tls-verified"
+
// These aliases are for WSS naming consistency.
#define NNG_OPT_WSS_REQUEST_HEADERS NNG_OPT_WS_REQUEST_HEADERS
#define NNG_OPT_WSS_RESPONSE_HEADERS NNG_OPT_WS_RESPONSE_HEADERS