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
${DATASTORE_TOP}>
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=""
+
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.";
- }
- }
- */
- }