Refactoring of RESTCONF/TLS close code

Single closing function: restconf_close_ssl_socket
Added constant HTTP_ON_HTTPS_REPLY for http request on https socket
This commit is contained in:
Olof hagsand 2022-09-13 22:55:11 +02:00
parent e39d18d59f
commit c1e4595949
11 changed files with 354 additions and 272 deletions

View file

@ -67,6 +67,7 @@ Users may have to change how they access the system
### Corrected Bugs ### Corrected Bugs
* Fixed: [yang regular char \w not include underline char](https://github.com/clicon/clixon/issues/357)
* Fixed: [Clixon backend transactions for choice/case is not logical](https://github.com/clicon/clixon/issues/361) * Fixed: [Clixon backend transactions for choice/case is not logical](https://github.com/clicon/clixon/issues/361)
* Fixed: [Clixon backend transaction callback fails for empty types](https://github.com/clicon/clixon/issues/360) * Fixed: [Clixon backend transaction callback fails for empty types](https://github.com/clicon/clixon/issues/360)
* Fixed: [with-defaults=trim does not work due to dodgy handling of state data marked as default](https://github.com/clicon/clixon/issues/348) * Fixed: [with-defaults=trim does not work due to dodgy handling of state data marked as default](https://github.com/clicon/clixon/issues/348)

View file

@ -346,7 +346,7 @@ cli_auto_top(clicon_handle h,
* <dbname> "running"|"candidate"|"startup" * <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <pretty> true|false: pretty-print or not * <pretty> true|false: pretty-print or not
* <state> true|false: pretty-print or not * <state> true|false: include state in output
* <default> Retrieval default mode: report-all, trim, explicit, report-all-tagged, * <default> Retrieval default mode: report-all, trim, explicit, report-all-tagged,
* report-all-tagged-default, report-all-tagged-strip * report-all-tagged-default, report-all-tagged-strip
* <prefix> CLI prefix: to print before cli syntax output * <prefix> CLI prefix: to print before cli syntax output

View file

@ -36,14 +36,22 @@
* Data structures: * Data structures:
* 1 1 * 1 1
* +--------------------+ restconf_handle_get +--------------------+ * +--------------------+ restconf_handle_get +--------------------+
* | rh restconf_handle | <--------------------- | h clicon_handle | * | rn restconf_native | <--------------------- | h clicon_handle |
* +--------------------+ +--------------------+ * | _handle | +--------------------+
* common SSL config \ ^ * +--------------------+ ^
* \ | n * common SSL config \ |
* \ rh_sockets +--------------------+ * \ | n
* +-----------> | rs restconf_socket | * \ rn_sockets +--------------------+
* +-----------> | rs restconf_socket |
* +--------------------+ * +--------------------+
* n per-socket SSL config * per-server socket (per config)
* | ^
* rs_conns v | n
* +--------------------+
* | rc restconf_conn |
* +--------------------+
* per-connection (transient)
* n
* +--------------------+ * +--------------------+
* | rr restconf_request| per-packet * | rr restconf_request| per-packet
* +--------------------+ * +--------------------+
@ -449,38 +457,6 @@ restconf_ssl_context_configure(clixon_handle h,
return retval; return retval;
} }
/*! Utility function to close restconf server ssl socket.
* There are many variants to closing, one could probably make this more generic
* and always use this function, but it is difficult.
*/
int
restconf_close_ssl_socket(restconf_conn *rc,
int shutdown)
{
int retval = -1;
int ret;
if (rc->rc_ssl != NULL){
if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){
#if 0
case SSL_ERROR_ZERO_RETURN: /* 6 */
Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that the underlying transport has been closed.
#endif
int e = SSL_get_error(rc->rc_ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
}
if (restconf_connection_close(rc->rc_h, rc->rc_s, rc->rc_socket) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
#if 0 /* debug */ #if 0 /* debug */
/*! Debug print all loaded certs /*! Debug print all loaded certs
*/ */
@ -581,7 +557,7 @@ restconf_accept_client(int fd,
goto done; goto done;
} }
/* Accept SSL */ /* Accept SSL */
if (restconf_ssl_accept_client(h, s, rsock) < 0) if (restconf_ssl_accept_client(h, s, rsock, NULL) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
@ -596,22 +572,29 @@ restconf_accept_client(int fd,
static int static int
restconf_native_terminate(clicon_handle h) restconf_native_terminate(clicon_handle h)
{ {
restconf_native_handle *rh; restconf_native_handle *rn;
restconf_socket *rsock; restconf_socket *rsock;
restconf_conn *rc;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if ((rh = restconf_native_handle_get(h)) != NULL){ if ((rn = restconf_native_handle_get(h)) != NULL){
while ((rsock = rh->rh_sockets) != NULL){ while ((rsock = rn->rn_sockets) != NULL){
while ((rc = rsock->rs_conns) != NULL){
if (rc->rc_s != -1){
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
close(rc->rc_s);
}
DELQ(rc, rsock->rs_conns, restconf_conn *);
restconf_close_ssl_socket(rc, __FUNCTION__, 0);
}
if (rsock->rs_callhome){ if (rsock->rs_callhome){
restconf_callhome_timer_unreg(rsock); restconf_callhome_timer_unreg(rsock);
if (rsock->rs_periodic)
restconf_idle_timer_unreg(rsock);
} }
else if (rsock->rs_ss != -1){ else if (rsock->rs_ss != -1){
clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client); clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client);
close(rsock->rs_ss); close(rsock->rs_ss);
} }
DELQ(rsock, rh->rh_sockets, restconf_socket *); DELQ(rsock, rn->rn_sockets, restconf_socket *);
if (rsock->rs_description) if (rsock->rs_description)
free(rsock->rs_description); free(rsock->rs_description);
if (rsock->rs_addrstr) if (rsock->rs_addrstr)
@ -620,9 +603,9 @@ restconf_native_terminate(clicon_handle h)
free(rsock->rs_addrtype); free(rsock->rs_addrtype);
free(rsock); free(rsock);
} }
if (rh->rh_ctx) if (rn->rn_ctx)
SSL_CTX_free(rh->rh_ctx); SSL_CTX_free(rn->rn_ctx);
free(rh); free(rn);
} }
EVP_cleanup(); EVP_cleanup();
return 0; return 0;
@ -716,7 +699,7 @@ openssl_init_socket(clicon_handle h,
char *addrtype = NULL; char *addrtype = NULL;
uint16_t port = 0; uint16_t port = 0;
int ss = -1; int ss = -1;
restconf_native_handle *rh = NULL; restconf_native_handle *rn = NULL;
restconf_socket *rsock = NULL; /* openssl per socket struct */ restconf_socket *rsock = NULL; /* openssl per socket struct */
struct timeval now; struct timeval now;
@ -755,7 +738,7 @@ openssl_init_socket(clicon_handle h,
) < 0) ) < 0)
goto done; goto done;
} }
if ((rh = restconf_native_handle_get(h)) == NULL){ if ((rn = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle"); clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done; goto done;
} }
@ -768,7 +751,7 @@ openssl_init_socket(clicon_handle h,
goto done; goto done;
} }
rsock->rs_port = port; rsock->rs_port = port;
INSQ(rsock, rh->rh_sockets); INSQ(rsock, rn->rn_sockets);
if (rsock->rs_callhome){ if (rsock->rs_callhome){
rsock->rs_ss = -1; /* Not applicable from callhome */ rsock->rs_ss = -1; /* Not applicable from callhome */
@ -809,7 +792,7 @@ restconf_openssl_init(clicon_handle h,
char *server_cert_path = NULL; char *server_cert_path = NULL;
char *server_key_path = NULL; char *server_key_path = NULL;
char *server_ca_cert_path = NULL; char *server_ca_cert_path = NULL;
restconf_native_handle *rh; restconf_native_handle *rn;
clixon_auth_type_t auth_type; clixon_auth_type_t auth_type;
int dbg; int dbg;
char *bstr; char *bstr;
@ -868,8 +851,8 @@ restconf_openssl_init(clicon_handle h,
if (restconf_ssl_context_configure(h, ctx, server_cert_path, server_key_path, server_ca_cert_path) < 0) if (restconf_ssl_context_configure(h, ctx, server_cert_path, server_key_path, server_ca_cert_path) < 0)
goto done; goto done;
} }
rh = restconf_native_handle_get(h); rn = restconf_native_handle_get(h);
rh->rh_ctx = ctx; rn->rn_ctx = ctx;
/* get the list of socket config-data */ /* get the list of socket config-data */
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0) if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
goto done; goto done;
@ -1123,7 +1106,7 @@ main(int argc,
clicon_handle h; clicon_handle h;
int dbg = 0; int dbg = 0;
int logdst = CLICON_LOG_SYSLOG; int logdst = CLICON_LOG_SYSLOG;
restconf_native_handle *rh = NULL; restconf_native_handle *rn = NULL;
int ret; int ret;
cxobj *xrestconf = NULL; cxobj *xrestconf = NULL;
char *inline_config = NULL; char *inline_config = NULL;
@ -1286,12 +1269,12 @@ main(int argc,
goto done; goto done;
} }
/* Create and stroe global openssl handle */ /* Create and stroe global openssl handle */
if ((rh = malloc(sizeof *rh)) == NULL){ if ((rn = malloc(sizeof *rn)) == NULL){
clicon_err(OE_UNIX, errno, "malloc"); clicon_err(OE_UNIX, errno, "malloc");
goto done; goto done;
} }
memset(rh, 0, sizeof *rh); memset(rn, 0, sizeof *rn);
if (restconf_native_handle_set(h, rh) < 0) if (restconf_native_handle_set(h, rn) < 0)
goto done; goto done;
/* Openssl inits */ /* Openssl inits */
if (restconf_openssl_init(h, dbg, xrestconf) < 0) if (restconf_openssl_init(h, dbg, xrestconf) < 0)
@ -1305,7 +1288,6 @@ main(int argc,
/* Main event loop */ /* Main event loop */
if (clixon_event_loop(h) < 0) if (clixon_event_loop(h) < 0)
goto done; goto done;
clicon_debug(1, "%s after", __FUNCTION__);
retval = 0; retval = 0;
done: done:
clicon_debug(1, "restconf_main_openssl done"); clicon_debug(1, "restconf_main_openssl done");

View file

@ -271,6 +271,10 @@ api_data_get2(clicon_handle h,
goto done; goto done;
if (xmlns_set_all(x, nscd) < 0) if (xmlns_set_all(x, nscd) < 0)
goto done; goto done;
if (nscd){
cvec_free(nscd);
nscd = NULL;
}
if (clixon_xml2cbuf(cbx, x, 0, pretty, -1, 0) < 0) /* Dont print top object? */ if (clixon_xml2cbuf(cbx, x, 0, pretty, -1, 0) < 0) /* Dont print top object? */
goto done; goto done;
} }
@ -301,7 +305,7 @@ api_data_get2(clicon_handle h,
if (xpath) if (xpath)
free(xpath); free(xpath);
if (nscd) if (nscd)
xml_nsctx_free(nscd); cvec_free(nscd);
if (nsc) if (nsc)
xml_nsctx_free(nsc); xml_nsctx_free(nsc);
if (xtop) if (xtop)

View file

@ -164,7 +164,12 @@ restconf_stream_free(restconf_stream_data *sd)
return 0; return 0;
} }
/*! Create restconf connection struct /*! Create restconf connection struct, per connect, ie transient
*
* @param[in] h Clixon handle
* @param[in] s Connected socket
* @param[in] rsock Backpointer to server struct
* @see restconf_conn_free
*/ */
restconf_conn * restconf_conn *
restconf_conn_new(clicon_handle h, restconf_conn_new(clicon_handle h,
@ -180,22 +185,30 @@ restconf_conn_new(clicon_handle h,
memset(rc, 0, sizeof(restconf_conn)); memset(rc, 0, sizeof(restconf_conn));
rc->rc_h = h; rc->rc_h = h;
rc->rc_s = s; rc->rc_s = s;
rc->rc_callhome = rsock->rs_callhome;
rc->rc_socket = rsock; rc->rc_socket = rsock;
INSQ(rc, rsock->rs_conns);
clicon_debug(1, "%s %p", __FUNCTION__, rc);
return rc; return rc;
} }
/*! Free clixon/cbuf resources related to a connection /*! Free clixon/cbuf resources related to a connection
* @param[in] rc restconf connection * @param[in] rc restconf connection
*/ */
int static int
restconf_conn_free(restconf_conn *rc) restconf_conn_free(restconf_conn *rc)
{ {
int retval = -1;
restconf_stream_data *sd; restconf_stream_data *sd;
restconf_socket *rsock;
restconf_conn *rc1;
clicon_debug(1, "%s", __FUNCTION__);
if (rc == NULL){ if (rc == NULL){
clicon_err(OE_RESTCONF, EINVAL, "rc is NULL"); clicon_err(OE_RESTCONF, EINVAL, "rc is NULL");
return -1; goto done;
} }
clicon_debug(1, "%s %p", __FUNCTION__, rc);
#ifdef HAVE_LIBNGHTTP2 #ifdef HAVE_LIBNGHTTP2
if (rc->rc_ngsession) if (rc->rc_ngsession)
nghttp2_session_del(rc->rc_ngsession); nghttp2_session_del(rc->rc_ngsession);
@ -206,8 +219,21 @@ restconf_conn_free(restconf_conn *rc)
if (sd) if (sd)
restconf_stream_free(sd); restconf_stream_free(sd);
} }
/* Free connect from server sock */
if ((rsock = rc->rc_socket) != NULL &&
(rc1 = rsock->rs_conns) != NULL){
do {
if (rc == rc1){
DELQ(rc, rsock->rs_conns, restconf_conn *);
break;
}
rc1 = NEXTQ(restconf_conn *, rc1);
} while (rc1 && rc1 != rsock->rs_conns);
}
free(rc); free(rc);
return 0; retval = 0;
done:
return retval;
} }
/*! Given SSL connection, get peer certificate one-line name /*! Given SSL connection, get peer certificate one-line name
@ -337,20 +363,27 @@ restconf_connection_sanity(clicon_handle h,
/* Write buf to socket /* Write buf to socket
* see also this function in restcont_api_openssl.c * see also this function in restcont_api_openssl.c
* @retval 1 OK
* @retval 0 OK, but socket write returned error, caller should close rc
* @retval -1 Error
*/ */
static int static int
native_buf_write(clicon_handle h, native_buf_write(clicon_handle h,
char *buf, char *buf,
size_t buflen, size_t buflen,
int s, restconf_conn *rc)
SSL *ssl,
restconf_socket *rsock)
{ {
int retval = -1; int retval = -1;
ssize_t len; ssize_t len;
ssize_t totlen = 0; ssize_t totlen = 0;
int er; int er;
SSL *ssl;
if (rc == NULL){
clicon_err(OE_RESTCONF, EINVAL, "rc is NULL");
goto done;
}
ssl = rc->rc_ssl;
/* Two problems with debugging buffers that this fixes: /* Two problems with debugging buffers that this fixes:
* 1. they are not "strings" in the sense they are not NULL-terminated * 1. they are not "strings" in the sense they are not NULL-terminated
* 2. they are often very long * 2. they are often very long
@ -376,12 +409,9 @@ native_buf_write(clicon_handle h,
case SSL_ERROR_SYSCALL: /* 5 */ case SSL_ERROR_SYSCALL: /* 5 */
if (er == ECONNRESET || /* Connection reset by peer */ if (er == ECONNRESET || /* Connection reset by peer */
er == EPIPE) { /* Reading end of socket is closed */ er == EPIPE) { /* Reading end of socket is closed */
if (ssl){ if (0 && restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
SSL_free(ssl);
}
if (restconf_connection_close(h, s, rsock) < 0)
goto done; goto done;
goto ok; /* Close socket and ssl */ goto closed; /* Close socket and ssl */
} }
else if (er == EAGAIN){ else if (er == EAGAIN){
clicon_debug(1, "%s write EAGAIN", __FUNCTION__); clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
@ -402,7 +432,7 @@ native_buf_write(clicon_handle h,
} }
} }
else{ else{
if ((len = write(s, buf+totlen, buflen-totlen)) < 0){ if ((len = write(rc->rc_s, buf+totlen, buflen-totlen)) < 0){
switch (errno){ switch (errno){
case EAGAIN: /* Operation would block */ case EAGAIN: /* Operation would block */
clicon_debug(1, "%s write EAGAIN", __FUNCTION__); clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
@ -412,9 +442,9 @@ native_buf_write(clicon_handle h,
// case EBADF: // XXX if this happens there is some larger error // case EBADF: // XXX if this happens there is some larger error
case ECONNRESET: /* Connection reset by peer */ case ECONNRESET: /* Connection reset by peer */
case EPIPE: /* Broken pipe */ case EPIPE: /* Broken pipe */
if (restconf_connection_close(h, s, rsock) < 0) if (0 && restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done; goto done;
goto ok; /* Close socket and ssl */ goto closed; /* Close socket and ssl */
break; break;
default: default:
clicon_err(OE_UNIX, errno, "write %d", errno); clicon_err(OE_UNIX, errno, "write %d", errno);
@ -426,27 +456,30 @@ native_buf_write(clicon_handle h,
} }
totlen += len; totlen += len;
} /* while */ } /* while */
ok: retval = 1;
retval = 0;
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval; return retval;
closed:
retval = 0;
goto done;
} }
/*! Send early handcoded bad request reply before actual packet received, just after accept /*! Send early handcoded bad request reply before actual packet received, just after accept
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] s Socket * @param[in] media
* @param[in] ssl If set, it will be freed
* @param[in] body If given add message body using media * @param[in] body If given add message body using media
* @param[in] rc Restconf connection, note may be closed in this
* @retval 1 OK
* @retval 0 OK, but socket write returned error, caller should close rc
* @retval -1 Error
* @see restconf_badrequest which can only be called in a request context * @see restconf_badrequest which can only be called in a request context
*/ */
static int static int
native_send_badrequest(clicon_handle h, native_send_badrequest(clicon_handle h,
int s, char *media,
SSL *ssl, char *body,
char *media, restconf_conn *rc)
char *body,
restconf_socket *rsock)
{ {
int retval = -1; int retval = -1;
cbuf *cb = NULL; cbuf *cb = NULL;
@ -466,9 +499,7 @@ native_send_badrequest(clicon_handle h,
cprintf(cb, "\r\n"); cprintf(cb, "\r\n");
if (body) if (body)
cprintf(cb, "%s\r\n", body); cprintf(cb, "%s\r\n", body);
if (native_buf_write(h, cbuf_get(cb), cbuf_len(cb), s, ssl, rsock) < 0) // XXX rsock retval = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc);
goto done;
retval = 0;
done: done:
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
@ -579,9 +610,8 @@ read_regular(restconf_conn *rc,
switch(errno){ switch(errno){
case ECONNRESET:/* Connection reset by peer */ case ECONNRESET:/* Connection reset by peer */
clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s); clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s);
if (restconf_connection_close(rc->rc_h, rc->rc_s, rc->rc_socket) < 0) if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done; goto done;
restconf_conn_free(rc);
retval = 0; /* Close socket and ssl */ retval = 0; /* Close socket and ssl */
goto done; goto done;
break; break;
@ -614,10 +644,10 @@ read_regular(restconf_conn *rc,
* @retval 1 OK * @retval 1 OK
*/ */
static int static int
restconf_http1_process(restconf_conn *rc, restconf_http1_process(restconf_conn *rc,
char *buf, char *buf,
size_t n, size_t n,
int *readmore) int *readmore)
{ {
int retval = -1; int retval = -1;
restconf_stream_data *sd; restconf_stream_data *sd;
@ -680,7 +710,7 @@ restconf_http1_process(restconf_conn *rc,
goto done; goto done;
} }
cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>%s</error-message></error></errors>", clicon_err_reason); cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>%s</error-message></error></errors>", clicon_err_reason);
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", cbuf_get(cberr), rc->rc_socket) < 0) if ((ret = native_send_badrequest(h, "application/yang-data+xml", cbuf_get(cberr), rc)) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -690,11 +720,17 @@ restconf_http1_process(restconf_conn *rc,
if ((ret = http1_check_expect(h, rc, sd)) < 0) if ((ret = http1_check_expect(h, rc, sd)) < 0)
goto done; goto done;
if (ret == 1){ if (ret == 1){
if (native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), if ((ret = native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), rc)) < 0)
rc->rc_s, rc->rc_ssl, rc->rc_socket) < 0)
goto done; goto done;
cvec_reset(sd->sd_outp_hdrs); cvec_reset(sd->sd_outp_hdrs);
cbuf_reset(sd->sd_outp_buf); cbuf_reset(sd->sd_outp_buf);
if (ret == 0){
if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done;
rc = NULL;
retval = 0;
goto done;
}
} }
} }
/* Check whole message is read. /* Check whole message is read.
@ -718,19 +754,15 @@ restconf_http1_process(restconf_conn *rc,
/* main restconf processing */ /* main restconf processing */
if (restconf_http1_path_root(h, rc) < 0) if (restconf_http1_path_root(h, rc) < 0)
goto done; goto done;
if (native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), if ((ret = native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), rc)) < 0)
rc->rc_s, rc->rc_ssl, rc->rc_socket) < 0)
goto done; goto done;
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */ cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(sd->sd_outp_buf); cbuf_reset(sd->sd_outp_buf);
cbuf_reset(sd->sd_inbuf); cbuf_reset(sd->sd_inbuf);
cbuf_reset(sd->sd_indata); cbuf_reset(sd->sd_indata);
if (rc->rc_exit){ /* Server-initiated exit */ if (ret == 0 || rc->rc_exit){ /* Server-initiated exit */
SSL_free(rc->rc_ssl); if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
rc->rc_ssl = NULL;
if (restconf_connection_close(h, rc->rc_s, rc->rc_socket) < 0)
goto done; goto done;
restconf_conn_free(rc);
retval = 0; retval = 0;
goto done; goto done;
} }
@ -761,7 +793,7 @@ restconf_http2_upgrade(restconf_conn *rc)
/* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */ /* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */
rc->rc_proto = HTTP_2; rc->rc_proto = HTTP_2;
if (http2_session_init(rc) < 0){ if (http2_session_init(rc) < 0){
restconf_close_ssl_socket(rc, 1); restconf_close_ssl_socket(rc, __FUNCTION__, 0);
goto done; goto done;
} }
/* The HTTP/1.1 request that is sent prior to upgrade is assigned a /* The HTTP/1.1 request that is sent prior to upgrade is assigned a
@ -780,7 +812,7 @@ restconf_http2_upgrade(restconf_conn *rc)
goto done; goto done;
} }
if (http2_send_server_connection(rc) < 0){ if (http2_send_server_connection(rc) < 0){
restconf_close_ssl_socket(rc, 1); restconf_close_ssl_socket(rc, __FUNCTION__, 0);
goto done; goto done;
} }
/* Use params from original http/1 session to http/2 stream */ /* Use params from original http/1 session to http/2 stream */
@ -836,8 +868,7 @@ restconf_http2_process(restconf_conn *rc,
if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0) if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
restconf_close_ssl_socket(rc, 0); if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
if (restconf_conn_free(rc) < 0)
goto done; goto done;
retval = 0; retval = 0;
goto done; goto done;
@ -860,7 +891,7 @@ restconf_http2_process(restconf_conn *rc,
/*! Get restconf native handle /*! Get restconf native handle
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @retval rh Restconf native handle * @retval rn Restconf native handle
*/ */
restconf_native_handle * restconf_native_handle *
restconf_native_handle_get(clicon_handle h) restconf_native_handle_get(clicon_handle h)
@ -925,9 +956,8 @@ restconf_connection(int s,
continue; continue;
if (n == 0){ if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__); clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (restconf_close_ssl_socket(rc, 0) < 0) if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done; goto done;
restconf_conn_free(rc);
rc = NULL; rc = NULL;
goto ok; goto ok;
} }
@ -966,27 +996,33 @@ restconf_connection(int s,
return retval; return retval;
} /* restconf_connection */ } /* restconf_connection */
/*----------------------------- Close socket ------------------------------*/
/*! Close Restconf native connection socket and unregister callback /*! Close Restconf native connection socket and unregister callback
* For callhome also start reconnect timer * For callhome also start reconnect timer
* @param[in] rc rstconf connection
*/ */
int static int
restconf_connection_close(clicon_handle h, restconf_connection_close1(restconf_conn *rc)
int s,
restconf_socket *rsock)
{ {
int retval = -1; int retval = -1;
restconf_socket *rsock;
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); if (rc == NULL){
if (close(s) < 0){ clicon_err(OE_RESTCONF, EINVAL, "rc is NULL");
goto done;
}
rsock = rc->rc_socket;
clicon_debug(1, "%s \"%s\"", __FUNCTION__, rsock->rs_description);
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close"); clicon_err(OE_UNIX, errno, "close");
goto done; goto done;
} }
clixon_event_unreg_fd(s, restconf_connection); clixon_event_unreg_fd(rc->rc_s, restconf_connection);
/* re-set timer */ /* re-set timer */
if (rsock->rs_callhome){ if (rc->rc_callhome){
if (rsock->rs_periodic && if (rsock->rs_periodic)
restconf_idle_timer_unreg(rsock) < 0) restconf_idle_timer_unreg(rc);
goto done;
if (restconf_callhome_timer(rsock, 1) < 0) if (restconf_callhome_timer(rsock, 1) < 0)
goto done; goto done;
} }
@ -995,7 +1031,62 @@ restconf_connection_close(clicon_handle h,
clicon_debug(1, "%s %d", __FUNCTION__, retval); clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval; return retval;
} }
/*! Utility function to close restconf server ssl socket.
* There are many variants to closing, one could probably make this more generic
* and always use this function, but it is difficult.
* @param[in] rc restconf connection
* @param[in] callfn For debug
* @param[in] dontshutdown If != 0, do not shutdown
*/
int
restconf_close_ssl_socket(restconf_conn *rc,
const char *callfn,
int dontshutdown)
{
int retval = -1;
int ret;
int sslerr;
int er;
clicon_debug(1, "%s", __FUNCTION__);
if (rc->rc_ssl != NULL){
if (!dontshutdown &&
(ret = SSL_shutdown(rc->rc_ssl)) < 0){
er = errno;
sslerr = SSL_get_error(rc->rc_ssl, ret);
clicon_debug(1, "%s errno:%d sslerr:%d", __FUNCTION__, er, sslerr);
// case SSL_ERROR_ZERO_RETURN: /* 6 */
// Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that the underlying transport has been closed.
if (sslerr == SSL_ERROR_SSL){ /* 1 */
}
else if (sslerr == SSL_ERROR_SYSCALL){ /* 5 */
/* Some non-recoverable, fatal I/O error occurred. The OpenSSL error queue
may contain more information on the error. For socket I/O on Unix systems,
consult errno for details. If this error occurs then no further I/O
operations should be performed on the connection and SSL_shutdown() must
not be called.*/
/* Ignore eg EBADF/ECONNRESET/EPIPE */
}
else{
clicon_err(OE_SSL, sslerr, "SSL_shutdown, %s err:%d %d", callfn, sslerr, er);
goto done;
}
}
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
}
if (restconf_connection_close1(rc) < 0)
goto done;
if (restconf_conn_free(rc) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*------------------------------ Accept--------------------------------*/ /*------------------------------ Accept--------------------------------*/
/*! Check ALPN result /*! Check ALPN result
@ -1012,7 +1103,6 @@ ssl_alpn_check(clicon_handle h,
restconf_http_proto *proto) restconf_http_proto *proto)
{ {
int retval = -1; int retval = -1;
int ret;
cbuf *cberr = NULL; cbuf *cberr = NULL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
@ -1037,34 +1127,17 @@ ssl_alpn_check(clicon_handle h,
if (alpn != NULL){ if (alpn != NULL){
cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>ALPN: protocol not recognized: %s</error-message></error></errors>", alpn); cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>ALPN: protocol not recognized: %s</error-message></error></errors>", alpn);
clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr)); clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr));
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl, if (native_send_badrequest(h,
"application/yang-data+xml", "application/yang-data+xml",
cbuf_get(cberr), rc->rc_socket) < 0) cbuf_get(cberr), rc) < 0)
goto done; goto done;
} }
else{ else{
/* XXX Sending badrequest here gives a segv in SSL_shutdown() later or a SIGPIPE here */ /* XXX Sending badrequest here gives a segv in SSL_shutdown() later or a SIGPIPE here */
clicon_log(LOG_INFO, "%s Warning: ALPN: No protocol selected, or no ALPN?", __FUNCTION__); clicon_log(LOG_INFO, "%s Warning: ALPN: No protocol selected, or no ALPN?", __FUNCTION__);
} }
if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
if (rc->rc_ssl){ goto done;
/* nmap ssl-known-key SEGV at s->method->ssl_shutdown(s);
* OR OpenSSL error: : SSL_shutdown, err: SSL_ERROR_SYSCALL(5)
*/
if ((ret = SSL_shutdown(rc->rc_ssl)) < 0){
int e = SSL_get_error(rc->rc_ssl, ret);
if (e == SSL_ERROR_SYSCALL){
clicon_log(LOG_INFO, "%s Warning: SSL_shutdown SSL_ERROR_SYSCALL", __FUNCTION__);
/* Continue */
}
else {
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
}
SSL_free(rc->rc_ssl);
}
restconf_conn_free(rc);
} }
retval = 0; /* ALPN not OK */ retval = 0; /* ALPN not OK */
done: done:
@ -1078,15 +1151,18 @@ ssl_alpn_check(clicon_handle h,
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] s Socket (unix or ip) * @param[in] s Socket (unix or ip)
* @param[in] rsock Socket struct * @param[in] rsock Socket struct
* @param[out] rcp Restconf connection
* @see openssl_init_socket where this callback is registered * @see openssl_init_socket where this callback is registered
*/ */
int int
restconf_ssl_accept_client(clicon_handle h, restconf_ssl_accept_client(clicon_handle h,
int s, int s,
restconf_socket *rsock) restconf_socket *rsock,
restconf_conn **rcp
)
{ {
int retval = -1; int retval = -1;
restconf_native_handle *rh = NULL; restconf_native_handle *rn = NULL;
restconf_conn *rc = NULL; restconf_conn *rc = NULL;
char *name = NULL; char *name = NULL;
int ret; int ret;
@ -1103,7 +1179,7 @@ restconf_ssl_accept_client(clicon_handle h,
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */ proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
#endif #endif
#endif #endif
if ((rh = restconf_native_handle_get(h)) == NULL){ if ((rn = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle"); clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done; goto done;
} }
@ -1114,7 +1190,7 @@ restconf_ssl_accept_client(clicon_handle h,
goto done; goto done;
clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s); clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s);
if (rsock->rs_ssl){ if (rsock->rs_ssl){
if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){ if ((rc->rc_ssl = SSL_new(rn->rn_ctx)) == NULL){
clicon_err(OE_SSL, 0, "SSL_new"); clicon_err(OE_SSL, 0, "SSL_new");
goto done; goto done;
} }
@ -1160,11 +1236,15 @@ restconf_ssl_accept_client(clicon_handle h,
switch (e){ switch (e){
case SSL_ERROR_SSL: /* 1 */ case SSL_ERROR_SSL: /* 1 */
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__); clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
#ifdef HTTP_ON_HTTPS_REPLY
SSL_free(rc->rc_ssl); SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL; rc->rc_ssl = NULL;
if (restconf_connection_close(h, rc->rc_s, rc->rc_socket) < 0) if (native_send_badrequest(h, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The plain HTTP request was sent to HTTPS port</error-message></error></errors>", rc) < 0)
goto done;
#endif
if (restconf_close_ssl_socket(rc, __FUNCTION__, 1) < 0)
goto done; goto done;
restconf_conn_free(rc);
goto ok; goto ok;
break; break;
case SSL_ERROR_SYSCALL: /* 5 */ case SSL_ERROR_SYSCALL: /* 5 */
@ -1174,9 +1254,8 @@ restconf_ssl_accept_client(clicon_handle h,
operations should be performed on the connection and SSL_shutdown() must operations should be performed on the connection and SSL_shutdown() must
not be called.*/ not be called.*/
clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er); clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er);
if (restconf_close_ssl_socket(rc, 0) < 0) if (restconf_close_ssl_socket(rc, __FUNCTION__, 1) < 0)
goto done; goto done;
restconf_conn_free(rc);
rc = NULL; rc = NULL;
goto ok; goto ok;
break; break;
@ -1237,18 +1316,10 @@ restconf_ssl_accept_client(clicon_handle h,
else { /* Get certificates (if available) */ else { /* Get certificates (if available) */
if (proto != HTTP_2 && if (proto != HTTP_2 &&
native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>", rc->rc_socket) < 0) "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>", rc->rc_socket, rc) < 0)
goto done;
if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done; goto done;
restconf_conn_free(rc);
if (rc->rc_ssl){
if ((ret = SSL_shutdown(rc->rc_ssl)) < 0){
int e = SSL_get_error(rc->rc_ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
}
goto ok; goto ok;
} }
} }
@ -1291,25 +1362,11 @@ restconf_ssl_accept_client(clicon_handle h,
#ifdef HAVE_LIBNGHTTP2 #ifdef HAVE_LIBNGHTTP2
case HTTP_2:{ case HTTP_2:{
if (http2_session_init(rc) < 0){ if (http2_session_init(rc) < 0){
restconf_close_ssl_socket(rc, 1); restconf_close_ssl_socket(rc, __FUNCTION__, 0);
goto done; goto done;
} }
if (http2_send_server_connection(rc) < 0){ if (http2_send_server_connection(rc) < 0){
restconf_close_ssl_socket(rc, 1); restconf_close_ssl_socket(rc, __FUNCTION__, 0);
#ifdef NYI
if (ssl) {
SSL_shutdown(ssl);
}
bufferevent_free(session_data->bev);
nghttp2_session_del(session_data->session);
for (stream_data = session_data->root.next; stream_data;) {
http2_stream_data *next = stream_data->next;
delete_http2_stream_data(stream_data);
stream_data = next;
}
free(session_data->client_addr);
free(session_data);
#endif
goto done; goto done;
} }
break; break;
@ -1320,6 +1377,8 @@ restconf_ssl_accept_client(clicon_handle h,
} /* switch proto */ } /* switch proto */
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0) if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
goto done; goto done;
if (rcp)
*rcp = rc;
ok: ok:
retval = 0; retval = 0;
done: done:
@ -1330,6 +1389,7 @@ restconf_ssl_accept_client(clicon_handle h,
} /* restconf_ssl_accept_client */ } /* restconf_ssl_accept_client */
/*! idle timeout timer callback /*! idle timeout timer callback
* @param[in] rc restconf connection, more specifically: callhome connection
*/ */
static int static int
restconf_idle_cb(int fd, restconf_idle_cb(int fd,
@ -1337,15 +1397,20 @@ restconf_idle_cb(int fd,
{ {
int retval = -1; int retval = -1;
restconf_socket *rsock; restconf_socket *rsock;
restconf_conn *rc;
if ((rsock = (restconf_socket *)arg) == NULL){ if ((rc = (restconf_conn *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rc is NULL");
goto done;
}
if ((rsock = rc->rc_socket) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL"); clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done; goto done;
} }
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); clicon_debug(1, "%s \"%s\"", __FUNCTION__, rsock->rs_description);
/* sanity */ /* sanity */
if (rsock->rs_callhome && rsock->rs_periodic && rsock->rs_idle_timeout && rsock->rs_ss > 0){ if (rc->rc_callhome && rsock->rs_periodic && rsock->rs_idle_timeout && rc->rc_s > 0){
if (restconf_connection_close(rsock->rs_h, rsock->rs_ss, rsock) < 0) if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
@ -1354,9 +1419,9 @@ restconf_idle_cb(int fd,
} }
int int
restconf_idle_timer_unreg(restconf_socket *rsock) restconf_idle_timer_unreg(restconf_conn *rc)
{ {
return clixon_event_unreg_timeout(restconf_idle_cb, rsock); return clixon_event_unreg_timeout(restconf_idle_cb, rc);
} }
/*! Set callhome periodic idle-timeout /*! Set callhome periodic idle-timeout
@ -1370,19 +1435,25 @@ restconf_idle_timer_unreg(restconf_socket *rsock)
* @see restconf_idle_timer_unreg * @see restconf_idle_timer_unreg
*/ */
int int
restconf_idle_timer(restconf_socket *rsock) restconf_idle_timer(restconf_conn *rc)
{ {
int retval = -1; int retval = -1;
struct timeval now; struct timeval now;
struct timeval t; struct timeval t;
struct timeval t1 = {0, 0}; struct timeval t1 = {0, 0};
cbuf *cb = NULL; cbuf *cb = NULL;
restconf_socket *rsock;
if (rsock == NULL || !rsock->rs_callhome || !rsock->rs_periodic || rsock->rs_idle_timeout==0){ if (rc == NULL || !rc->rc_callhome){
clicon_err(OE_YANG, EINVAL, "rsock is NULL or not periodic callhome"); clicon_err(OE_YANG, EINVAL, "rc is NULL or not callhome");
goto done; goto done;
} }
clicon_debug(1, "%s %s register", __FUNCTION__, rsock->rs_description); rsock = rc->rc_socket;
if (rsock == NULL || !rsock->rs_periodic || rsock->rs_idle_timeout==0){
clicon_err(OE_YANG, EINVAL, "rsock is NULL or not periodic");
goto done;
}
clicon_debug(1, "%s \"%s\" register", __FUNCTION__, rsock->rs_description);
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
@ -1394,7 +1465,7 @@ restconf_idle_timer(restconf_socket *rsock)
clicon_debug(1, "%s now:%lu timeout:%lu.%lu", __FUNCTION__, now.tv_sec, t.tv_sec, t.tv_usec); clicon_debug(1, "%s now:%lu timeout:%lu.%lu", __FUNCTION__, now.tv_sec, t.tv_sec, t.tv_usec);
if (clixon_event_reg_timeout(t, if (clixon_event_reg_timeout(t,
restconf_idle_cb, restconf_idle_cb,
rsock, rc,
cbuf_get(cb)) < 0) cbuf_get(cb)) < 0)
goto done; goto done;
retval = 0; retval = 0;
@ -1423,13 +1494,14 @@ restconf_callhome_cb(int fd,
struct sockaddr *sa = (struct sockaddr *)&sin6; struct sockaddr *sa = (struct sockaddr *)&sin6;
size_t sa_len; size_t sa_len;
int s; int s;
restconf_conn *rc = NULL;
rsock = (restconf_socket *)arg; rsock = (restconf_socket *)arg;
if (rsock == NULL || !rsock->rs_callhome){ if (rsock == NULL || !rsock->rs_callhome){
clicon_err(OE_YANG, EINVAL, "rsock is NULL"); clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done; goto done;
} }
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); clicon_debug(1, "%s \"%s\"", __FUNCTION__, rsock->rs_description);
h = rsock->rs_h; h = rsock->rs_h;
/* Already computed in restconf_socket_init, could be saved in rsock? */ /* Already computed in restconf_socket_init, could be saved in rsock? */
if (clixon_inet2sin(rsock->rs_addrtype, rsock->rs_addrstr, rsock->rs_port, sa, &sa_len) < 0) if (clixon_inet2sin(rsock->rs_addrtype, rsock->rs_addrstr, rsock->rs_port, sa, &sa_len) < 0)
@ -1438,26 +1510,23 @@ restconf_callhome_cb(int fd,
clicon_err(OE_UNIX, errno, "socket"); clicon_err(OE_UNIX, errno, "socket");
goto done; goto done;
} }
clicon_debug(1, "%s connect %hu", __FUNCTION__, rsock->rs_port);
if (connect(s, sa, sa_len) < 0){ if (connect(s, sa, sa_len) < 0){
clicon_debug(1, "%s connect:%d %s", __FUNCTION__, errno, strerror(errno)); clicon_debug(1, "%s connect %hu fail:%d %s", __FUNCTION__, rsock->rs_port, errno, strerror(errno));
close(s); close(s);
rsock->rs_attempts++; rsock->rs_attempts++;
rsock->rs_ss = -1;
/* Fail: Initiate new timer */ /* Fail: Initiate new timer */
if (restconf_callhome_timer(rsock, 0) < 0) if (restconf_callhome_timer(rsock, 0) < 0)
goto done; goto done;
} }
else { else {
rsock->rs_ss = s; clicon_debug(1, "%s connect %hu OK", __FUNCTION__, rsock->rs_port);
rsock->rs_attempts = 0; rsock->rs_attempts = 0;
if (restconf_ssl_accept_client(h, s, rsock) < 0) if (restconf_ssl_accept_client(h, s, rsock, &rc) < 0)
goto done; goto done;
if (rsock->rs_periodic && rsock->rs_idle_timeout && if (rsock->rs_periodic && rsock->rs_idle_timeout &&
restconf_idle_timer(rsock) < 0) restconf_idle_timer(rc) < 0)
goto done; goto done;
} }
clicon_debug(1, "%s connect done", __FUNCTION__);
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -1491,7 +1560,7 @@ restconf_callhome_timer(restconf_socket *rsock,
clicon_err(OE_YANG, EINVAL, "rsock is NULL or not callhome"); clicon_err(OE_YANG, EINVAL, "rsock is NULL or not callhome");
goto done; goto done;
} }
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); clicon_debug(1, "%s \"%s\"", __FUNCTION__, rsock->rs_description);
if (!rsock->rs_callhome) if (!rsock->rs_callhome)
goto ok; /* shouldnt happen */ goto ok; /* shouldnt happen */
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
@ -1519,7 +1588,7 @@ restconf_callhome_timer(restconf_socket *rsock,
} }
cprintf(cb, "restconf callhome timer %s", rsock->rs_description); cprintf(cb, "restconf callhome timer %s", rsock->rs_description);
if (rsock->rs_description) if (rsock->rs_description)
clicon_debug(1, "%s registering %s: %lu", __FUNCTION__, rsock->rs_description, t.tv_sec); clicon_debug(1, "%s registering \"%s\": %lu", __FUNCTION__, rsock->rs_description, t.tv_sec);
else else
clicon_debug(1, "%s: %lu", __FUNCTION__, t.tv_sec); clicon_debug(1, "%s: %lu", __FUNCTION__, t.tv_sec);
/* Should be only place restconf_callhome_cb is registered */ /* Should be only place restconf_callhome_cb is registered */

View file

@ -32,16 +32,25 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* *
* Data structures:
* 1 1 * 1 1
* +--------------------+ restconf_handle_get +--------------------+ * +--------------------+ restconf_handle_get +--------------------+
* | rh restconf_handle | <--------------------- | h clicon_handle | * | rn restconf_native | <--------------------- | h clicon_handle |
* +--------------------+ +--------------------+ * | _handle | +--------------------+
* common SSL config \ ^ * +--------------------+ ^
* \ | n * common SSL config \ |
* \ rh_sockets +--------------------+ * \ | n
* +-----------> | rs restconf_socket | * \ rn_sockets +--------------------+
* +-----------> | rs restconf_socket |
* +--------------------+ * +--------------------+
* n per-socket SSL config * per-server socket (per config)
* | ^
* rs_conns v | n
* +--------------------+
* | rc restconf_conn |
* +--------------------+
* per-connection (transient)
* n
* +--------------------+ * +--------------------+
* | rr restconf_request| per-packet * | rr restconf_request| per-packet
* +--------------------+ * +--------------------+
@ -85,44 +94,17 @@ typedef struct {
uint8_t *sd_settings2; /* Settings for upgrade to http/2 request */ uint8_t *sd_settings2; /* Settings for upgrade to http/2 request */
} restconf_stream_data; } restconf_stream_data;
/* Restconf per socket handle typedef struct restconf_socket restconf_socket;
* Two types: listen and callhome.
* Listen: Uses socket rs_ss to listen for connections and accepts them, creates one
* restconf_conn for each new accept.
* Callhome: Calls connect according to timer to setup single restconf_conn.
* when this is closed, new connect is made, according to connection-type.
*/
typedef struct {
qelem_t rs_qelem; /* List header */
clicon_handle rs_h; /* Clixon handle */
char *rs_description; /* Description */
int rs_callhome; /* 0: listen, 1: callhome */
int rs_ss; /* Listen: Server socket, ready for accept
* Callhome: connect socket (same as restconf_conn->rc_s) */
int rs_ssl; /* 0: Not SSL socket, 1:SSL socket */
char *rs_addrtype; /* Address type according to ietf-inet-types:
eg inet:ipv4-address or inet:ipv6-address */
char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */
uint16_t rs_port; /* Protocol port */
int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */
uint32_t rs_period; /* Period in s (if callhome & periodic) */
uint8_t rs_max_attempts; /* max connect attempts (if callhome) */
uint16_t rs_idle_timeout; /* Max underlying TCP session remains idle (if callhome and periodic) */
uint64_t rs_start; /* First period start, next is start+periods*period */
uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods.
Set in restconf_callhome_timer*/
uint8_t rs_attempts; /* Dynamic connect attempts in this round (if callhome)
* Set in restconf_callhome_cb
*/
} restconf_socket;
/* Restconf connection handle /* Restconf connection handle
* Per connection request * Per connection request
*/ */
typedef struct restconf_conn { typedef struct restconf_conn {
qelem_t rc_qelem; /* List header */
/* XXX rc_proto and rc_proto_d1/d2 may not both be necessary. /* XXX rc_proto and rc_proto_d1/d2 may not both be necessary.
* remove rc_proto? * remove rc_proto?
*/ */
int rc_callhome; /* 0: listen, 1: callhome */
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */ restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
int rc_proto_d1; /* parsed version digit 1 */ int rc_proto_d1; /* parsed version digit 1 */
int rc_proto_d2; /* parsed version digit 2 */ int rc_proto_d2; /* parsed version digit 2 */
@ -139,14 +121,47 @@ typedef struct restconf_conn {
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */ restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
} restconf_conn; } restconf_conn;
/* Restconf per socket handle
* Two types: listen and callhome.
* Listen: Uses socket rs_ss to listen for connections and accepts them, creates one
* restconf_conn for each new accept.
* Callhome: Calls connect according to timer to setup single restconf_conn.
* when this is closed, new connect is made, according to connection-type.
*/
typedef struct restconf_socket{
qelem_t rs_qelem; /* List header */
clicon_handle rs_h; /* Clixon handle */
char *rs_description; /* Description */
int rs_callhome; /* 0: listen, 1: callhome */
int rs_ss; /* Listen: Server socket, ready for accept
* XXXCallhome: connect socket (same as restconf_conn->rc_s)
* Callhome: No-op, see restconf_conn->rc_s
*/
int rs_ssl; /* 0: Not SSL socket, 1:SSL socket */
char *rs_addrtype; /* Address type according to ietf-inet-types:
eg inet:ipv4-address or inet:ipv6-address */
char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */
uint16_t rs_port; /* Protocol port */
int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */
uint32_t rs_period; /* Period in s (if callhome & periodic) */
uint8_t rs_max_attempts; /* max connect attempts (if callhome) */
uint16_t rs_idle_timeout; /* Max underlying TCP session remains idle (if callhome and periodic) */
uint64_t rs_start; /* First period start, next is start+periods*period */
uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods.
Set in restconf_callhome_timer*/
uint8_t rs_attempts; /* Dynamic connect attempts in this round (if callhome)
* Set in restconf_callhome_cb
*/
restconf_conn *rs_conns; /* List of transient connect sockets */
} restconf_socket;
/* Restconf handle /* Restconf handle
* Global data about ssl (not per packet/request) * Global data about ssl (not per packet/request)
*/ */
typedef struct { typedef struct {
SSL_CTX *rh_ctx; /* SSL context */ SSL_CTX *rn_ctx; /* SSL context */
restconf_socket *rh_sockets; /* List of restconf server (ready for accept) sockets */ restconf_socket *rn_sockets; /* List of restconf server (ready for accept) sockets */
void *rh_arg; /* Packet specific handle */ void *rn_arg; /* Packet specific handle */
} restconf_native_handle; } restconf_native_handle;
/* /*
@ -156,17 +171,15 @@ restconf_stream_data *restconf_stream_data_new(restconf_conn *rc, int32_t stream
restconf_stream_data *restconf_stream_find(restconf_conn *rc, int32_t id); restconf_stream_data *restconf_stream_find(restconf_conn *rc, int32_t id);
int restconf_stream_free(restconf_stream_data *sd); int restconf_stream_free(restconf_stream_data *sd);
restconf_conn *restconf_conn_new(clicon_handle h, int s, restconf_socket *socket); restconf_conn *restconf_conn_new(clicon_handle h, int s, restconf_socket *socket);
int restconf_conn_free(restconf_conn *rc);
int ssl_x509_name_oneline(SSL *ssl, char **oneline); int ssl_x509_name_oneline(SSL *ssl, char **oneline);
int restconf_close_ssl_socket(restconf_conn *rc, int shutdown); /* XXX in restconf_main_native.c */ int restconf_close_ssl_socket(restconf_conn *rc, const char *callfn, int sslerr0);
int restconf_connection_sanity(clicon_handle h, restconf_conn *rc, restconf_stream_data *sd); int restconf_connection_sanity(clicon_handle h, restconf_conn *rc, restconf_stream_data *sd);
restconf_native_handle *restconf_native_handle_get(clicon_handle h); restconf_native_handle *restconf_native_handle_get(clicon_handle h);
int restconf_connection(int s, void *arg); int restconf_connection(int s, void *arg);
int restconf_connection_close(clicon_handle h, int s, restconf_socket *rsock); int restconf_ssl_accept_client(clicon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp);
int restconf_ssl_accept_client(clicon_handle h, int s, restconf_socket *rsock); int restconf_idle_timer_unreg(restconf_conn *rc);
int restconf_idle_timer_unreg(restconf_socket *rsock); int restconf_idle_timer(restconf_conn *rc);
int restconf_idle_timer(restconf_socket *rsock);
int restconf_callhome_timer_unreg(restconf_socket *rsock); int restconf_callhome_timer_unreg(restconf_socket *rsock);
int restconf_callhome_timer(restconf_socket *rsock, int status); int restconf_callhome_timer(restconf_socket *rsock, int status);
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock, int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,

View file

@ -180,7 +180,7 @@ session_send_callback(nghttp2_session *session,
if ((len = SSL_write(rc->rc_ssl, buf+totlen, buflen-totlen)) <= 0){ if ((len = SSL_write(rc->rc_ssl, buf+totlen, buflen-totlen)) <= 0){
er = errno; er = errno;
sslerr = SSL_get_error(rc->rc_ssl, len); sslerr = SSL_get_error(rc->rc_ssl, len);
clicon_debug(1, "%s errno:%d sslerr:%d", __FUNCTION__, errno, sslerr); clicon_debug(1, "%s errno:%d sslerr:%d", __FUNCTION__, er, sslerr);
switch (sslerr){ switch (sslerr){
case SSL_ERROR_WANT_WRITE: /* 3 */ case SSL_ERROR_WANT_WRITE: /* 3 */
clicon_debug(1, "%s write SSL_ERROR_WANT_WRITE", __FUNCTION__); clicon_debug(1, "%s write SSL_ERROR_WANT_WRITE", __FUNCTION__);
@ -640,11 +640,9 @@ on_stream_close_callback(nghttp2_session *session,
// restconf_conn *rc = (restconf_conn *)user_data; // restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s %d %s", __FUNCTION__, error_code, nghttp2_strerror(error_code)); clicon_debug(1, "%s %d %s", __FUNCTION__, error_code, nghttp2_strerror(error_code));
#ifdef NOTNEEDED /* XXX think this is not necessary? */ #if 0 // NOTNEEDED /* XXX think this is not necessary? */
if (error_code){ if (error_code){
if (restconf_close_ssl_socket(rc, 0) < 0) if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0)
return -1;
if (restconf_conn_free(rc) < 0)
return -1; return -1;
} }
#endif #endif

View file

@ -164,3 +164,18 @@
* This means that text output can not be parsed and loaded. * This means that text output can not be parsed and loaded.
*/ */
#undef TEXT_SYNTAX_NOPREFIX #undef TEXT_SYNTAX_NOPREFIX
/*! Reply with HTTP error when HTTP request on HTTPS socket
* If not set, just close socket and return with TCP reset.
* If set: Incoming request on an SSL socket is known to be non-TLS.
* Problematic part is it is not known it is proper non-TLS HTTP, for that it
* needs parsing/ALPN etc.
* This is the approx algorithm:
* s = accept();
* ssl = SSL_new()
* if (SSL_accept(ssl) < 0){
* if (SSL_get_error(ssl, ) == SSL_ERROR_SSL){
* SSL_free(ssl);
* // Here "s" is still open and you can reply on the non-ssl underlying socket
*/
#define HTTP_ON_HTTPS_REPLY

View file

@ -78,7 +78,7 @@ enum clicon_err{
OE_FATAL, /* Fatal error */ OE_FATAL, /* Fatal error */
OE_UNDEF, OE_UNDEF,
/*-- From here error extensions using clixon_err_cat_reg, XXX register dynamically? --*/ /*-- From here error extensions using clixon_err_cat_reg, XXX register dynamically? --*/
OE_SSL, /* Openssl errors, see eg ssl_get_error */ OE_SSL, /* Openssl errors, see eg ssl_get_error and clixon_openssl_log_cb */
OE_SNMP , /* Netsnmp error */ OE_SNMP , /* Netsnmp error */
OE_NGHTTP2, /* nghttp2 errors, see HAVE_LIBNGHTTP2 */ OE_NGHTTP2, /* nghttp2 errors, see HAVE_LIBNGHTTP2 */
}; };

View file

@ -210,9 +210,9 @@ function testrun()
new "Wrong proto=https on http port, expect err 35 wrong version number" new "Wrong proto=https on http port, expect err 35 wrong version number"
expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version
else # see (1) http to https port in restconf_main_native.c else # see (1) http to https port in restconf_main_native.c
new "Wrong proto=http on https port, expect bad request" new "Wrong proto=http on https port, expect bad request http1+2"
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer" # expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer"
# expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400" expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400"
fi fi
@ -263,7 +263,7 @@ function testrun()
new "Wrong proto=https on http port, expect err 35 wrong version number" new "Wrong proto=https on http port, expect err 35 wrong version number"
expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version
else # see (1) http to https port in restconf_main_native.c else # see (1) http to https port in restconf_main_native.c
new "Wrong proto=http on https port, expect bad request" new "Wrong proto=http on https port, expect bad request http2-only"
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" "16 52 55 56" --not-- 'HTTP' expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" "16 52 55 56" --not-- 'HTTP'
fi fi
@ -304,9 +304,9 @@ function testrun()
new "Wrong proto=https on http port, expect err 35 wrong version number" new "Wrong proto=https on http port, expect err 35 wrong version number"
expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version
else # see (1) http to https port in restconf_main_native.c else # see (1) http to https port in restconf_main_native.c
new "Wrong proto=http on https port, expect bad request" new "Wrong proto=http on https port, expect bad request http/1 only"
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer" # expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer"
# expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400" expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400"
fi fi
fi # HTTP/2 fi # HTTP/2

View file

@ -246,12 +246,12 @@ expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+jso
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)' new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>' expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed #new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed
new 'B.3.3. "fields" Parameter' #new 'B.3.3. "fields" Parameter'
new 'B.3.6. "filter" Parameter' #new 'B.3.6. "filter" Parameter'
new 'B.3.7. "start-time" Parameter' #new 'B.3.7. "start-time" Parameter'
new 'B.3.8. "stop-time" Parameter' #new 'B.3.8. "stop-time" Parameter'
new 'B.3.9. "with-defaults" Parameter' #new 'B.3.9. "with-defaults" Parameter'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"