RESTCONF callhome, periodic and persistent connection-types, multiple clients
This commit is contained in:
parent
0676ee4e64
commit
b748d68912
10 changed files with 765 additions and 913 deletions
|
|
@ -867,23 +867,32 @@ restconf_socket_init(const char *netns0,
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xs socket config
|
* @param[in] xs socket config
|
||||||
* @param[in] nsc Namespace context
|
* @param[in] nsc Namespace context
|
||||||
|
* @param[out] description
|
||||||
* @param[out] namespace
|
* @param[out] namespace
|
||||||
* @param[out] address Address as string, eg "0.0.0.0", "::"
|
* @param[out] address Address as string, eg "0.0.0.0", "::"
|
||||||
* @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address
|
* @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address
|
||||||
* @param[out] port TCP Port
|
* @param[out] port TCP Port
|
||||||
* @param[out] ssl SSL enabled?
|
* @param[out] ssl SSL enabled?
|
||||||
* @param[out] callhome Callhome enabled?
|
* @param[out] callhome Callhome
|
||||||
|
* if callhome:
|
||||||
|
* @param[out] periodic persistent:0, periodic:1
|
||||||
|
* @param[out] period in s. (if periodic)
|
||||||
|
* @param[out] attempts Number of max reconnect attempts
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
restconf_socket_extract(clicon_handle h,
|
restconf_socket_extract(clicon_handle h,
|
||||||
cxobj *xs,
|
cxobj *xs,
|
||||||
cvec *nsc,
|
cvec *nsc,
|
||||||
|
char **description,
|
||||||
char **namespace,
|
char **namespace,
|
||||||
char **address,
|
char **address,
|
||||||
char **addrtype,
|
char **addrtype,
|
||||||
uint16_t *port,
|
uint16_t *port,
|
||||||
uint16_t *ssl,
|
uint16_t *ssl,
|
||||||
int *callhome)
|
int *callhome,
|
||||||
|
int *periodic,
|
||||||
|
uint32_t *period,
|
||||||
|
uint8_t *attempts)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *x;
|
cxobj *x;
|
||||||
|
|
@ -900,6 +909,9 @@ restconf_socket_extract(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
*namespace = xml_body(x);
|
*namespace = xml_body(x);
|
||||||
|
if ((x = xpath_first(xs, nsc, "description")) != NULL){
|
||||||
|
*description = xml_body(x);
|
||||||
|
}
|
||||||
if ((x = xpath_first(xs, nsc, "address")) == NULL){
|
if ((x = xpath_first(xs, nsc, "address")) == NULL){
|
||||||
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
|
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -963,8 +975,37 @@ restconf_socket_extract(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((x = xpath_first(xs, nsc, "call-home")) != NULL)
|
if (xpath_first(xs, nsc, "call-home") != NULL){
|
||||||
*callhome = 1;
|
*callhome = 1;
|
||||||
|
if (xpath_first(xs, nsc, "call-home/connection-type/persistent") != NULL){
|
||||||
|
*periodic = 0;
|
||||||
|
}
|
||||||
|
else if (xpath_first(xs, nsc, "call-home/connection-type/periodic") != NULL){
|
||||||
|
*periodic = 1;
|
||||||
|
if ((x = xpath_first(xs, nsc, "call-home/connection-type/periodic/period")) != NULL &&
|
||||||
|
(str = xml_body(x)) != NULL){
|
||||||
|
if ((ret = parse_uint32(str, period, &reason)) < 0){
|
||||||
|
clicon_err(OE_XML, errno, "parse_uint16");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ret == 0){
|
||||||
|
clicon_err(OE_XML, EINVAL, "Unrecognized value of period: %s", str);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((x = xpath_first(xs, nsc, "call-home/reconnect-strategy/max-attempts")) != NULL &&
|
||||||
|
(str = xml_body(x)) != NULL){
|
||||||
|
if ((ret = parse_uint8(str, attempts, &reason)) < 0){
|
||||||
|
clicon_err(OE_XML, errno, "parse_uint8");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ret == 0){
|
||||||
|
clicon_err(OE_XML, EINVAL, "Unrecognized value of max-attempts: %s", str);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
*callhome = 0;
|
*callhome = 0;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ int restconf_drop_privileges(clicon_handle h);
|
||||||
int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out);
|
int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out);
|
||||||
int restconf_config_init(clicon_handle h, cxobj *xrestconf);
|
int restconf_config_init(clicon_handle h, cxobj *xrestconf);
|
||||||
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
|
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
|
||||||
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome);
|
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **description, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome, int *periodic, uint32_t *period, uint8_t *attempts);
|
||||||
|
|
||||||
#endif /* _RESTCONF_LIB_H_ */
|
#endif /* _RESTCONF_LIB_H_ */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -474,7 +474,7 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that
|
||||||
SSL_free(rc->rc_ssl);
|
SSL_free(rc->rc_ssl);
|
||||||
rc->rc_ssl = NULL;
|
rc->rc_ssl = NULL;
|
||||||
}
|
}
|
||||||
if (restconf_connection_close(rc->rc_h, rc->rc_s) < 0)
|
if (restconf_connection_close(rc->rc_h, rc->rc_s, rc->rc_socket) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -548,342 +548,6 @@ restconf_checkcert_file(cxobj *xrestconf,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Check ALPN result
|
|
||||||
* @proto[out] proto
|
|
||||||
* @retval 1 OK with proto set
|
|
||||||
* @retval 0 Fail, ALPN null or not recognized
|
|
||||||
* @retval -1 Error
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
ssl_alpn_check(clicon_handle h,
|
|
||||||
const unsigned char *alpn,
|
|
||||||
unsigned int alpnlen,
|
|
||||||
restconf_conn *rc,
|
|
||||||
restconf_http_proto *proto)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
int ret;
|
|
||||||
cbuf *cberr = NULL;
|
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
|
||||||
/* Alternatively, call restconf_str2proto but alpn is not a proper string */
|
|
||||||
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
|
|
||||||
*proto = HTTP_11;
|
|
||||||
retval = 1; /* http/1.1 */
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
else if (alpn && alpnlen == 2 && memcmp("h2", alpn, 2) == 0){
|
|
||||||
*proto = HTTP_2;
|
|
||||||
retval = 1; /* http/2 */
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else {
|
|
||||||
if ((cberr = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr));
|
|
||||||
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl,
|
|
||||||
"application/yang-data+xml",
|
|
||||||
cbuf_get(cberr)) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
/* 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__);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rc->rc_ssl){
|
|
||||||
/* 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 */
|
|
||||||
done:
|
|
||||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
|
||||||
if (cberr)
|
|
||||||
cbuf_free(cberr);
|
|
||||||
return retval;
|
|
||||||
} /* ssl_alpn_check */
|
|
||||||
|
|
||||||
|
|
||||||
/*! Accept new socket client. Note SSL not ip, this applies also to callhome
|
|
||||||
* @param[in] h Clixon handle
|
|
||||||
* @param[in] s Socket (unix or ip)
|
|
||||||
* @param[in] rsock Socket struct
|
|
||||||
* @see openssl_init_socket where this callback is registered
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
restconf_ssl_accept_client(clicon_handle h,
|
|
||||||
int s,
|
|
||||||
restconf_socket *rsock)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
restconf_native_handle *rh = NULL;
|
|
||||||
restconf_conn *rc = NULL;
|
|
||||||
char *name = NULL;
|
|
||||||
int ret;
|
|
||||||
int e;
|
|
||||||
int er;
|
|
||||||
int readmore;
|
|
||||||
const unsigned char *alpn = NULL;
|
|
||||||
unsigned int alpnlen = 0;
|
|
||||||
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
|
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
#ifndef HAVE_HTTP1
|
|
||||||
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
if ((rh = restconf_native_handle_get(h)) == NULL){
|
|
||||||
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Register callbacks for actual data socket
|
|
||||||
*/
|
|
||||||
if ((rc = restconf_conn_new(h, s)) == NULL)
|
|
||||||
goto done;
|
|
||||||
clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s);
|
|
||||||
if (rsock->rs_ssl){
|
|
||||||
if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s SSL_new(%p)", __FUNCTION__, rc->rc_ssl);
|
|
||||||
/* CCL_CTX_set_verify already set, need not call SSL_set_verify again for this server
|
|
||||||
*/
|
|
||||||
/* X509_CHECK_FLAG_NO_WILDCARDS disables wildcard expansion */
|
|
||||||
SSL_set_hostflags(rc->rc_ssl, X509_CHECK_FLAG_NO_WILDCARDS);
|
|
||||||
#if 0
|
|
||||||
/* XXX This code is kept for the time being just for reference, it does not belong here.
|
|
||||||
* If you want to restrict client certs to a specific set.
|
|
||||||
* Otherwise this is done in restcon ca-auth callback and ultimately NACM
|
|
||||||
* SSL_set1_host() sets the expected DNS hostname to name
|
|
||||||
C = SE
|
|
||||||
L = Stockholm
|
|
||||||
O = Clixon
|
|
||||||
OU = clixon
|
|
||||||
CN = ca <---
|
|
||||||
emailAddress = olof@hagsand.se
|
|
||||||
*/
|
|
||||||
if (SSL_set1_host(rc->rc_ssl, "andy") != 1) { /* for peer cert */
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_set1_host");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (SSL_add1_host(rc->rc_ssl, "olof") != 1) { /* for peer cert */
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_set1_host");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (SSL_set_fd(rc->rc_ssl, rc->rc_s) != 1){
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_set_fd");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
readmore = 1;
|
|
||||||
while (readmore){
|
|
||||||
readmore = 0;
|
|
||||||
/* 1: OK, -1 fatal, 0: TLS/SSL handshake was not successful
|
|
||||||
* Both error cases: Call SSL_get_error() with the return value ret
|
|
||||||
*/
|
|
||||||
if ((ret = SSL_accept(rc->rc_ssl)) != 1) {
|
|
||||||
clicon_debug(1, "%s SSL_accept() ret:%d errno:%d", __FUNCTION__, ret, er=errno);
|
|
||||||
e = SSL_get_error(rc->rc_ssl, ret);
|
|
||||||
switch (e){
|
|
||||||
case SSL_ERROR_SSL: /* 1 */
|
|
||||||
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
|
|
||||||
#if 1
|
|
||||||
if (native_send_badrequest(h, rc->rc_s, NULL, "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>") < 0)
|
|
||||||
goto done;
|
|
||||||
#endif
|
|
||||||
SSL_free(rc->rc_ssl);
|
|
||||||
rc->rc_ssl = NULL;
|
|
||||||
if (restconf_connection_close(h, rc->rc_s) < 0)
|
|
||||||
goto done;
|
|
||||||
restconf_conn_free(rc);
|
|
||||||
goto ok;
|
|
||||||
break;
|
|
||||||
case 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.*/
|
|
||||||
clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er);
|
|
||||||
if (restconf_close_ssl_socket(rc, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
restconf_conn_free(rc);
|
|
||||||
rc = NULL;
|
|
||||||
goto ok;
|
|
||||||
break;
|
|
||||||
case SSL_ERROR_WANT_READ: /* 2 */
|
|
||||||
case SSL_ERROR_WANT_WRITE: /* 3 */
|
|
||||||
/* SSL_ERROR_WANT_READ is returned when the last operation was a read operation
|
|
||||||
* from a nonblocking BIO.
|
|
||||||
* That is, it can happen if restconf_socket_init() below is called
|
|
||||||
* with SOCK_NONBLOCK
|
|
||||||
*/
|
|
||||||
clicon_debug(1, "%s write SSL_ERROR_WANT_READ", __FUNCTION__);
|
|
||||||
usleep(10000);
|
|
||||||
readmore = 1;
|
|
||||||
break;
|
|
||||||
case SSL_ERROR_NONE: /* 0 */
|
|
||||||
case SSL_ERROR_ZERO_RETURN: /* 6 */
|
|
||||||
case SSL_ERROR_WANT_CONNECT: /* 7 */
|
|
||||||
case SSL_ERROR_WANT_ACCEPT: /* 8 */
|
|
||||||
case SSL_ERROR_WANT_X509_LOOKUP: /* 4 */
|
|
||||||
case SSL_ERROR_WANT_ASYNC: /* 8 */
|
|
||||||
case SSL_ERROR_WANT_ASYNC_JOB: /* 10 */
|
|
||||||
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
|
|
||||||
case SSL_ERROR_WANT_CLIENT_HELLO_CB: /* 11 */
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_accept:%d", e);
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} /* SSL_accept */
|
|
||||||
} /* while(readmore) */
|
|
||||||
/* Sets data and len to point to the client's requested protocol for this connection. */
|
|
||||||
#ifndef OPENSSL_NO_NEXTPROTONEG
|
|
||||||
SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen);
|
|
||||||
#endif /* !OPENSSL_NO_NEXTPROTONEG */
|
|
||||||
if (alpn == NULL) {
|
|
||||||
/* Returns a pointer to the selected protocol in data with length len. */
|
|
||||||
SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen);
|
|
||||||
}
|
|
||||||
if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (ret == 0)
|
|
||||||
goto ok;
|
|
||||||
clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto));
|
|
||||||
|
|
||||||
#if 0 /* Seems too early to fail here, instead let authentication callback deal with this */
|
|
||||||
/* For client-cert authentication, check if any certs are present,
|
|
||||||
* if not, send bad request
|
|
||||||
* Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
|
|
||||||
* but then SSL_accept fails.
|
|
||||||
*/
|
|
||||||
if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){
|
|
||||||
X509 *peercert;
|
|
||||||
|
|
||||||
if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){
|
|
||||||
X509_free(peercert);
|
|
||||||
}
|
|
||||||
else { /* Get certificates (if available) */
|
|
||||||
if (proto != HTTP_2 &&
|
|
||||||
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>") < 0)
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ?
|
|
||||||
* Note this _only_ works if SSL_set1_host() was set previously,...
|
|
||||||
*/
|
|
||||||
if ((ret = SSL_get_verify_result(rc->rc_ssl)) == X509_V_OK) { /* for peer cert */
|
|
||||||
const char *peername = SSL_get0_peername(rc->rc_ssl);
|
|
||||||
if (peername != NULL) {
|
|
||||||
/* Name checks were in scope and matched the peername */
|
|
||||||
clicon_debug(1, "%s peername:%s", __FUNCTION__, peername);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
else{
|
|
||||||
clicon_log(LOG_NOTICE, "Cert error: %s", X509_verify_cert_error_string(ret));
|
|
||||||
/* Maybe should return already here, but to get proper return message need to
|
|
||||||
* continue to http/1 or http/2 handling
|
|
||||||
* @see restconf_connection_sanity
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if 0 /* debug */
|
|
||||||
if (clicon_debug_get())
|
|
||||||
restconf_listcerts(rc->rc_ssl);
|
|
||||||
#endif
|
|
||||||
} /* if ssl */
|
|
||||||
rc->rc_proto = proto;
|
|
||||||
switch (rc->rc_proto){
|
|
||||||
#ifdef HAVE_HTTP1
|
|
||||||
case HTTP_10:
|
|
||||||
case HTTP_11:
|
|
||||||
/* Create a default stream for http/1 */
|
|
||||||
if (restconf_stream_data_new(rc, 0) == NULL)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
#endif /* HAVE_HTTP1 */
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
case HTTP_2:{
|
|
||||||
if (http2_session_init(rc) < 0){
|
|
||||||
restconf_close_ssl_socket(rc, 1);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (http2_send_server_connection(rc) < 0){
|
|
||||||
restconf_close_ssl_socket(rc, 1);
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif /* HAVE_LIBNGHTTP2 */
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
} /* switch proto */
|
|
||||||
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
|
|
||||||
goto done;
|
|
||||||
ok:
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
|
||||||
if (name)
|
|
||||||
free(name);
|
|
||||||
return retval;
|
|
||||||
} /* restconf_ssl_accept_client */
|
|
||||||
|
|
||||||
/*! Accept new socket client
|
/*! Accept new socket client
|
||||||
* @param[in] fd Socket (unix or ip)
|
* @param[in] fd Socket (unix or ip)
|
||||||
* @param[in] arg typecast clicon_handle
|
* @param[in] arg typecast clicon_handle
|
||||||
|
|
@ -939,11 +603,16 @@ restconf_native_terminate(clicon_handle h)
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if ((rh = restconf_native_handle_get(h)) != NULL){
|
if ((rh = restconf_native_handle_get(h)) != NULL){
|
||||||
while ((rsock = rh->rh_sockets) != NULL){
|
while ((rsock = rh->rh_sockets) != NULL){
|
||||||
if (rsock->rs_ss != -1){
|
if (rsock->rs_callhome){
|
||||||
|
clixon_event_unreg_timeout(restconf_callhome_cb, rsock);
|
||||||
|
}
|
||||||
|
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, rh->rh_sockets, restconf_socket *);
|
||||||
|
if (rsock->rs_description)
|
||||||
|
free(rsock->rs_description);
|
||||||
if (rsock->rs_addrstr)
|
if (rsock->rs_addrstr)
|
||||||
free(rsock->rs_addrstr);
|
free(rsock->rs_addrstr);
|
||||||
if (rsock->rs_addrtype)
|
if (rsock->rs_addrtype)
|
||||||
|
|
@ -1028,62 +697,6 @@ restconf_clixon_backend(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Periodically try to connect to callhome client
|
|
||||||
*
|
|
||||||
* @param[in] fd No-op
|
|
||||||
* @param[in] arg restconf_socket
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
restconf_callhome_timer(int fdxxx,
|
|
||||||
void *arg)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
clicon_handle h;
|
|
||||||
struct timeval now;
|
|
||||||
struct timeval t;
|
|
||||||
struct timeval t1 = {1, 0}; // XXX once every second
|
|
||||||
restconf_socket *rsock = NULL;
|
|
||||||
struct sockaddr_in6 sin6 = {0,}; // because its larger than sin and sa
|
|
||||||
struct sockaddr *sa = (struct sockaddr *)&sin6;
|
|
||||||
size_t sa_len;
|
|
||||||
int s;
|
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
|
||||||
gettimeofday(&now, NULL);
|
|
||||||
if ((rsock = (restconf_socket *)arg) == NULL){
|
|
||||||
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
h = rsock->rs_h;
|
|
||||||
/* 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)
|
|
||||||
goto done;
|
|
||||||
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
|
||||||
clicon_err(OE_UNIX, errno, "socket");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s connect", __FUNCTION__);
|
|
||||||
if (connect(s, sa, sa_len) < 0){
|
|
||||||
clicon_debug(1, "%s connect:%d %s", __FUNCTION__, errno, strerror(errno));
|
|
||||||
close(s);
|
|
||||||
/* Fail: Initiate new timer */
|
|
||||||
timeradd(&now, &t1, &t);
|
|
||||||
if (clixon_event_reg_timeout(t,
|
|
||||||
restconf_callhome_timer, /* this function */
|
|
||||||
rsock,
|
|
||||||
"restconf callhome timer") < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (restconf_ssl_accept_client(h, s, rsock) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s connect done", __FUNCTION__);
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Per-socket openssl inits
|
/*! Per-socket openssl inits
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xs XML config of single restconf socket
|
* @param[in] xs XML config of single restconf socket
|
||||||
|
|
@ -1097,6 +710,7 @@ openssl_init_socket(clicon_handle h,
|
||||||
cvec *nsc)
|
cvec *nsc)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
char *description = NULL;
|
||||||
char *netns = NULL;
|
char *netns = NULL;
|
||||||
char *address = NULL;
|
char *address = NULL;
|
||||||
char *addrtype = NULL;
|
char *addrtype = NULL;
|
||||||
|
|
@ -1106,10 +720,14 @@ openssl_init_socket(clicon_handle h,
|
||||||
int ss = -1;
|
int ss = -1;
|
||||||
restconf_native_handle *rh = NULL;
|
restconf_native_handle *rh = NULL;
|
||||||
restconf_socket *rsock = NULL; /* openssl per socket struct */
|
restconf_socket *rsock = NULL; /* openssl per socket struct */
|
||||||
|
int periodic = 0;
|
||||||
|
uint32_t period = 0;
|
||||||
|
uint8_t max_attempts = 0;
|
||||||
|
struct timeval now;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
||||||
if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl, &callhome) < 0)
|
if (restconf_socket_extract(h, xs, nsc, &description, &netns, &address, &addrtype, &port, &ssl, &callhome, &periodic, &period, &max_attempts) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1124,6 +742,16 @@ openssl_init_socket(clicon_handle h,
|
||||||
rsock->rs_h = h;
|
rsock->rs_h = h;
|
||||||
rsock->rs_ssl = ssl; /* true/false */
|
rsock->rs_ssl = ssl; /* true/false */
|
||||||
rsock->rs_callhome = callhome;
|
rsock->rs_callhome = callhome;
|
||||||
|
rsock->rs_periodic = periodic;
|
||||||
|
rsock->rs_period = period;
|
||||||
|
rsock->rs_max_attempts = max_attempts;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
rsock->rs_start = now.tv_sec;
|
||||||
|
if (description &&
|
||||||
|
(rsock->rs_description = strdup(description)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "strdup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
if (callhome){
|
if (callhome){
|
||||||
if (!ssl){
|
if (!ssl){
|
||||||
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
|
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
|
||||||
|
|
@ -1159,8 +787,8 @@ openssl_init_socket(clicon_handle h,
|
||||||
INSQ(rsock, rh->rh_sockets);
|
INSQ(rsock, rh->rh_sockets);
|
||||||
|
|
||||||
if (callhome){
|
if (callhome){
|
||||||
rsock->rs_ss = -1; /* Not applicable fro callhome */
|
rsock->rs_ss = -1; /* Not applicable from callhome */
|
||||||
if (restconf_callhome_timer(0, rsock) < 0)
|
if (restconf_callhome_timer(rsock, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,9 @@ restconf_stream_free(restconf_stream_data *sd)
|
||||||
/*! Create restconf connection struct
|
/*! Create restconf connection struct
|
||||||
*/
|
*/
|
||||||
restconf_conn *
|
restconf_conn *
|
||||||
restconf_conn_new(clicon_handle h,
|
restconf_conn_new(clicon_handle h,
|
||||||
int s)
|
int s,
|
||||||
|
restconf_socket *rsock)
|
||||||
{
|
{
|
||||||
restconf_conn *rc;
|
restconf_conn *rc;
|
||||||
|
|
||||||
|
|
@ -179,6 +180,7 @@ 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_socket = rsock;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,11 +339,12 @@ restconf_connection_sanity(clicon_handle h,
|
||||||
* see also this function in restcont_api_openssl.c
|
* see also this function in restcont_api_openssl.c
|
||||||
*/
|
*/
|
||||||
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,
|
int s,
|
||||||
SSL *ssl)
|
SSL *ssl,
|
||||||
|
restconf_socket *rsock)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
|
|
@ -376,7 +379,7 @@ native_buf_write(clicon_handle h,
|
||||||
if (ssl){
|
if (ssl){
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
}
|
}
|
||||||
if (restconf_connection_close(h, s) < 0)
|
if (restconf_connection_close(h, s, rsock) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok; /* Close socket and ssl */
|
goto ok; /* Close socket and ssl */
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +411,7 @@ native_buf_write(clicon_handle h,
|
||||||
break;
|
break;
|
||||||
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) < 0)
|
if (restconf_connection_close(h, s, rsock) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok; /* Close socket and ssl */
|
goto ok; /* Close socket and ssl */
|
||||||
break;
|
break;
|
||||||
|
|
@ -436,12 +439,13 @@ native_buf_write(clicon_handle h,
|
||||||
* @param[in] body If given add message body using media
|
* @param[in] body If given add message body using media
|
||||||
* @see restconf_badrequest which can only be called in a request context
|
* @see restconf_badrequest which can only be called in a request context
|
||||||
*/
|
*/
|
||||||
int
|
static int
|
||||||
native_send_badrequest(clicon_handle h,
|
native_send_badrequest(clicon_handle h,
|
||||||
int s,
|
int s,
|
||||||
SSL *ssl,
|
SSL *ssl,
|
||||||
char *media,
|
char *media,
|
||||||
char *body)
|
char *body,
|
||||||
|
restconf_socket *rsock)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
|
|
@ -461,7 +465,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) < 0)
|
if (native_buf_write(h, cbuf_get(cb), cbuf_len(cb), s, ssl, rsock) < 0) // XXX rsock
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -574,7 +578,7 @@ 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) < 0)
|
if (restconf_connection_close(rc->rc_h, rc->rc_s, rc->rc_socket) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
restconf_conn_free(rc);
|
restconf_conn_free(rc);
|
||||||
retval = 0; /* Close socket and ssl */
|
retval = 0; /* Close socket and ssl */
|
||||||
|
|
@ -675,7 +679,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)) < 0)
|
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", cbuf_get(cberr), rc->rc_socket) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -686,7 +690,7 @@ restconf_http1_process(restconf_conn *rc,
|
||||||
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 (native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
||||||
rc->rc_s, rc->rc_ssl) < 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);
|
||||||
|
|
@ -714,7 +718,7 @@ restconf_http1_process(restconf_conn *rc,
|
||||||
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 (native_buf_write(h, cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
||||||
rc->rc_s, rc->rc_ssl) < 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);
|
||||||
|
|
@ -723,7 +727,7 @@ restconf_http1_process(restconf_conn *rc,
|
||||||
if (rc->rc_exit){ /* Server-initiated exit */
|
if (rc->rc_exit){ /* Server-initiated exit */
|
||||||
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) < 0)
|
if (restconf_connection_close(h, rc->rc_s, rc->rc_socket) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
restconf_conn_free(rc);
|
restconf_conn_free(rc);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -869,6 +873,8 @@ restconf_native_handle_get(clicon_handle h)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*---------------------------- Connect ---------------------------------*/
|
||||||
|
|
||||||
/*! New data connection after accept, receive and reply on data socket
|
/*! New data connection after accept, receive and reply on data socket
|
||||||
*
|
*
|
||||||
* @param[in] s Socket where message arrived. read from this.
|
* @param[in] s Socket where message arrived. read from this.
|
||||||
|
|
@ -959,37 +965,491 @@ restconf_connection(int s,
|
||||||
return retval;
|
return retval;
|
||||||
} /* restconf_connection */
|
} /* restconf_connection */
|
||||||
|
|
||||||
int restconf_callhome_timer(int fd, void *arg); // XXX FIX HEADERS INSTEAD
|
|
||||||
|
|
||||||
/*! 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
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
restconf_connection_close(clicon_handle h,
|
restconf_connection_close(clicon_handle h,
|
||||||
int s)
|
int s,
|
||||||
|
restconf_socket *rsock)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_native_handle *rh = NULL;
|
|
||||||
restconf_socket *rsock;
|
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if (close(s) < 0){
|
if (close(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(s, restconf_connection);
|
||||||
|
/* re-set timer */
|
||||||
|
if (rsock->rs_callhome &&
|
||||||
|
restconf_callhome_timer(rsock, 1) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------ Accept--------------------------------*/
|
||||||
|
|
||||||
if (0 && (rh = restconf_native_handle_get(h)) != NULL){
|
/*! Check ALPN result
|
||||||
while ((rsock = rh->rh_sockets) != NULL)
|
* @proto[out] proto
|
||||||
if (rsock->rs_callhome && s == rsock->rs_ss){
|
* @retval 1 OK with proto set
|
||||||
/* Maybe we should have a special wrapper function that only sets timer here? */
|
* @retval 0 Fail, ALPN null or not recognized
|
||||||
if (restconf_callhome_timer(0, rsock) < 0)
|
* @retval -1 Error
|
||||||
goto done;
|
*/
|
||||||
break;
|
static int
|
||||||
}
|
ssl_alpn_check(clicon_handle h,
|
||||||
|
const unsigned char *alpn,
|
||||||
|
unsigned int alpnlen,
|
||||||
|
restconf_conn *rc,
|
||||||
|
restconf_http_proto *proto)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int ret;
|
||||||
|
cbuf *cberr = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
/* Alternatively, call restconf_str2proto but alpn is not a proper string */
|
||||||
|
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
|
||||||
|
*proto = HTTP_11;
|
||||||
|
retval = 1; /* http/1.1 */
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
else if (alpn && alpnlen == 2 && memcmp("h2", alpn, 2) == 0){
|
||||||
|
*proto = HTTP_2;
|
||||||
|
retval = 1; /* http/2 */
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
|
if ((cberr = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr));
|
||||||
|
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl,
|
||||||
|
"application/yang-data+xml",
|
||||||
|
cbuf_get(cberr), rc->rc_socket) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
/* 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__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc->rc_ssl){
|
||||||
|
/* 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 */
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
|
if (cberr)
|
||||||
|
cbuf_free(cberr);
|
||||||
|
return retval;
|
||||||
|
} /* ssl_alpn_check */
|
||||||
|
|
||||||
|
/*! Accept new socket client. Note SSL not ip, this applies also to callhome
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] s Socket (unix or ip)
|
||||||
|
* @param[in] rsock Socket struct
|
||||||
|
* @see openssl_init_socket where this callback is registered
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_ssl_accept_client(clicon_handle h,
|
||||||
|
int s,
|
||||||
|
restconf_socket *rsock)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
restconf_native_handle *rh = NULL;
|
||||||
|
restconf_conn *rc = NULL;
|
||||||
|
char *name = NULL;
|
||||||
|
int ret;
|
||||||
|
int e;
|
||||||
|
int er;
|
||||||
|
int readmore;
|
||||||
|
const unsigned char *alpn = NULL;
|
||||||
|
unsigned int alpnlen = 0;
|
||||||
|
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
#ifndef HAVE_HTTP1
|
||||||
|
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
if ((rh = restconf_native_handle_get(h)) == NULL){
|
||||||
|
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Register callbacks for actual data socket
|
||||||
|
*/
|
||||||
|
if ((rc = restconf_conn_new(h, s, rsock)) == NULL)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s);
|
||||||
|
if (rsock->rs_ssl){
|
||||||
|
if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s SSL_new(%p)", __FUNCTION__, rc->rc_ssl);
|
||||||
|
/* CCL_CTX_set_verify already set, need not call SSL_set_verify again for this server
|
||||||
|
*/
|
||||||
|
/* X509_CHECK_FLAG_NO_WILDCARDS disables wildcard expansion */
|
||||||
|
SSL_set_hostflags(rc->rc_ssl, X509_CHECK_FLAG_NO_WILDCARDS);
|
||||||
|
#if 0
|
||||||
|
/* XXX This code is kept for the time being just for reference, it does not belong here.
|
||||||
|
* If you want to restrict client certs to a specific set.
|
||||||
|
* Otherwise this is done in restcon ca-auth callback and ultimately NACM
|
||||||
|
* SSL_set1_host() sets the expected DNS hostname to name
|
||||||
|
C = SE
|
||||||
|
L = Stockholm
|
||||||
|
O = Clixon
|
||||||
|
OU = clixon
|
||||||
|
CN = ca <---
|
||||||
|
emailAddress = olof@hagsand.se
|
||||||
|
*/
|
||||||
|
if (SSL_set1_host(rc->rc_ssl, "andy") != 1) { /* for peer cert */
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_set1_host");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (SSL_add1_host(rc->rc_ssl, "olof") != 1) { /* for peer cert */
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_set1_host");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (SSL_set_fd(rc->rc_ssl, rc->rc_s) != 1){
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_set_fd");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
readmore = 1;
|
||||||
|
while (readmore){
|
||||||
|
readmore = 0;
|
||||||
|
/* 1: OK, -1 fatal, 0: TLS/SSL handshake was not successful
|
||||||
|
* Both error cases: Call SSL_get_error() with the return value ret
|
||||||
|
*/
|
||||||
|
if ((ret = SSL_accept(rc->rc_ssl)) != 1) {
|
||||||
|
clicon_debug(1, "%s SSL_accept() ret:%d errno:%d", __FUNCTION__, ret, er=errno);
|
||||||
|
e = SSL_get_error(rc->rc_ssl, ret);
|
||||||
|
switch (e){
|
||||||
|
case SSL_ERROR_SSL: /* 1 */
|
||||||
|
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
|
||||||
|
#if 1
|
||||||
|
if (native_send_badrequest(h, rc->rc_s, NULL, "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->rc_socket) < 0)
|
||||||
|
goto done;
|
||||||
|
#endif
|
||||||
|
SSL_free(rc->rc_ssl);
|
||||||
|
rc->rc_ssl = NULL;
|
||||||
|
if (restconf_connection_close(h, rc->rc_s, rc->rc_socket) < 0)
|
||||||
|
goto done;
|
||||||
|
restconf_conn_free(rc);
|
||||||
|
goto ok;
|
||||||
|
break;
|
||||||
|
case 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.*/
|
||||||
|
clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er);
|
||||||
|
if (restconf_close_ssl_socket(rc, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
restconf_conn_free(rc);
|
||||||
|
rc = NULL;
|
||||||
|
goto ok;
|
||||||
|
break;
|
||||||
|
case SSL_ERROR_WANT_READ: /* 2 */
|
||||||
|
case SSL_ERROR_WANT_WRITE: /* 3 */
|
||||||
|
/* SSL_ERROR_WANT_READ is returned when the last operation was a read operation
|
||||||
|
* from a nonblocking BIO.
|
||||||
|
* That is, it can happen if restconf_socket_init() below is called
|
||||||
|
* with SOCK_NONBLOCK
|
||||||
|
*/
|
||||||
|
clicon_debug(1, "%s write SSL_ERROR_WANT_READ", __FUNCTION__);
|
||||||
|
usleep(10000);
|
||||||
|
readmore = 1;
|
||||||
|
break;
|
||||||
|
case SSL_ERROR_NONE: /* 0 */
|
||||||
|
case SSL_ERROR_ZERO_RETURN: /* 6 */
|
||||||
|
case SSL_ERROR_WANT_CONNECT: /* 7 */
|
||||||
|
case SSL_ERROR_WANT_ACCEPT: /* 8 */
|
||||||
|
case SSL_ERROR_WANT_X509_LOOKUP: /* 4 */
|
||||||
|
case SSL_ERROR_WANT_ASYNC: /* 8 */
|
||||||
|
case SSL_ERROR_WANT_ASYNC_JOB: /* 10 */
|
||||||
|
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
|
||||||
|
case SSL_ERROR_WANT_CLIENT_HELLO_CB: /* 11 */
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_accept:%d", e);
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} /* SSL_accept */
|
||||||
|
} /* while(readmore) */
|
||||||
|
/* Sets data and len to point to the client's requested protocol for this connection. */
|
||||||
|
#ifndef OPENSSL_NO_NEXTPROTONEG
|
||||||
|
SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen);
|
||||||
|
#endif /* !OPENSSL_NO_NEXTPROTONEG */
|
||||||
|
if (alpn == NULL) {
|
||||||
|
/* Returns a pointer to the selected protocol in data with length len. */
|
||||||
|
SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen);
|
||||||
|
}
|
||||||
|
if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0)
|
||||||
|
goto ok;
|
||||||
|
clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto));
|
||||||
|
|
||||||
|
#if 0 /* Seems too early to fail here, instead let authentication callback deal with this */
|
||||||
|
/* For client-cert authentication, check if any certs are present,
|
||||||
|
* if not, send bad request
|
||||||
|
* Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
|
||||||
|
* but then SSL_accept fails.
|
||||||
|
*/
|
||||||
|
if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){
|
||||||
|
X509 *peercert;
|
||||||
|
|
||||||
|
if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){
|
||||||
|
X509_free(peercert);
|
||||||
|
}
|
||||||
|
else { /* Get certificates (if available) */
|
||||||
|
if (proto != HTTP_2 &&
|
||||||
|
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)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ?
|
||||||
|
* Note this _only_ works if SSL_set1_host() was set previously,...
|
||||||
|
*/
|
||||||
|
if ((ret = SSL_get_verify_result(rc->rc_ssl)) == X509_V_OK) { /* for peer cert */
|
||||||
|
const char *peername = SSL_get0_peername(rc->rc_ssl);
|
||||||
|
if (peername != NULL) {
|
||||||
|
/* Name checks were in scope and matched the peername */
|
||||||
|
clicon_debug(1, "%s peername:%s", __FUNCTION__, peername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
else{
|
||||||
|
clicon_log(LOG_NOTICE, "Cert error: %s", X509_verify_cert_error_string(ret));
|
||||||
|
/* Maybe should return already here, but to get proper return message need to
|
||||||
|
* continue to http/1 or http/2 handling
|
||||||
|
* @see restconf_connection_sanity
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if 0 /* debug */
|
||||||
|
if (clicon_debug_get())
|
||||||
|
restconf_listcerts(rc->rc_ssl);
|
||||||
|
#endif
|
||||||
|
} /* if ssl */
|
||||||
|
rc->rc_proto = proto;
|
||||||
|
switch (rc->rc_proto){
|
||||||
|
#ifdef HAVE_HTTP1
|
||||||
|
case HTTP_10:
|
||||||
|
case HTTP_11:
|
||||||
|
/* Create a default stream for http/1 */
|
||||||
|
if (restconf_stream_data_new(rc, 0) == NULL)
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
#endif /* HAVE_HTTP1 */
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
case HTTP_2:{
|
||||||
|
if (http2_session_init(rc) < 0){
|
||||||
|
restconf_close_ssl_socket(rc, 1);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (http2_send_server_connection(rc) < 0){
|
||||||
|
restconf_close_ssl_socket(rc, 1);
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_LIBNGHTTP2 */
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
} /* switch proto */
|
||||||
|
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
|
||||||
|
goto done;
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
||||||
|
if (name)
|
||||||
|
free(name);
|
||||||
|
return retval;
|
||||||
|
} /* restconf_ssl_accept_client */
|
||||||
|
|
||||||
|
/*! Callhome timer callback
|
||||||
|
*
|
||||||
|
* @param[in] fd No-op
|
||||||
|
* @param[in] arg restconf_socket
|
||||||
|
* Can be called directly, but typically call wrapper restconf_callhome_timer instead
|
||||||
|
* the reason is that this function may continue with SSL accept handling whereas the wrapper always
|
||||||
|
* returns directly.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_callhome_cb(int fd,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
clicon_handle h;
|
||||||
|
restconf_socket *rsock = NULL;
|
||||||
|
struct sockaddr_in6 sin6 = {0,}; // because its larger than sin and sa
|
||||||
|
struct sockaddr *sa = (struct sockaddr *)&sin6;
|
||||||
|
size_t sa_len;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
if ((rsock = (restconf_socket *)arg) == NULL){
|
||||||
|
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (rsock->rs_description)
|
||||||
|
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
|
||||||
|
else
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
h = rsock->rs_h;
|
||||||
|
/* 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)
|
||||||
|
goto done;
|
||||||
|
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
||||||
|
clicon_err(OE_UNIX, errno, "socket");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s connect %hu", __FUNCTION__, rsock->rs_port);
|
||||||
|
if (connect(s, sa, sa_len) < 0){
|
||||||
|
clicon_debug(1, "%s connect:%d %s", __FUNCTION__, errno, strerror(errno));
|
||||||
|
close(s);
|
||||||
|
rsock->rs_attempts++;
|
||||||
|
rsock->rs_ss = -1;
|
||||||
|
/* Fail: Initiate new timer */
|
||||||
|
if (restconf_callhome_timer(rsock, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rsock->rs_ss = s;
|
||||||
|
rsock->rs_attempts = 0;
|
||||||
|
if (restconf_ssl_accept_client(h, s, rsock) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s connect done", __FUNCTION__);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Set callhome timer, which tries to connect to callhome client
|
||||||
|
*
|
||||||
|
* Implement callhome re-connect strategies in ietf-restconf-server.yang
|
||||||
|
* NYI: start-with, anchor-time, idle-timeout
|
||||||
|
* @param[in] rsock restconf_socket
|
||||||
|
* @param[in] new Start a new period (if periodic)
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_callhome_timer(restconf_socket *rsock,
|
||||||
|
int new)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
struct timeval now;
|
||||||
|
struct timeval t;
|
||||||
|
struct timeval t1 = {0, 0};
|
||||||
|
cbuf *cb = NULL;
|
||||||
|
|
||||||
|
if (rsock->rs_description)
|
||||||
|
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
|
||||||
|
else
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if (!rsock->rs_callhome)
|
||||||
|
goto ok; /* shouldnt happen */
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
if (rsock->rs_periodic){
|
||||||
|
if (new || rsock->rs_attempts >= rsock->rs_max_attempts){
|
||||||
|
rsock->rs_period_nr++;
|
||||||
|
rsock->rs_attempts = 0;
|
||||||
|
t1.tv_sec = rsock->rs_start + rsock->rs_period_nr*rsock->rs_period;
|
||||||
|
while (t1.tv_sec < now.tv_sec)
|
||||||
|
t1.tv_sec += rsock->rs_period;
|
||||||
|
t = t1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
t1.tv_sec = 1;
|
||||||
|
timeradd(&now, &t1, &t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{ /* persistent: try again: attempts? */
|
||||||
|
t1.tv_sec = 1;
|
||||||
|
timeradd(&now, &t1, &t);
|
||||||
|
}
|
||||||
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (rsock->rs_description)
|
||||||
|
cprintf(cb, "restconf callhome timer %s", rsock->rs_description);
|
||||||
|
else
|
||||||
|
cprintf(cb, "restconf callhome timer");
|
||||||
|
if (rsock->rs_description)
|
||||||
|
clicon_debug(1, "%s registering %s: %lu", __FUNCTION__, rsock->rs_description, t.tv_sec);
|
||||||
|
else
|
||||||
|
clicon_debug(1, "%s: %lu", __FUNCTION__, t.tv_sec);
|
||||||
|
/* Should be only place restconf_callhome_cb is registered */
|
||||||
|
if (clixon_event_reg_timeout(t,
|
||||||
|
restconf_callhome_cb,
|
||||||
|
rsock,
|
||||||
|
cbuf_get(cb)) < 0)
|
||||||
|
goto done;
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,28 +85,6 @@ 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 connection handle
|
|
||||||
* Per connection request
|
|
||||||
*/
|
|
||||||
typedef struct restconf_conn {
|
|
||||||
/* XXX rc_proto and rc_proto_d1/d2 may not both be necessary.
|
|
||||||
* remove rc_proto?
|
|
||||||
*/
|
|
||||||
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
|
|
||||||
int rc_proto_d1; /* parsed version digit 1 */
|
|
||||||
int rc_proto_d2; /* parsed version digit 2 */
|
|
||||||
int rc_s; /* Connection socket */
|
|
||||||
clicon_handle rc_h; /* Clixon handle */
|
|
||||||
SSL *rc_ssl; /* Structure for SSL connection */
|
|
||||||
restconf_stream_data *rc_streams; /* List of http/2 session streams */
|
|
||||||
int rc_exit; /* Set to close socket server-side */
|
|
||||||
/* Decision to keep lib-specific data here, otherwise new struct necessary
|
|
||||||
* drawback is specific includes need to go everywhere */
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
nghttp2_session *rc_ngsession; /* XXX Not sure it is needed */
|
|
||||||
#endif
|
|
||||||
} restconf_conn;
|
|
||||||
|
|
||||||
/* Restconf per socket handle
|
/* Restconf per socket handle
|
||||||
* Two types: listen and callhome.
|
* Two types: listen and callhome.
|
||||||
* Listen: Uses socket rs_ss to listen for connections and accepts them, creates one
|
* Listen: Uses socket rs_ss to listen for connections and accepts them, creates one
|
||||||
|
|
@ -117,6 +95,7 @@ typedef struct restconf_conn {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
qelem_t rs_qelem; /* List header */
|
qelem_t rs_qelem; /* List header */
|
||||||
clicon_handle rs_h; /* Clixon handle */
|
clicon_handle rs_h; /* Clixon handle */
|
||||||
|
char *rs_description; /* Description */
|
||||||
int rs_callhome; /* 0: listen, 1: callhome */
|
int rs_callhome; /* 0: listen, 1: callhome */
|
||||||
int rs_ss; /* Listen: Server socket, ready for accept
|
int rs_ss; /* Listen: Server socket, ready for accept
|
||||||
* Callhome: connect socket (same as restconf_conn->rc_s) */
|
* Callhome: connect socket (same as restconf_conn->rc_s) */
|
||||||
|
|
@ -125,8 +104,42 @@ typedef struct {
|
||||||
eg inet:ipv4-address or inet:ipv6-address */
|
eg inet:ipv4-address or inet:ipv6-address */
|
||||||
char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */
|
char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */
|
||||||
uint16_t rs_port; /* Protocol port */
|
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) */
|
||||||
|
|
||||||
|
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_socket;
|
||||||
|
|
||||||
|
/* Restconf connection handle
|
||||||
|
* Per connection request
|
||||||
|
*/
|
||||||
|
typedef struct restconf_conn {
|
||||||
|
/* XXX rc_proto and rc_proto_d1/d2 may not both be necessary.
|
||||||
|
* remove rc_proto?
|
||||||
|
*/
|
||||||
|
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
|
||||||
|
int rc_proto_d1; /* parsed version digit 1 */
|
||||||
|
int rc_proto_d2; /* parsed version digit 2 */
|
||||||
|
int rc_s; /* Connection socket */
|
||||||
|
clicon_handle rc_h; /* Clixon handle */
|
||||||
|
SSL *rc_ssl; /* Structure for SSL connection */
|
||||||
|
restconf_stream_data *rc_streams; /* List of http/2 session streams */
|
||||||
|
int rc_exit; /* Set to close socket server-side */
|
||||||
|
/* Decision to keep lib-specific data here, otherwise new struct necessary
|
||||||
|
* drawback is specific includes need to go everywhere */
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
nghttp2_session *rc_ngsession; /* XXX Not sure it is needed */
|
||||||
|
#endif
|
||||||
|
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
|
||||||
|
} restconf_conn;
|
||||||
|
|
||||||
|
|
||||||
/* Restconf handle
|
/* Restconf handle
|
||||||
* Global data about ssl (not per packet/request)
|
* Global data about ssl (not per packet/request)
|
||||||
*/
|
*/
|
||||||
|
|
@ -142,16 +155,19 @@ typedef struct {
|
||||||
restconf_stream_data *restconf_stream_data_new(restconf_conn *rc, int32_t stream_id);
|
restconf_stream_data *restconf_stream_data_new(restconf_conn *rc, int32_t stream_id);
|
||||||
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_conn *restconf_conn_new(clicon_handle h, int s, restconf_socket *socket);
|
||||||
int restconf_conn_free(restconf_conn *rc);
|
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, int shutdown); /* XXX in restconf_main_native.c */
|
||||||
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);
|
||||||
int native_send_badrequest(clicon_handle h, int s, SSL *ssl, char *media, char *body);
|
|
||||||
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);
|
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);
|
||||||
|
int restconf_callhome_cb(int fd, void *arg);
|
||||||
|
|
||||||
|
int restconf_callhome_timer(restconf_socket *rsock, int new);
|
||||||
|
|
||||||
#endif /* _RESTCONF_NATIVE_H_ */
|
#endif /* _RESTCONF_NATIVE_H_ */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
# test of Restconf callhome
|
# test of Restconf callhome
|
||||||
# See RFC 8071 NETCONF Call Home and RESTCONF Call Home
|
# See RFC 8071 NETCONF Call Home and RESTCONF Call Home
|
||||||
# Simple NACM for single "andy" user
|
# Simple NACM for single "andy" user
|
||||||
|
# The client is clixon_restconf_callhome_client that waits for accept, connects, sends a GET immediately,
|
||||||
|
# closes the socket and re-listens
|
||||||
|
# The server opens three sockets:
|
||||||
|
# 1) regular listen socket for setting init value
|
||||||
|
# 2) persistent socket
|
||||||
|
# 3) periodic socket (10s) port 8336
|
||||||
|
# XXX periodic: idle-timeout not tested
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
@ -19,6 +26,10 @@ cfg=$dir/conf_yang.xml
|
||||||
fyang=$dir/clixon-example.yang
|
fyang=$dir/clixon-example.yang
|
||||||
clispec=$dir/spec.cli
|
clispec=$dir/spec.cli
|
||||||
|
|
||||||
|
# HTTP request client->server
|
||||||
|
frequest=$dir/frequest
|
||||||
|
# HTTP expected reply server->client
|
||||||
|
|
||||||
certdir=$dir/certs
|
certdir=$dir/certs
|
||||||
cakey=$certdir/ca_key.pem
|
cakey=$certdir/ca_key.pem
|
||||||
cacert=$certdir/ca_cert.pem
|
cacert=$certdir/ca_cert.pem
|
||||||
|
|
@ -60,17 +71,39 @@ cat <<EOF > $cfg
|
||||||
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
||||||
<debug>1</debug>
|
<debug>1</debug>
|
||||||
<socket>
|
<socket>
|
||||||
|
<description>callhome persistent</description>
|
||||||
<namespace>default</namespace>
|
<namespace>default</namespace>
|
||||||
<call-home>
|
<call-home>
|
||||||
<connection-type>
|
<connection-type>
|
||||||
<persistent/>
|
<persistent/>
|
||||||
</connection-type>
|
</connection-type>
|
||||||
|
<reconnect-strategy>
|
||||||
|
<max-attempts>3</max-attempts>
|
||||||
|
</reconnect-strategy>
|
||||||
</call-home>
|
</call-home>
|
||||||
<address>127.0.0.1</address>
|
<address>127.0.0.1</address>
|
||||||
<port>4336</port>
|
<port>4336</port>
|
||||||
<ssl>true</ssl>
|
<ssl>true</ssl>
|
||||||
</socket>
|
</socket>
|
||||||
<socket>
|
<socket>
|
||||||
|
<description>callhome periodic</description>
|
||||||
|
<namespace>default</namespace>
|
||||||
|
<call-home>
|
||||||
|
<connection-type>
|
||||||
|
<periodic>
|
||||||
|
<period>10</period>
|
||||||
|
</periodic>
|
||||||
|
</connection-type>
|
||||||
|
<reconnect-strategy>
|
||||||
|
<max-attempts>3</max-attempts>
|
||||||
|
</reconnect-strategy>
|
||||||
|
</call-home>
|
||||||
|
<address>127.0.0.1</address>
|
||||||
|
<port>8336</port>
|
||||||
|
<ssl>true</ssl>
|
||||||
|
</socket>
|
||||||
|
<socket>
|
||||||
|
<description>listen</description>
|
||||||
<namespace>default</namespace>
|
<namespace>default</namespace>
|
||||||
<address>0.0.0.0</address>
|
<address>0.0.0.0</address>
|
||||||
<port>443</port>
|
<port>443</port>
|
||||||
|
|
@ -219,14 +252,16 @@ cat <<EOF > $dir/startup_db
|
||||||
</${DATASTORE_TOP}>
|
</${DATASTORE_TOP}>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Callhome request from client
|
# Callhome request from client->server
|
||||||
cat <<EOF > $dir/data
|
cat <<EOF > $frequest
|
||||||
GET /restconf/data/clixon-example:table HTTP/$HVERCH
|
GET /restconf/data/clixon-example:table HTTP/$HVERCH
|
||||||
Host: localhost
|
Host: localhost
|
||||||
Accept: application/yang-data+xml
|
Accept: application/yang-data+xml
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
expectreply="<table xmlns=\"urn:example:clixon\"><parameter><name>x</name><value>foo</value></parameter></table>"
|
||||||
|
|
||||||
new "test params: -f $cfg"
|
new "test params: -f $cfg"
|
||||||
# Bring your own backend
|
# Bring your own backend
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
|
|
@ -249,7 +284,7 @@ if [ $RC -ne 0 ]; then
|
||||||
stop_restconf_pre
|
stop_restconf_pre
|
||||||
|
|
||||||
new "start restconf daemon"
|
new "start restconf daemon"
|
||||||
start_restconf -f $cfg
|
start_restconf -f $cfg -D 1 -l s
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "wait restconf"
|
new "wait restconf"
|
||||||
|
|
@ -258,9 +293,39 @@ wait_restconf
|
||||||
new "restconf Add init data"
|
new "restconf Add init data"
|
||||||
expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201"
|
expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201"
|
||||||
|
|
||||||
new "Send GET via callhome client"
|
# tests for (one) well-formed HTTP status and three replies and that the time is reasonable
|
||||||
echo "${clixon_restconf_callhome_client} -D $DBG -f $dir/data -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert"
|
t0=$(date +"%s")
|
||||||
expectpart "$(${clixon_restconf_callhome_client} -D $DBG -f $dir/data -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert)" 0 "HTTP/$HVERCH 200 OK" "Content-Type: application/yang-data+xml"
|
new "Send GET via callhome persistence client port 4336"
|
||||||
|
expectpart "$(${clixon_restconf_callhome_client} -p 4336 -D $DBG -f $frequest -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert -n 3)" 0 "HTTP/$HVERCH 200" "OK 1" "OK 2" "OK 3" $expectreply --not-- "OK 4"
|
||||||
|
t1=$(date +"%s")
|
||||||
|
|
||||||
|
let t=t1-t0
|
||||||
|
new "Check persistent interval ($t) is in interval [2,4]"
|
||||||
|
if [ $t -lt 2 -o $t -ge 4 ]; then
|
||||||
|
err1 "timer in interval [2,4] but is: $t"
|
||||||
|
fi
|
||||||
|
|
||||||
|
t0=$(date +"%s")
|
||||||
|
new "Send GET via callhome client periodic port 8336"
|
||||||
|
expectpart "$(${clixon_restconf_callhome_client} -p 8336 -D $DBG -f $frequest -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert -n 3)" 0 "HTTP/$HVERCH 200" "OK 1" "OK 2" "OK 3" $expectreply --not-- "OK "4
|
||||||
|
t1=$(date +"%s")
|
||||||
|
|
||||||
|
let t=t1-t0
|
||||||
|
new "Check periodic interval ($t) is larger than 20 and less than 31"
|
||||||
|
if [ $t -lt 20 -o $t -ge 31 ]; then
|
||||||
|
err1 "timer in interval [20-31] but is: $t"
|
||||||
|
fi
|
||||||
|
|
||||||
|
t0=$(date +"%s")
|
||||||
|
new "Send GET via callhome persistence again"
|
||||||
|
expectpart "$(${clixon_restconf_callhome_client} -p 4336 -D $DBG -f $frequest -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert -n 3)" 0 "OK 1" "OK 2" "OK 3" --not-- "OK 4"
|
||||||
|
t1=$(date +"%s")
|
||||||
|
|
||||||
|
let t=t1-t0
|
||||||
|
new "Check persistent interval ($t) is in interval [2,4]"
|
||||||
|
if [ $t -lt 2 -o $t -ge 4 ]; then
|
||||||
|
err1 "timer in interval [2,4] but is: $t"
|
||||||
|
fi
|
||||||
|
|
||||||
# Kill old
|
# Kill old
|
||||||
if [ $RC -ne 0 ]; then
|
if [ $RC -ne 0 ]; then
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@
|
||||||
|
|
||||||
The callhome-client listens on accept, when connect comes in, creates data socket and sends
|
The callhome-client listens on accept, when connect comes in, creates data socket and sends
|
||||||
RESTCONF GET to server, then re-waits for new accepts.
|
RESTCONF GET to server, then re-waits for new accepts.
|
||||||
|
When accepting a connection, send HTTP data from input or -f <file> tehn wait for reply
|
||||||
|
Reply is matched with -e <expectfile> or printed on stdout
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
|
|
@ -66,7 +68,7 @@
|
||||||
/* clixon */
|
/* clixon */
|
||||||
#include "clixon/clixon.h"
|
#include "clixon/clixon.h"
|
||||||
|
|
||||||
#define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:n:"
|
#define UTIL_TLS_OPTS "hD:f:e:F:a:p:c:C:k:n:"
|
||||||
|
|
||||||
#define RESTCONF_CH_TLS 4336
|
#define RESTCONF_CH_TLS 4336
|
||||||
|
|
||||||
|
|
@ -143,18 +145,63 @@ callhome_bind(struct sockaddr *sa,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Client data socket
|
/*! read data from file return a malloced buffer
|
||||||
|
* Note same file is reread multiple times: same request/reply is made each iteration
|
||||||
|
* Also, the file read is limited to 1024 bytes
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
tls_input_cb(int s,
|
read_data_file(FILE *fe,
|
||||||
void *arg)
|
char **bufp,
|
||||||
|
size_t *lenp)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
char *buf = NULL;
|
||||||
|
int buflen = 1024; /* start size */
|
||||||
|
char ch;
|
||||||
|
size_t len = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if ((buf = malloc(buflen)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(buf, 0, buflen);
|
||||||
|
/* Start file form beginning */
|
||||||
|
rewind(fe);
|
||||||
|
while (1){
|
||||||
|
if ((ret = fread(&ch, 1, 1, fe)) < 0){
|
||||||
|
clicon_err(OE_JSON, errno, "fread");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
buf[len++] = ch;
|
||||||
|
// XXX No realloc, can overflow
|
||||||
|
}
|
||||||
|
*bufp = buf;
|
||||||
|
*lenp = len;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Client data socket, receive reply from server
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_server_reply_cb(int s,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
tls_session_data *sd = (tls_session_data *)arg;
|
tls_session_data *sd = (tls_session_data *)arg;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
int n;
|
int n;
|
||||||
|
char *expbuf = NULL;
|
||||||
|
struct timeval tn;
|
||||||
|
struct timeval td;
|
||||||
|
static int seq = 1;
|
||||||
|
static struct timeval t0 = {0,};
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
ssl = sd->sd_ssl;
|
ssl = sd->sd_ssl;
|
||||||
/* get reply & decrypt */
|
/* get reply & decrypt */
|
||||||
|
|
@ -167,10 +214,14 @@ tls_input_cb(int s,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
buf[n] = 0;
|
buf[n] = 0;
|
||||||
fprintf(stdout, "%s\n", buf);
|
if (seq == 1)
|
||||||
|
gettimeofday(&t0, NULL);
|
||||||
|
gettimeofday(&tn, NULL);
|
||||||
|
timersub(&tn, &t0, &td);
|
||||||
|
fprintf(stdout, "OK %d %lu\n%s\n", seq++, td.tv_sec, buf);
|
||||||
SSL_shutdown(ssl);
|
SSL_shutdown(ssl);
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
clixon_event_unreg_fd(s, tls_input_cb);
|
clixon_event_unreg_fd(s, tls_server_reply_cb);
|
||||||
close(s);
|
close(s);
|
||||||
free(sd);
|
free(sd);
|
||||||
if (_connects == 1)
|
if (_connects == 1)
|
||||||
|
|
@ -179,6 +230,8 @@ tls_input_cb(int s,
|
||||||
_connects--;
|
_connects--;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (expbuf)
|
||||||
|
free(expbuf);
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
@ -260,30 +313,15 @@ tls_write_file(FILE *fp,
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *buf = NULL;
|
char *buf = NULL;
|
||||||
int buflen = 1024; /* start size */
|
size_t len = 0;
|
||||||
char ch;
|
|
||||||
int ret;
|
int ret;
|
||||||
int sslerr;
|
int sslerr;
|
||||||
size_t len = 0;
|
|
||||||
|
|
||||||
if ((buf = malloc(buflen)) == NULL){
|
if (read_data_file(fp, &buf, &len) < 0)
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
|
||||||
memset(buf, 0, buflen);
|
|
||||||
while (1){
|
|
||||||
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
|
||||||
clicon_err(OE_JSON, errno, "read");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (ret == 0)
|
|
||||||
break;
|
|
||||||
buf[len++] = ch;
|
|
||||||
// XXX No realloc, can overflow
|
|
||||||
}
|
|
||||||
if ((ret = SSL_write(ssl, buf, len)) < 1){
|
if ((ret = SSL_write(ssl, buf, len)) < 1){
|
||||||
sslerr = SSL_get_error(ssl, ret);
|
sslerr = SSL_get_error(ssl, ret);
|
||||||
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
clicon_debug(1, "%s SSL_write() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -292,11 +330,11 @@ tls_write_file(FILE *fp,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Callhome-server accept socket
|
/*! Callhome-server accept socket
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
tls_accept_cb(int ss,
|
tls_server_accept_cb(int ss,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
||||||
|
|
@ -327,7 +365,7 @@ tls_accept_cb(int ss,
|
||||||
if (tls_write_file(ta->ta_f, ssl) < 0)
|
if (tls_write_file(ta->ta_f, ssl) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* register callback for reply */
|
/* register callback for reply */
|
||||||
if (clixon_event_reg_fd(s, tls_input_cb, sd, "tls data") < 0)
|
if (clixon_event_reg_fd(s, tls_server_reply_cb, sd, "tls server reply") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -400,7 +438,8 @@ usage(char *argv0)
|
||||||
"where options are\n"
|
"where options are\n"
|
||||||
"\t-h \t\tHelp\n"
|
"\t-h \t\tHelp\n"
|
||||||
"\t-D <level> \tDebug\n"
|
"\t-D <level> \tDebug\n"
|
||||||
"\t-f <file> \tHHTP input file (overrides stdin)\n"
|
"\t-f <file> \tHTTP input file (overrides stdin)\n"
|
||||||
|
"\t-e <file> \tFile including HTTP output (otherwise echo to stdout)\n"
|
||||||
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
|
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
|
||||||
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
|
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
|
||||||
"\t-p <port> \tPort (default %d)\n"
|
"\t-p <port> \tPort (default %d)\n"
|
||||||
|
|
@ -528,14 +567,14 @@ main(int argc,
|
||||||
ta->ta_ctx = ctx;
|
ta->ta_ctx = ctx;
|
||||||
ta->ta_ss = ss;
|
ta->ta_ss = ss;
|
||||||
ta->ta_f = fp;
|
ta->ta_f = fp;
|
||||||
if (clixon_event_reg_fd(ss, tls_accept_cb, ta, "tls accept socket") < 0)
|
if (clixon_event_reg_fd(ss, tls_server_accept_cb, ta, "tls server accept") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (clixon_event_loop(h) < 0)
|
if (clixon_event_loop(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (ss != -1)
|
if (ss != -1)
|
||||||
clixon_event_unreg_fd(ss, tls_accept_cb);
|
clixon_event_unreg_fd(ss, tls_server_accept_cb);
|
||||||
if (ta)
|
if (ta)
|
||||||
free(ta);
|
free(ta);
|
||||||
if (fp)
|
if (fp)
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,9 @@ module clixon-restconf {
|
||||||
On platforms where namespaces are not suppported, 'default'
|
On platforms where namespaces are not suppported, 'default'
|
||||||
Default value can be changed by RESTCONF_NETNS_DEFAULT";
|
Default value can be changed by RESTCONF_NETNS_DEFAULT";
|
||||||
}
|
}
|
||||||
|
leaf description{
|
||||||
|
type string;
|
||||||
|
}
|
||||||
leaf address {
|
leaf address {
|
||||||
type inet:ip-address;
|
type inet:ip-address;
|
||||||
description "IP address to bind to";
|
description "IP address to bind to";
|
||||||
|
|
@ -299,28 +302,28 @@ module clixon-restconf {
|
||||||
presence
|
presence
|
||||||
"Indicates periodic connects";
|
"Indicates periodic connects";
|
||||||
leaf period {
|
leaf period {
|
||||||
type uint16;
|
type uint32; /* XXX: note uit16 in std */
|
||||||
units "minutes";
|
units "seconds"; /* XXX: note minutes in standard */
|
||||||
default "60";
|
default "60";
|
||||||
description
|
description
|
||||||
"Duration of time between periodic connections.";
|
"Duration of time between periodic connections.";
|
||||||
}
|
}
|
||||||
leaf idle-timeout {
|
|
||||||
type uint16;
|
|
||||||
units "seconds";
|
|
||||||
default "120"; // two minutes
|
|
||||||
description
|
|
||||||
"Specifies the maximum number of seconds that
|
|
||||||
the underlying TCP session may remain idle.
|
|
||||||
A TCP session will be dropped if it is idle
|
|
||||||
for an interval longer than this number of
|
|
||||||
seconds. If set to zero, then the server
|
|
||||||
will never drop a session because it is idle.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
container reconnect-strategy {
|
||||||
|
leaf max-attempts {
|
||||||
|
type uint8 {
|
||||||
|
range "1..max";
|
||||||
|
}
|
||||||
|
default "3";
|
||||||
|
description
|
||||||
|
"Specifies the number times the RESTCONF server tries
|
||||||
|
to connect to a specific endpoint before moving on to
|
||||||
|
the next endpoint in the list (round robin).";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
module ietf-list-pagination-nc {
|
|
||||||
yang-version 1.1;
|
|
||||||
namespace "urn:ietf:params:xml:ns:yang:ietf-list-pagination-nc";
|
|
||||||
prefix lpgnc;
|
|
||||||
|
|
||||||
import ietf-netconf {
|
|
||||||
prefix nc;
|
|
||||||
reference
|
|
||||||
"RFC 6241: Network Configuration Protocol (NETCONF)";
|
|
||||||
}
|
|
||||||
|
|
||||||
import ietf-netconf-nmda {
|
|
||||||
prefix ncds;
|
|
||||||
reference
|
|
||||||
"RFC 8526: NETCONF Extensions to Support the
|
|
||||||
Network Management Datastore Architecture";
|
|
||||||
}
|
|
||||||
|
|
||||||
import ietf-list-pagination {
|
|
||||||
prefix lp;
|
|
||||||
reference
|
|
||||||
"RFC XXXX: List Pagination for YANG-driven Protocols";
|
|
||||||
}
|
|
||||||
|
|
||||||
organization
|
|
||||||
"IETF NETCONF (Network Configuration) Working Group";
|
|
||||||
|
|
||||||
contact
|
|
||||||
"WG Web: <http://tools.ietf.org/wg/netconf/>
|
|
||||||
WG List: <mailto:netconf@ietf.org>";
|
|
||||||
|
|
||||||
description
|
|
||||||
"This module augments the <get>, <get-config>, and <get-data>
|
|
||||||
'rpc' statements to support list pagination.
|
|
||||||
|
|
||||||
Copyright (c) 2021 IETF Trust and the persons identified
|
|
||||||
as authors of the code. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with
|
|
||||||
or without modification, is permitted pursuant to, and
|
|
||||||
subject to the license terms contained in, the Simplified
|
|
||||||
BSD License set forth in Section 4.c of the IETF Trust's
|
|
||||||
Legal Provisions Relating to IETF Documents
|
|
||||||
(https://trustee.ietf.org/license-info).
|
|
||||||
|
|
||||||
This version of this YANG module is part of RFC XXXX
|
|
||||||
(https://www.rfc-editor.org/info/rfcXXXX); see the RFC
|
|
||||||
itself for full legal notices.
|
|
||||||
|
|
||||||
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
|
|
||||||
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
|
|
||||||
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
|
|
||||||
are to be interpreted as described in BCP 14 (RFC 2119)
|
|
||||||
(RFC 8174) when, and only when, they appear in all
|
|
||||||
capitals, as shown here.";
|
|
||||||
|
|
||||||
revision 2021-10-25 {
|
|
||||||
description
|
|
||||||
"Initial revision.";
|
|
||||||
reference
|
|
||||||
"RFC XXXX: NETCONF Extensions to Support List Pagination";
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping pagination-parameters {
|
|
||||||
description "A grouping for list pagination parameters.";
|
|
||||||
container list-pagination {
|
|
||||||
description "List pagination parameters.";
|
|
||||||
presence "Flag that request contains pagination parameters";
|
|
||||||
uses lp:where-param-grouping;
|
|
||||||
uses lp:sort-by-param-grouping;
|
|
||||||
uses lp:direction-param-grouping;
|
|
||||||
uses lp:offset-param-grouping;
|
|
||||||
uses lp:limit-param-grouping;
|
|
||||||
uses lp:sublist-limit-param-grouping;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
augment "/nc:get/nc:input" {
|
|
||||||
description
|
|
||||||
"Allow the 'get' operation to use content filter
|
|
||||||
parameter for specifying the YANG list or leaf-list
|
|
||||||
that is to be retrieved";
|
|
||||||
uses pagination-parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
augment "/nc:get-config/nc:input" {
|
|
||||||
description
|
|
||||||
"Allow the 'get-config' operation to use content filter
|
|
||||||
parameter for specifying the YANG list or leaf-list
|
|
||||||
that is to be retrieved";
|
|
||||||
uses pagination-parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
augment "/ncds:get-data/ncds:input" {
|
|
||||||
description
|
|
||||||
"Allow the 'get-data' operation to use content filter
|
|
||||||
parameter for specifying the YANG list or leaf-list
|
|
||||||
that is to be retrieved";
|
|
||||||
uses pagination-parameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
module ietf-list-pagination {
|
|
||||||
yang-version 1.1;
|
|
||||||
namespace
|
|
||||||
"urn:ietf:params:xml:ns:yang:ietf-list-pagination";
|
|
||||||
prefix lpg;
|
|
||||||
|
|
||||||
import ietf-yang-types {
|
|
||||||
prefix yang;
|
|
||||||
reference
|
|
||||||
"RFC 6991: Common YANG Data Types";
|
|
||||||
}
|
|
||||||
|
|
||||||
import ietf-yang-metadata {
|
|
||||||
prefix md;
|
|
||||||
reference
|
|
||||||
"RFC 7952: Defining and Using Metadata with YANG";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* XXX system-capabilities brings in NACM that breaks clixon testing
|
|
||||||
import ietf-system-capabilities {
|
|
||||||
prefix sysc;
|
|
||||||
reference
|
|
||||||
"draft-ietf-netconf-notification-capabilities:
|
|
||||||
YANG Modules describing Capabilities for
|
|
||||||
Systems and Datastore Update Notifications";
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
organization
|
|
||||||
"IETF NETCONF (Network Configuration) Working Group";
|
|
||||||
|
|
||||||
contact
|
|
||||||
"WG Web: <http://tools.ietf.org/wg/netconf/>
|
|
||||||
WG List: <mailto:netconf@ietf.org>";
|
|
||||||
|
|
||||||
description
|
|
||||||
"This module is used by servers to 1) indicate they support
|
|
||||||
pagination on 'list' and 'leaf-list' resources, 2) define a
|
|
||||||
grouping for each list-pagination parameter, and 3) indicate
|
|
||||||
which 'config false' lists have constrained 'where' and
|
|
||||||
'sort-by' parameters and how they may be used, if at all.
|
|
||||||
|
|
||||||
Copyright (c) 2021 IETF Trust and the persons identified
|
|
||||||
as authors of the code. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with
|
|
||||||
or without modification, is permitted pursuant to, and
|
|
||||||
subject to the license terms contained in, the Simplified
|
|
||||||
BSD License set forth in Section 4.c of the IETF Trust's
|
|
||||||
Legal Provisions Relating to IETF Documents
|
|
||||||
(https://trustee.ietf.org/license-info).
|
|
||||||
|
|
||||||
This version of this YANG module is part of RFC XXXX
|
|
||||||
(https://www.rfc-editor.org/info/rfcXXXX); see the RFC
|
|
||||||
itself for full legal notices.
|
|
||||||
|
|
||||||
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
|
|
||||||
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
|
|
||||||
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
|
|
||||||
are to be interpreted as described in BCP 14 (RFC 2119)
|
|
||||||
(RFC 8174) when, and only when, they appear in all
|
|
||||||
capitals, as shown here.";
|
|
||||||
|
|
||||||
revision 2021-10-25 {
|
|
||||||
description
|
|
||||||
"Initial revision.";
|
|
||||||
reference
|
|
||||||
"RFC XXXX: List Pagination for YANG-driven Protocols";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Annotations
|
|
||||||
|
|
||||||
md:annotation remaining {
|
|
||||||
type union {
|
|
||||||
type uint32;
|
|
||||||
type enumeration {
|
|
||||||
enum "unknown" {
|
|
||||||
description
|
|
||||||
"Indicates that number of remaining entries is unknown
|
|
||||||
to the server in case, e.g., the server has determined
|
|
||||||
that counting would be prohibitively expensive.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"This annotation contains the number of elements not included
|
|
||||||
in the result set (a positive value) due to a 'limit' or
|
|
||||||
'sublist-limit' operation. If no elements were removed,
|
|
||||||
this annotation MUST NOT appear. The minimum value (0),
|
|
||||||
which never occurs in normal operation, is reserved to
|
|
||||||
represent 'unknown'. The maximum value (2^32-1) is
|
|
||||||
reserved to represent any value greater than or equal
|
|
||||||
to 2^32-1 elements.";
|
|
||||||
}
|
|
||||||
// Identities
|
|
||||||
|
|
||||||
identity list-pagination-error {
|
|
||||||
description
|
|
||||||
"Base identity for list-pagination errors.";
|
|
||||||
}
|
|
||||||
|
|
||||||
identity offset-out-of-range {
|
|
||||||
base list-pagination-error;
|
|
||||||
description
|
|
||||||
"The 'offset' query parameter value is greater than the number
|
|
||||||
of instances in the target list or leaf-list resource.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Groupings
|
|
||||||
|
|
||||||
grouping where-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf where {
|
|
||||||
type union {
|
|
||||||
type yang:xpath1.0;
|
|
||||||
type enumeration {
|
|
||||||
enum "unfiltered" {
|
|
||||||
description
|
|
||||||
"Indicates that no entries are to be filtered
|
|
||||||
from the working result-set.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default "unfiltered";
|
|
||||||
description
|
|
||||||
"The 'where' parameter specifies a boolean expression
|
|
||||||
that result-set entries must match.
|
|
||||||
|
|
||||||
It is an error if the XPath expression references a node
|
|
||||||
identifier that does not exist in the schema, is optional
|
|
||||||
or conditional in the schema or, for constrained 'config
|
|
||||||
false' lists and leaf-lists, if the node identifier does
|
|
||||||
not point to a node having the 'indexed' extension
|
|
||||||
statement applied to it (see RFC XXXX).";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping sort-by-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf sort-by {
|
|
||||||
type union {
|
|
||||||
type string {
|
|
||||||
// An RFC 7950 'descendant-schema-nodeid'.
|
|
||||||
pattern '([0-9a-fA-F]*:)?[0-9a-fA-F]*'
|
|
||||||
+ '(/([0-9a-fA-F]*:)?[0-9a-fA-F]*)*';
|
|
||||||
}
|
|
||||||
type enumeration {
|
|
||||||
enum "none" {
|
|
||||||
description
|
|
||||||
"Indicates that the list or leaf-list's default
|
|
||||||
order is to be used, per the YANG 'ordered-by'
|
|
||||||
statement.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default "none";
|
|
||||||
description
|
|
||||||
"The 'sort-by' parameter indicates the node in the
|
|
||||||
working result-set (i.e., after the 'where' parameter
|
|
||||||
has been applied) that entries should be sorted by.
|
|
||||||
|
|
||||||
Sorts are in ascending order (e.g., '1' before '9',
|
|
||||||
'a' before 'z', etc.). Missing values are sorted to
|
|
||||||
the end (e.g., after all nodes having values).";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping direction-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf direction {
|
|
||||||
type enumeration {
|
|
||||||
enum forwards {
|
|
||||||
description
|
|
||||||
"Indicates that entries should be traversed from
|
|
||||||
the first to last item in the working result set.";
|
|
||||||
}
|
|
||||||
enum backwards {
|
|
||||||
description
|
|
||||||
"Indicates that entries should be traversed from
|
|
||||||
the last to first item in the working result set.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default "forwards";
|
|
||||||
description
|
|
||||||
"The 'direction' parameter indicates how the entries in the
|
|
||||||
working result-set (i.e., after the 'sort-by' parameter
|
|
||||||
has been applied) should be traversed.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping offset-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf offset {
|
|
||||||
type uint32;
|
|
||||||
default 0;
|
|
||||||
description
|
|
||||||
"The 'offset' parameter indicates the number of entries
|
|
||||||
in the working result-set (i.e., after the 'direction'
|
|
||||||
parameter has been applied) that should be skipped over
|
|
||||||
when preparing the response.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping limit-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf limit {
|
|
||||||
type union {
|
|
||||||
type uint32 {
|
|
||||||
range "1..max";
|
|
||||||
}
|
|
||||||
type enumeration {
|
|
||||||
enum "unbounded" {
|
|
||||||
description
|
|
||||||
"Indicates that the number of entries that may be
|
|
||||||
returned is unbounded.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default "unbounded";
|
|
||||||
description
|
|
||||||
"The 'limit' parameter limits the number of entries returned
|
|
||||||
from the working result-set (i.e., after the 'offset'
|
|
||||||
parameter has been applied).
|
|
||||||
|
|
||||||
Any result-set that is limited includes, somewhere in its
|
|
||||||
encoding, the metadata value 'remaining' to indicate the
|
|
||||||
number entries not included in the result set.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouping sublist-limit-param-grouping {
|
|
||||||
description
|
|
||||||
"This grouping may be used by protocol-specific YANG modules
|
|
||||||
to define a protocol-specific query parameter.";
|
|
||||||
leaf sublist-limit {
|
|
||||||
type union {
|
|
||||||
type uint32 {
|
|
||||||
range "1..max";
|
|
||||||
}
|
|
||||||
type enumeration {
|
|
||||||
enum "unbounded" {
|
|
||||||
description
|
|
||||||
"Indicates that the number of entries that may be
|
|
||||||
returned is unbounded.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default "unbounded";
|
|
||||||
description
|
|
||||||
"The 'sublist-limit' parameter limits the number of entries
|
|
||||||
for descendent lists and leaf-lists.
|
|
||||||
|
|
||||||
Any result-set that is limited includes, somewhere in
|
|
||||||
its encoding, the metadata value 'remaining' to indicate
|
|
||||||
the number entries not included in the result set.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocol-accessible nodes
|
|
||||||
/* XXX system-capabilities brings in NACM that breaks clixon testing
|
|
||||||
augment // FIXME: ensure datastore == <operational>
|
|
||||||
"/sysc:system-capabilities/sysc:datastore-capabilities"
|
|
||||||
+ "/sysc:per-node-capabilities" {
|
|
||||||
description
|
|
||||||
"Defines some leafs that MAY be used by the server to
|
|
||||||
describe constraints imposed of the 'where' filters and
|
|
||||||
'sort-by' parameters used in list pagination queries.";
|
|
||||||
leaf constrained {
|
|
||||||
type empty;
|
|
||||||
description
|
|
||||||
"Indicates that 'where' filters and 'sort-by' parameters
|
|
||||||
on the targeted 'config false' list node are constrained.
|
|
||||||
If a list is not 'constrained', then full XPath 1.0
|
|
||||||
expressions may be used in 'where' filters and all node
|
|
||||||
identifiers are usable by 'sort-by'.";
|
|
||||||
}
|
|
||||||
leaf indexed {
|
|
||||||
type empty;
|
|
||||||
description
|
|
||||||
"Indicates that the targeted descendent node of a
|
|
||||||
'constrained' list (see the 'constrained' leaf) may be
|
|
||||||
used in 'where' filters and/or 'sort-by' parameters.
|
|
||||||
If a descendent node of a 'constrained' list is not
|
|
||||||
'indexed', then it MUST NOT be used in 'where' filters
|
|
||||||
or 'sort-by' parameters.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue