aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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