diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 6a57af01..ef021863 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -867,23 +867,32 @@ restconf_socket_init(const char *netns0, * @param[in] h Clicon handle * @param[in] xs socket config * @param[in] nsc Namespace context + * @param[out] description * @param[out] namespace * @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] port TCP Port * @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 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 *callhome, + int *periodic, + uint32_t *period, + uint8_t *attempts) { int retval = -1; cxobj *x; @@ -900,6 +909,9 @@ restconf_socket_extract(clicon_handle h, goto done; } *namespace = xml_body(x); + if ((x = xpath_first(xs, nsc, "description")) != NULL){ + *description = xml_body(x); + } if ((x = xpath_first(xs, nsc, "address")) == NULL){ clicon_err(OE_XML, EINVAL, "Mandatory address not given"); goto done; @@ -963,8 +975,37 @@ restconf_socket_extract(clicon_handle h, goto done; } } - if ((x = xpath_first(xs, nsc, "call-home")) != NULL) + if (xpath_first(xs, nsc, "call-home") != NULL){ *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 *callhome = 0; retval = 0; diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index c1f4eaae..9b20aec7 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -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_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_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_ */ diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index afa6f54c..ef117d2d 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -474,7 +474,7 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that SSL_free(rc->rc_ssl); 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; retval = 0; done: @@ -548,342 +548,6 @@ restconf_checkcert_file(cxobj *xrestconf, 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, "protocolmalformed-messageALPN: protocol not recognized: %s", 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", - "protocolmalformed-messageThe plain HTTP request was sent to HTTPS port") < 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", - "protocolmalformed-messagePeer certificate required") < 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 * @param[in] fd Socket (unix or ip) * @param[in] arg typecast clicon_handle @@ -939,11 +603,16 @@ restconf_native_terminate(clicon_handle h) clicon_debug(1, "%s", __FUNCTION__); if ((rh = restconf_native_handle_get(h)) != 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); close(rsock->rs_ss); } DELQ(rsock, rh->rh_sockets, restconf_socket *); + if (rsock->rs_description) + free(rsock->rs_description); if (rsock->rs_addrstr) free(rsock->rs_addrstr); if (rsock->rs_addrtype) @@ -1028,62 +697,6 @@ restconf_clixon_backend(clicon_handle h, 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 * @param[in] h Clicon handle * @param[in] xs XML config of single restconf socket @@ -1097,6 +710,7 @@ openssl_init_socket(clicon_handle h, cvec *nsc) { int retval = -1; + char *description = NULL; char *netns = NULL; char *address = NULL; char *addrtype = NULL; @@ -1106,10 +720,14 @@ openssl_init_socket(clicon_handle h, int ss = -1; restconf_native_handle *rh = NULL; 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__); /* 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; /* @@ -1124,6 +742,16 @@ openssl_init_socket(clicon_handle h, rsock->rs_h = h; rsock->rs_ssl = ssl; /* true/false */ 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 (!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); if (callhome){ - rsock->rs_ss = -1; /* Not applicable fro callhome */ - if (restconf_callhome_timer(0, rsock) < 0) + rsock->rs_ss = -1; /* Not applicable from callhome */ + if (restconf_callhome_timer(rsock, 0) < 0) goto done; } else { diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c index 7a5b2d00..9a22d334 100644 --- a/apps/restconf/restconf_native.c +++ b/apps/restconf/restconf_native.c @@ -167,8 +167,9 @@ restconf_stream_free(restconf_stream_data *sd) /*! Create restconf connection struct */ restconf_conn * -restconf_conn_new(clicon_handle h, - int s) +restconf_conn_new(clicon_handle h, + int s, + restconf_socket *rsock) { restconf_conn *rc; @@ -179,6 +180,7 @@ restconf_conn_new(clicon_handle h, memset(rc, 0, sizeof(restconf_conn)); rc->rc_h = h; rc->rc_s = s; + rc->rc_socket = rsock; return rc; } @@ -337,11 +339,12 @@ restconf_connection_sanity(clicon_handle h, * see also this function in restcont_api_openssl.c */ static int -native_buf_write(clicon_handle h, - char *buf, - size_t buflen, - int s, - SSL *ssl) +native_buf_write(clicon_handle h, + char *buf, + size_t buflen, + int s, + SSL *ssl, + restconf_socket *rsock) { int retval = -1; ssize_t len; @@ -376,7 +379,7 @@ native_buf_write(clicon_handle h, if (ssl){ SSL_free(ssl); } - if (restconf_connection_close(h, s) < 0) + if (restconf_connection_close(h, s, rsock) < 0) goto done; goto ok; /* Close socket and ssl */ } @@ -408,7 +411,7 @@ native_buf_write(clicon_handle h, break; case ECONNRESET: /* Connection reset by peer */ case EPIPE: /* Broken pipe */ - if (restconf_connection_close(h, s) < 0) + if (restconf_connection_close(h, s, rsock) < 0) goto done; goto ok; /* Close socket and ssl */ break; @@ -436,12 +439,13 @@ native_buf_write(clicon_handle h, * @param[in] body If given add message body using media * @see restconf_badrequest which can only be called in a request context */ -int +static int native_send_badrequest(clicon_handle h, int s, SSL *ssl, char *media, - char *body) + char *body, + restconf_socket *rsock) { int retval = -1; cbuf *cb = NULL; @@ -461,7 +465,7 @@ native_send_badrequest(clicon_handle h, cprintf(cb, "\r\n"); if (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; retval = 0; done: @@ -574,7 +578,7 @@ read_regular(restconf_conn *rc, switch(errno){ case ECONNRESET:/* Connection reset by peer */ 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; restconf_conn_free(rc); retval = 0; /* Close socket and ssl */ @@ -675,7 +679,7 @@ restconf_http1_process(restconf_conn *rc, goto done; } cprintf(cberr, "protocolmalformed-message%s", 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 ok; } @@ -686,7 +690,7 @@ restconf_http1_process(restconf_conn *rc, goto done; if (ret == 1){ 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; cvec_reset(sd->sd_outp_hdrs); cbuf_reset(sd->sd_outp_buf); @@ -714,7 +718,7 @@ restconf_http1_process(restconf_conn *rc, if (restconf_http1_path_root(h, rc) < 0) goto done; 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; cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */ cbuf_reset(sd->sd_outp_buf); @@ -723,7 +727,7 @@ restconf_http1_process(restconf_conn *rc, if (rc->rc_exit){ /* Server-initiated exit */ SSL_free(rc->rc_ssl); 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; restconf_conn_free(rc); retval = 0; @@ -869,6 +873,8 @@ restconf_native_handle_get(clicon_handle h) return NULL; } +/*---------------------------- Connect ---------------------------------*/ + /*! New data connection after accept, receive and reply on data socket * * @param[in] s Socket where message arrived. read from this. @@ -959,37 +965,491 @@ restconf_connection(int s, return retval; } /* restconf_connection */ -int restconf_callhome_timer(int fd, void *arg); // XXX FIX HEADERS INSTEAD - /*! Close Restconf native connection socket and unregister callback * For callhome also start reconnect timer */ int -restconf_connection_close(clicon_handle h, - int s) +restconf_connection_close(clicon_handle h, + int s, + restconf_socket *rsock) { int retval = -1; - restconf_native_handle *rh = NULL; - restconf_socket *rsock; + clicon_debug(1, "%s", __FUNCTION__); if (close(s) < 0){ clicon_err(OE_UNIX, errno, "close"); goto done; } 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){ - while ((rsock = rh->rh_sockets) != NULL) - if (rsock->rs_callhome && s == rsock->rs_ss){ - /* Maybe we should have a special wrapper function that only sets timer here? */ - if (restconf_callhome_timer(0, rsock) < 0) - goto done; - break; - } +/*! 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, "protocolmalformed-messageALPN: protocol not recognized: %s", 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", + "protocolmalformed-messageThe plain HTTP request was sent to HTTPS port", 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", + "protocolmalformed-messagePeer certificate required", 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; done: 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; +} diff --git a/apps/restconf/restconf_native.h b/apps/restconf/restconf_native.h index df019a71..34a6fa01 100644 --- a/apps/restconf/restconf_native.h +++ b/apps/restconf/restconf_native.h @@ -85,28 +85,6 @@ typedef struct { uint8_t *sd_settings2; /* Settings for upgrade to http/2 request */ } 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 * Two types: listen and callhome. * Listen: Uses socket rs_ss to listen for connections and accepts them, creates one @@ -117,6 +95,7 @@ typedef struct restconf_conn { typedef struct { qelem_t rs_qelem; /* List header */ clicon_handle rs_h; /* Clixon handle */ + char *rs_description; /* Description */ int rs_callhome; /* 0: listen, 1: callhome */ int rs_ss; /* Listen: Server socket, ready for accept * Callhome: connect socket (same as restconf_conn->rc_s) */ @@ -125,8 +104,42 @@ typedef struct { eg inet:ipv4-address or inet:ipv6-address */ char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */ uint16_t rs_port; /* Protocol port */ + int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */ + uint32_t rs_period; /* Period in s (if callhome & periodic) */ + uint8_t rs_max_attempts; /* max connect attempts (if callhome) */ + + uint64_t rs_start; /* First period start, next is start+periods*period */ + uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods. + Set in restconf_callhome_timer*/ + uint8_t rs_attempts; /* Dynamic connect attempts in this round (if callhome) + * Set in restconf_callhome_cb + */ } restconf_socket; +/* Restconf connection handle + * 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 * 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_find(restconf_conn *rc, int32_t id); 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 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_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); 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_ */ diff --git a/test/test_restconf_callhome.sh b/test/test_restconf_callhome.sh index 34e2e29f..f069cf08 100755 --- a/test/test_restconf_callhome.sh +++ b/test/test_restconf_callhome.sh @@ -2,6 +2,13 @@ # test of Restconf callhome # See RFC 8071 NETCONF Call Home and RESTCONF Call Home # 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) 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 clispec=$dir/spec.cli +# HTTP request client->server +frequest=$dir/frequest +# HTTP expected reply server->client + certdir=$dir/certs cakey=$certdir/ca_key.pem cacert=$certdir/ca_cert.pem @@ -60,17 +71,39 @@ cat < $cfg $cacert 1 + callhome persistent default + + 3 +
127.0.0.1
4336 true
+ callhome periodic + default + + + + 10 + + + + 3 + + +
127.0.0.1
+ 8336 + true +
+ + listen default
0.0.0.0
443 @@ -219,14 +252,16 @@ cat < $dir/startup_db EOF -# Callhome request from client -cat < $dir/data +# Callhome request from client->server +cat < $frequest GET /restconf/data/clixon-example:table HTTP/$HVERCH Host: localhost Accept: application/yang-data+xml EOF +expectreply="xfoo
" + new "test params: -f $cfg" # Bring your own backend if [ $BE -ne 0 ]; then @@ -249,7 +284,7 @@ if [ $RC -ne 0 ]; then stop_restconf_pre new "start restconf daemon" - start_restconf -f $cfg + start_restconf -f $cfg -D 1 -l s fi new "wait restconf" @@ -258,9 +293,39 @@ wait_restconf 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" -new "Send GET via callhome client" -echo "${clixon_restconf_callhome_client} -D $DBG -f $dir/data -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert" -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" +# tests for (one) well-formed HTTP status and three replies and that the time is reasonable +t0=$(date +"%s") +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 if [ $RC -ne 0 ]; then diff --git a/util/clixon_restconf_callhome_client.c b/util/clixon_restconf_callhome_client.c index 029cdc22..1f8f3288 100644 --- a/util/clixon_restconf_callhome_client.c +++ b/util/clixon_restconf_callhome_client.c @@ -41,6 +41,8 @@ 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. + When accepting a connection, send HTTP data from input or -f tehn wait for reply + Reply is matched with -e or printed on stdout */ #ifdef HAVE_CONFIG_H @@ -66,7 +68,7 @@ /* clixon */ #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 @@ -143,18 +145,63 @@ callhome_bind(struct sockaddr *sa, 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 -tls_input_cb(int s, - void *arg) +read_data_file(FILE *fe, + 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; - SSL *ssl; - char buf[1024]; - int n; - + SSL *ssl; + char buf[1024]; + 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__); ssl = sd->sd_ssl; /* get reply & decrypt */ @@ -167,10 +214,14 @@ tls_input_cb(int s, goto done; } 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_free(ssl); - clixon_event_unreg_fd(s, tls_input_cb); + clixon_event_unreg_fd(s, tls_server_reply_cb); close(s); free(sd); if (_connects == 1) @@ -179,6 +230,8 @@ tls_input_cb(int s, _connects--; retval = 0; done: + if (expbuf) + free(expbuf); clicon_debug(1, "%s %d", __FUNCTION__, retval); return retval; } @@ -260,30 +313,15 @@ tls_write_file(FILE *fp, { int retval = -1; char *buf = NULL; - int buflen = 1024; /* start size */ - char ch; + size_t len = 0; int ret; int sslerr; - size_t len = 0; - if ((buf = malloc(buflen)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); + if (read_data_file(fp, &buf, &len) < 0) 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){ 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; done: @@ -292,11 +330,11 @@ tls_write_file(FILE *fp, return retval; } -/*! Callhome-server accept socket +/*! Callhome-server accept socket */ static int -tls_accept_cb(int ss, - void *arg) +tls_server_accept_cb(int ss, + void *arg) { int retval = -1; 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) goto done; /* 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; retval = 0; done: @@ -400,7 +438,8 @@ usage(char *argv0) "where options are\n" "\t-h \t\tHelp\n" "\t-D \tDebug\n" - "\t-f \tHHTP input file (overrides stdin)\n" + "\t-f \tHTTP input file (overrides stdin)\n" + "\t-e \tFile including HTTP output (otherwise echo to stdout)\n" "\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n" "\t-a \tIP address (eg 1.2.3.4) - mandatory\n" "\t-p \tPort (default %d)\n" @@ -528,14 +567,14 @@ main(int argc, ta->ta_ctx = ctx; ta->ta_ss = ss; 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; if (clixon_event_loop(h) < 0) goto done; retval = 0; done: if (ss != -1) - clixon_event_unreg_fd(ss, tls_accept_cb); + clixon_event_unreg_fd(ss, tls_server_accept_cb); if (ta) free(ta); if (fp) diff --git a/yang/clixon/clixon-restconf@2022-08-01.yang b/yang/clixon/clixon-restconf@2022-08-01.yang index 6e8c112d..ddcc20d6 100644 --- a/yang/clixon/clixon-restconf@2022-08-01.yang +++ b/yang/clixon/clixon-restconf@2022-08-01.yang @@ -258,6 +258,9 @@ module clixon-restconf { On platforms where namespaces are not suppported, 'default' Default value can be changed by RESTCONF_NETNS_DEFAULT"; } + leaf description{ + type string; + } leaf address { type inet:ip-address; description "IP address to bind to"; @@ -299,28 +302,28 @@ module clixon-restconf { presence "Indicates periodic connects"; leaf period { - type uint16; - units "minutes"; + type uint32; /* XXX: note uit16 in std */ + units "seconds"; /* XXX: note minutes in standard */ default "60"; description "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)."; + } + } } } } diff --git a/yang/mandatory/ietf-list-pagination-nc@2021-10-25.yang b/yang/mandatory/ietf-list-pagination-nc@2021-10-25.yang deleted file mode 100644 index 79e2a5dd..00000000 --- a/yang/mandatory/ietf-list-pagination-nc@2021-10-25.yang +++ /dev/null @@ -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: - WG List: "; - - description - "This module augments the , , and - '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; - } - } diff --git a/yang/mandatory/ietf-list-pagination@2021-10-25.yang b/yang/mandatory/ietf-list-pagination@2021-10-25.yang deleted file mode 100644 index 5fb62c5d..00000000 --- a/yang/mandatory/ietf-list-pagination@2021-10-25.yang +++ /dev/null @@ -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: - WG List: "; - - 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 == - "/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."; - } - } - */ - }