Restconf callhome: idle-timeout for periodic callhome

This commit is contained in:
Olof hagsand 2022-08-22 11:27:02 +02:00
parent 9a5504eed0
commit ba9b313295
10 changed files with 426 additions and 268 deletions

View file

@ -42,7 +42,13 @@ Expected: September 2022
### New features ### New features
* RESTCONF call home according to RFC 8071 * RESTCONF call home according to RFC 8071
* Experimental, work-in-progress * clixon-restconf.yang extended with callhome inspired by ietf-restconf-server.yang
* See e.g., draft-ietf-netconf-restconf-client-server-26.txt
* The `<socket>` list has been extended with a `call-home` presence container including:
* reconnect-strategy/max-attempts
* connection-type: either persistent or periodic
* idle-timeout for periodic call-homes.
* An example util client is `clixon_restconf_callhome_client.c` used in test cases
### Corrected Bugs ### Corrected Bugs

View file

@ -863,157 +863,3 @@ restconf_socket_init(const char *netns0,
return retval; return retval;
} }
/*! Extract socket info from backend config
* @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
* 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 *periodic,
uint32_t *period,
uint8_t *attempts)
{
int retval = -1;
cxobj *x;
char *str = NULL;
char *reason = NULL;
int ret;
char *body;
cg_var *cv = NULL;
yang_stmt *y;
yang_stmt *ysub = NULL;
if ((x = xpath_first(xs, nsc, "namespace")) == NULL){
clicon_err(OE_XML, EINVAL, "Mandatory namespace not given");
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;
}
/* address is a union type and needs a special investigation to see which type (ipv4 or ipv6)
* the address is
*/
body = xml_body(x);
y = xml_spec(x);
if ((cv = cv_dup(yang_cv_get(y))) == NULL){
clicon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
if ((ret = cv_parse1(body, cv, &reason)) < 0){
clicon_err(OE_XML, errno, "cv_parse1");
goto done;
}
if (ret == 0){
clicon_err(OE_XML, EFAULT, "%s", reason);
goto done;
}
if ((ret = ys_cv_validate(h, cv, y, &ysub, &reason)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, EFAULT, "Validation os address: %s", reason);
goto done;
}
if (ysub == NULL){
clicon_err(OE_XML, EFAULT, "No address union type");
goto done;
}
*address = body;
/* This is YANG type name of ip-address:
* typedef ip-address {
* type union {
* type inet:ipv4-address; <---
* type inet:ipv6-address; <---
* }
*/
*addrtype = yang_argument_get(ysub);
if ((x = xpath_first(xs, nsc, "port")) != NULL &&
(str = xml_body(x)) != NULL){
if ((ret = parse_uint16(str, port, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint16");
goto done;
}
if (ret == 0){
clicon_err(OE_XML, EINVAL, "Unrecognized value of port: %s", str);
goto done;
}
}
if ((x = xpath_first(xs, nsc, "ssl")) != NULL &&
(str = xml_body(x)) != NULL){
/* XXX use parse_bool but it is legacy static */
if (strcmp(str, "false") == 0)
*ssl = 0;
else if (strcmp(str, "true") == 0)
*ssl = 1;
else {
clicon_err(OE_XML, EINVAL, "Unrecognized value of ssl: %s", str);
goto done;
}
}
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;
done:
if (cv)
cv_free(cv);
if (reason)
free(reason);
return retval;
}

View file

@ -97,7 +97,6 @@ int restconf_drop_privileges(clicon_handle h);
int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out); int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out);
int restconf_config_init(clicon_handle h, cxobj *xrestconf); int restconf_config_init(clicon_handle h, cxobj *xrestconf);
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss); int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **description, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome, int *periodic, uint32_t *period, uint8_t *attempts);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -171,7 +171,6 @@
static int session_id_context = 1; static int session_id_context = 1;
/*! Set restconf native handle /*! Set restconf native handle
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] rh Restconf native handle (malloced pointer) * @param[in] rh Restconf native handle (malloced pointer)
@ -604,7 +603,9 @@ restconf_native_terminate(clicon_handle h)
if ((rh = restconf_native_handle_get(h)) != NULL){ if ((rh = restconf_native_handle_get(h)) != NULL){
while ((rsock = rh->rh_sockets) != NULL){ while ((rsock = rh->rh_sockets) != NULL){
if (rsock->rs_callhome){ if (rsock->rs_callhome){
clixon_event_unreg_timeout(restconf_callhome_cb, rsock); restconf_callhome_timer_unreg(rsock);
if (rsock->rs_periodic)
restconf_idle_timer_unreg(rsock);
} }
else if (rsock->rs_ss != -1){ else if (rsock->rs_ss != -1){
clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client); clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client);
@ -710,26 +711,16 @@ openssl_init_socket(clicon_handle h,
cvec *nsc) cvec *nsc)
{ {
int retval = -1; int retval = -1;
char *description = NULL;
char *netns = NULL; char *netns = NULL;
char *address = NULL; char *address = NULL;
char *addrtype = NULL; char *addrtype = NULL;
int callhome = 0;
uint16_t ssl = 0;
uint16_t port = 0; uint16_t port = 0;
int ss = -1; int ss = -1;
restconf_native_handle *rh = NULL; restconf_native_handle *rh = NULL;
restconf_socket *rsock = NULL; /* openssl per socket struct */ restconf_socket *rsock = NULL; /* openssl per socket struct */
int periodic = 0;
uint32_t period = 0;
uint8_t max_attempts = 0;
struct timeval now; struct timeval now;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
if (restconf_socket_extract(h, xs, nsc, &description, &netns, &address, &addrtype, &port, &ssl, &callhome, &periodic, &period, &max_attempts) < 0)
goto done;
/* /*
* Create per-socket openssl handle * Create per-socket openssl handle
* See restconf_native_terminate for freeing * See restconf_native_terminate for freeing
@ -740,20 +731,13 @@ openssl_init_socket(clicon_handle h,
} }
memset(rsock, 0, sizeof *rsock); memset(rsock, 0, sizeof *rsock);
rsock->rs_h = 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); gettimeofday(&now, NULL);
rsock->rs_start = now.tv_sec; rsock->rs_start = now.tv_sec;
if (description && /* Extract socket parameters from single socket config: ns, addr, port, ssl */
(rsock->rs_description = strdup(description)) == NULL){ if (restconf_socket_extract(h, xs, nsc, rsock, &netns, &address, &addrtype, &port) < 0)
clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} if (rsock->rs_callhome){
if (callhome){ if (!rsock->rs_ssl){
if (!ssl){
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL"); clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
goto done; goto done;
} }
@ -786,7 +770,7 @@ openssl_init_socket(clicon_handle h,
rsock->rs_port = port; rsock->rs_port = port;
INSQ(rsock, rh->rh_sockets); INSQ(rsock, rh->rh_sockets);
if (callhome){ if (rsock->rs_callhome){
rsock->rs_ss = -1; /* Not applicable from callhome */ rsock->rs_ss = -1; /* Not applicable from callhome */
if (restconf_callhome_timer(rsock, 0) < 0) if (restconf_callhome_timer(rsock, 0) < 0)
goto done; goto done;

View file

@ -309,8 +309,8 @@ restconf_connection_sanity(clicon_handle h,
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
cprintf(cberr, "HTTP cert verification failed: %s", cprintf(cberr, "HTTP cert verification failed: %s[%ld]",
X509_verify_cert_error_string(code)); X509_verify_cert_error_string(code), code);
if (netconf_invalid_value_xml(&xerr, "protocol", cbuf_get(cberr)) < 0) if (netconf_invalid_value_xml(&xerr, "protocol", cbuf_get(cberr)) < 0)
goto done; goto done;
if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){ if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
@ -975,16 +975,20 @@ restconf_connection_close(clicon_handle h,
{ {
int retval = -1; int retval = -1;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
if (close(s) < 0){ if (close(s) < 0){
clicon_err(OE_UNIX, errno, "close"); clicon_err(OE_UNIX, errno, "close");
goto done; goto done;
} }
clixon_event_unreg_fd(s, restconf_connection); clixon_event_unreg_fd(s, restconf_connection);
/* re-set timer */ /* re-set timer */
if (rsock->rs_callhome && if (rsock->rs_callhome){
restconf_callhome_timer(rsock, 1) < 0) if (rsock->rs_periodic &&
restconf_idle_timer_unreg(rsock) < 0)
goto done; goto done;
if (restconf_callhome_timer(rsock, 1) < 0)
goto done;
}
retval = 0; retval = 0;
done: done:
clicon_debug(1, "%s %d", __FUNCTION__, retval); clicon_debug(1, "%s %d", __FUNCTION__, retval);
@ -1328,6 +1332,81 @@ restconf_ssl_accept_client(clicon_handle h,
return retval; return retval;
} /* restconf_ssl_accept_client */ } /* restconf_ssl_accept_client */
/*! idle timeout timer callback
*/
static int
restconf_idle_cb(int fd,
void *arg)
{
int retval = -1;
restconf_socket *rsock;
if ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
/* sanity */
if (rsock->rs_callhome && rsock->rs_periodic && rsock->rs_idle_timeout && rsock->rs_ss > 0){
if (restconf_connection_close(rsock->rs_h, rsock->rs_ss, rsock) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
int
restconf_idle_timer_unreg(restconf_socket *rsock)
{
return clixon_event_unreg_timeout(restconf_idle_cb, rsock);
}
/*! Set callhome periodic idle-timeout
* 1) If callhome and periodic, set timer for t0+idle-timeout(ti)
* 2) Timestamp any data passing on the socket(td)
* 3) At timeout (ti) check if ti = td+idle-timeout (for first timeout same as t0=td),
* if so close socket
* 4) Else, reset timer to ti = td + idle-timeout. Goto (2)
* socket.
* XXX: now just timeout dont keep track of data (td)
* @see restconf_idle_timer_unreg
*/
int
restconf_idle_timer(restconf_socket *rsock)
{
int retval = -1;
struct timeval now;
struct timeval t;
struct timeval t1 = {0, 0};
cbuf *cb = NULL;
if (rsock == NULL || !rsock->rs_callhome || !rsock->rs_periodic || rsock->rs_idle_timeout==0){
clicon_err(OE_YANG, EINVAL, "rsock is NULL or not periodic callhome");
goto done;
}
clicon_debug(1, "%s %s register", __FUNCTION__, rsock->rs_description);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "restconf idle timer %s", rsock->rs_description);
gettimeofday(&now, NULL);
t1.tv_sec = rsock->rs_idle_timeout;
timeradd(&now, &t1, &t);
clicon_debug(1, "%s now:%lu timeout:%lu.%lu", __FUNCTION__, now.tv_sec, t.tv_sec, t.tv_usec);
if (clixon_event_reg_timeout(t,
restconf_idle_cb,
rsock,
cbuf_get(cb)) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Callhome timer callback /*! Callhome timer callback
* *
* @param[in] fd No-op * @param[in] fd No-op
@ -1336,7 +1415,7 @@ restconf_ssl_accept_client(clicon_handle h,
* the reason is that this function may continue with SSL accept handling whereas the wrapper always * the reason is that this function may continue with SSL accept handling whereas the wrapper always
* returns directly. * returns directly.
*/ */
int static int
restconf_callhome_cb(int fd, restconf_callhome_cb(int fd,
void *arg) void *arg)
{ {
@ -1348,14 +1427,12 @@ restconf_callhome_cb(int fd,
size_t sa_len; size_t sa_len;
int s; int s;
if ((rsock = (restconf_socket *)arg) == NULL){ rsock = (restconf_socket *)arg;
if (rsock == NULL || !rsock->rs_callhome){
clicon_err(OE_YANG, EINVAL, "rsock is NULL"); clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done; goto done;
} }
if (rsock->rs_description)
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
else
clicon_debug(1, "%s", __FUNCTION__);
h = rsock->rs_h; h = rsock->rs_h;
/* Already computed in restconf_socket_init, could be saved in rsock? */ /* Already computed in restconf_socket_init, could be saved in rsock? */
if (clixon_inet2sin(rsock->rs_addrtype, rsock->rs_addrstr, rsock->rs_port, sa, &sa_len) < 0) if (clixon_inet2sin(rsock->rs_addrtype, rsock->rs_addrstr, rsock->rs_port, sa, &sa_len) < 0)
@ -1379,6 +1456,9 @@ restconf_callhome_cb(int fd,
rsock->rs_attempts = 0; rsock->rs_attempts = 0;
if (restconf_ssl_accept_client(h, s, rsock) < 0) if (restconf_ssl_accept_client(h, s, rsock) < 0)
goto done; goto done;
if (rsock->rs_periodic && rsock->rs_idle_timeout &&
restconf_idle_timer(rsock) < 0)
goto done;
} }
clicon_debug(1, "%s connect done", __FUNCTION__); clicon_debug(1, "%s connect done", __FUNCTION__);
retval = 0; retval = 0;
@ -1386,16 +1466,23 @@ restconf_callhome_cb(int fd,
return retval; return retval;
} }
int
restconf_callhome_timer_unreg(restconf_socket *rsock)
{
return clixon_event_unreg_timeout(restconf_callhome_cb, rsock);
}
/*! Set callhome timer, which tries to connect to callhome client /*! Set callhome timer, which tries to connect to callhome client
* *
* Implement callhome re-connect strategies in ietf-restconf-server.yang * Implement callhome re-connect strategies in ietf-restconf-server.yang
* NYI: start-with, anchor-time, idle-timeout * NYI: start-with, anchor-time, idle-timeout
* @param[in] rsock restconf_socket * @param[in] rsock restconf_socket
* @param[in] new Start a new period (if periodic) * @param[in] new if periodic: 1: Force a new period
* @see restconf_callhome_timer_unreg
*/ */
int int
restconf_callhome_timer(restconf_socket *rsock, restconf_callhome_timer(restconf_socket *rsock,
int new) int status)
{ {
int retval = -1; int retval = -1;
struct timeval now; struct timeval now;
@ -1403,15 +1490,16 @@ restconf_callhome_timer(restconf_socket *rsock,
struct timeval t1 = {0, 0}; struct timeval t1 = {0, 0};
cbuf *cb = NULL; cbuf *cb = NULL;
if (rsock->rs_description) if (rsock == NULL || !rsock->rs_callhome){
clicon_err(OE_YANG, EINVAL, "rsock is NULL or not callhome");
goto done;
}
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description); clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
else
clicon_debug(1, "%s", __FUNCTION__);
if (!rsock->rs_callhome) if (!rsock->rs_callhome)
goto ok; /* shouldnt happen */ goto ok; /* shouldnt happen */
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
if (rsock->rs_periodic){ if (rsock->rs_periodic){
if (new || rsock->rs_attempts >= rsock->rs_max_attempts){ if ((status == 1) || rsock->rs_attempts >= rsock->rs_max_attempts){
rsock->rs_period_nr++; rsock->rs_period_nr++;
rsock->rs_attempts = 0; rsock->rs_attempts = 0;
t1.tv_sec = rsock->rs_start + rsock->rs_period_nr*rsock->rs_period; t1.tv_sec = rsock->rs_start + rsock->rs_period_nr*rsock->rs_period;
@ -1432,10 +1520,7 @@ restconf_callhome_timer(restconf_socket *rsock,
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
if (rsock->rs_description)
cprintf(cb, "restconf callhome timer %s", rsock->rs_description); cprintf(cb, "restconf callhome timer %s", rsock->rs_description);
else
cprintf(cb, "restconf callhome timer");
if (rsock->rs_description) if (rsock->rs_description)
clicon_debug(1, "%s registering %s: %lu", __FUNCTION__, rsock->rs_description, t.tv_sec); clicon_debug(1, "%s registering %s: %lu", __FUNCTION__, rsock->rs_description, t.tv_sec);
else else
@ -1453,3 +1538,159 @@ restconf_callhome_timer(restconf_socket *rsock,
cbuf_free(cb); cbuf_free(cb);
return retval; return retval;
} }
/*! Extract socket info from backend config
* @param[in] h Clicon handle
* @param[in] xs socket config
* @param[in] nsc Namespace context
* @param[out] rsock restconf socket data, filled in with many fields
* @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
*/
int
restconf_socket_extract(clicon_handle h,
cxobj *xs,
cvec *nsc,
restconf_socket *rsock,
char **namespace,
char **address,
char **addrtype,
uint16_t *port)
{
int retval = -1;
cxobj *x;
char *str = NULL;
char *reason = NULL;
int ret;
char *body;
cg_var *cv = NULL;
yang_stmt *y;
yang_stmt *ysub = NULL;
if ((x = xpath_first(xs, nsc, "namespace")) == NULL){
clicon_err(OE_XML, EINVAL, "Mandatory namespace not given");
goto done;
}
*namespace = xml_body(x);
if ((x = xpath_first(xs, nsc, "description")) != NULL){
if ((rsock->rs_description = strdup(xml_body(x))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if ((x = xpath_first(xs, nsc, "address")) == NULL){
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
goto done;
}
/* address is a union type and needs a special investigation to see which type (ipv4 or ipv6)
* the address is
*/
body = xml_body(x);
y = xml_spec(x);
if ((cv = cv_dup(yang_cv_get(y))) == NULL){
clicon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
if ((ret = cv_parse1(body, cv, &reason)) < 0){
clicon_err(OE_XML, errno, "cv_parse1");
goto done;
}
if (ret == 0){
clicon_err(OE_XML, EFAULT, "%s", reason);
goto done;
}
if ((ret = ys_cv_validate(h, cv, y, &ysub, &reason)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, EFAULT, "Validation os address: %s", reason);
goto done;
}
if (ysub == NULL){
clicon_err(OE_XML, EFAULT, "No address union type");
goto done;
}
*address = body;
/* This is YANG type name of ip-address:
* typedef ip-address {
* type union {
* type inet:ipv4-address; <---
* type inet:ipv6-address; <---
* }
*/
*addrtype = yang_argument_get(ysub);
if ((x = xpath_first(xs, nsc, "port")) != NULL &&
(str = xml_body(x)) != NULL){
if ((ret = parse_uint16(str, port, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint16");
goto done;
}
if (ret == 0){
clicon_err(OE_XML, EINVAL, "Unrecognized value of port: %s", str);
goto done;
}
}
if ((x = xpath_first(xs, nsc, "ssl")) != NULL &&
(str = xml_body(x)) != NULL){
/* XXX use parse_bool but it is legacy static */
if (strcmp(str, "false") == 0)
rsock->rs_ssl = 0;
else if (strcmp(str, "true") == 0)
rsock->rs_ssl = 1;
else {
clicon_err(OE_XML, EINVAL, "Unrecognized value of ssl: %s", str);
goto done;
}
}
if (xpath_first(xs, nsc, "call-home") != NULL){
rsock->rs_callhome = 1;
if (xpath_first(xs, nsc, "call-home/connection-type/persistent") != NULL){
rsock->rs_periodic = 0;
}
else if (xpath_first(xs, nsc, "call-home/connection-type/periodic") != NULL){
rsock->rs_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, &rsock->rs_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/connection-type/periodic/idle-timeout")) != NULL &&
(str = xml_body(x)) != NULL){
if ((ret = parse_uint16(str, &rsock->rs_idle_timeout, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint16");
goto done;
}
if (ret == 0){
clicon_err(OE_XML, EINVAL, "Unrecognized value of idle-timeout: %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, &rsock->rs_max_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;
}
}
}
retval = 0;
done:
if (cv)
cv_free(cv);
if (reason)
free(reason);
return retval;
}

View file

@ -107,7 +107,7 @@ typedef struct {
int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */ int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */
uint32_t rs_period; /* Period in s (if callhome & periodic) */ uint32_t rs_period; /* Period in s (if callhome & periodic) */
uint8_t rs_max_attempts; /* max connect attempts (if callhome) */ uint8_t rs_max_attempts; /* max connect attempts (if callhome) */
uint16_t rs_idle_timeout; /* Max underlying TCP session remains idle (if callhome and periodic) */
uint64_t rs_start; /* First period start, next is start+periods*period */ uint64_t rs_start; /* First period start, next is start+periods*period */
uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods. uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods.
Set in restconf_callhome_timer*/ Set in restconf_callhome_timer*/
@ -165,9 +165,12 @@ restconf_native_handle *restconf_native_handle_get(clicon_handle h);
int restconf_connection(int s, void *arg); int restconf_connection(int s, void *arg);
int restconf_connection_close(clicon_handle h, int s, restconf_socket *rsock); int restconf_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_ssl_accept_client(clicon_handle h, int s, restconf_socket *rsock);
int restconf_callhome_cb(int fd, void *arg); int restconf_idle_timer_unreg(restconf_socket *rsock);
int restconf_idle_timer(restconf_socket *rsock);
int restconf_callhome_timer(restconf_socket *rsock, int new); int restconf_callhome_timer_unreg(restconf_socket *rsock);
int restconf_callhome_timer(restconf_socket *rsock, int status);
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,
char **namespace, char **address, char **addrtype, uint16_t *port);
#endif /* _RESTCONF_NATIVE_H_ */ #endif /* _RESTCONF_NATIVE_H_ */

View file

@ -286,6 +286,8 @@ clixon_event_reg_timeout(struct timeval t,
* different from clixon_event_unreg_fd. * different from clixon_event_unreg_fd.
* @param[in] fn Function to call at time t * @param[in] fn Function to call at time t
* @param[in] arg Argument to function fn * @param[in] arg Argument to function fn
* @retval 0 OK, timeout unregistered
* @retval -1 OK, but timeout not found
* @see clixon_event_reg_timeout * @see clixon_event_reg_timeout
* @see clixon_event_unreg_fd * @see clixon_event_unreg_fd
*/ */

View file

@ -78,7 +78,7 @@ cat <<EOF > $cfg
<persistent/> <persistent/>
</connection-type> </connection-type>
<reconnect-strategy> <reconnect-strategy>
<max-attempts>3</max-attempts> <max-attempts>1</max-attempts>
</reconnect-strategy> </reconnect-strategy>
</call-home> </call-home>
<address>127.0.0.1</address> <address>127.0.0.1</address>
@ -92,10 +92,11 @@ cat <<EOF > $cfg
<connection-type> <connection-type>
<periodic> <periodic>
<period>10</period> <period>10</period>
<idle-timeout>5</idle-timeout>
</periodic> </periodic>
</connection-type> </connection-type>
<reconnect-strategy> <reconnect-strategy>
<max-attempts>3</max-attempts> <max-attempts>1</max-attempts>
</reconnect-strategy> </reconnect-strategy>
</call-home> </call-home>
<address>127.0.0.1</address> <address>127.0.0.1</address>
@ -290,13 +291,13 @@ fi
new "wait restconf" new "wait restconf"
wait_restconf wait_restconf
new "restconf Add init data" new "restconf Add init data"
expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201" expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201"
# tests for (one) well-formed HTTP status and three replies and that the time is reasonable
t0=$(date +"%s") t0=$(date +"%s")
new "Send GET via callhome persistence client port 4336" 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" 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" "Close 1 local" "OK 2" "Close 2 local" "OK 3" "Close 3 local" $expectreply --not-- "OK 4" "Close 4"
t1=$(date +"%s") t1=$(date +"%s")
let t=t1-t0 let t=t1-t0
@ -307,18 +308,18 @@ fi
t0=$(date +"%s") t0=$(date +"%s")
new "Send GET via callhome client periodic port 8336" 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 expectpart "$(${clixon_restconf_callhome_client} -t 30 -p 8336 -D $DBG -f $frequest -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert -n 2)" 0 "HTTP/$HVERCH 200" "OK 1" "Close 1" "OK 2" "Close 2" $expectreply --not-- "OK 3" "Close 3"
t1=$(date +"%s") t1=$(date +"%s")
let t=t1-t0 let t=t1-t0
new "Check periodic interval ($t) is larger than 20 and less than 31" new "Check periodic interval ($t) is in interval [10-21]"
if [ $t -lt 20 -o $t -ge 31 ]; then if [ $t -lt 10 -o $t -ge 21 ]; then
err1 "timer in interval [20-31] but is: $t" err1 "timer in interval [10-21] but is: $t"
fi fi
t0=$(date +"%s") t0=$(date +"%s")
new "Send GET via callhome persistence again" 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" 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" "Close 1 local" "OK 2" "Close 2 local" "OK 3" "Close 3 local" $expectreply --not-- "OK 4" "Close 4"
t1=$(date +"%s") t1=$(date +"%s")
let t=t1-t0 let t=t1-t0
@ -327,7 +328,17 @@ if [ $t -lt 2 -o $t -ge 4 ]; then
err1 "timer in interval [2,4] but is: $t" err1 "timer in interval [2,4] but is: $t"
fi fi
# Kill old t0=$(date +"%s")
new "Send GET and try idle-timeout, clients keeps socket open"
expectpart "$(${clixon_restconf_callhome_client} -o -t 30 -p 8336 -D $DBG -f $frequest -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert -n 1)" 0 "HTTP/$HVERCH 200" "OK 1" $expectreply "Close 1 remote" --not-- "OK 2" "Close 2"
t1=$(date +"%s")
let t=t1-t0
new "Check periodic interval ($t) is in interval [5-15]"
if [ $t -lt 5 -o $t -ge 15 ]; then
err1 "timer in interval [5-15] but is: $t"
fi
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -68,7 +68,7 @@
/* clixon */ /* clixon */
#include "clixon/clixon.h" #include "clixon/clixon.h"
#define UTIL_TLS_OPTS "hD:f:e:F:a:p:c:C:k:n:" #define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:n:ot:"
#define RESTCONF_CH_TLS 4336 #define RESTCONF_CH_TLS 4336
@ -88,6 +88,12 @@ typedef struct {
/* Expected connects */ /* Expected connects */
static int _connects = 1; static int _connects = 1;
/* Keep socket open, dont close immediately after reply */
static int _keep_open = 0;
/* Timeout in seconds after each accept, if fired just exit */
static int _timeout_s = 60;
/*! Create and bind stream socket /*! Create and bind stream socket
* @param[in] sa Socketaddress * @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len * @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
@ -186,6 +192,9 @@ read_data_file(FILE *fe,
} }
/*! Client data socket, receive reply from server /*! Client data socket, receive reply from server
* Print info on stdout
* If keep_open = 0, then close socket directly after 1st reply (client close)
* If keep_open = 1, then keep socket open (server close)
*/ */
static int static int
tls_server_reply_cb(int s, tls_server_reply_cb(int s,
@ -199,10 +208,12 @@ tls_server_reply_cb(int s,
char *expbuf = NULL; char *expbuf = NULL;
struct timeval tn; struct timeval tn;
struct timeval td; struct timeval td;
static int seq = 1; static int seq = 0;
static struct timeval t0 = {0,}; static struct timeval t0 = {0,};
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if (seq == 0)
gettimeofday(&t0, NULL);
ssl = sd->sd_ssl; ssl = sd->sd_ssl;
/* get reply & decrypt */ /* get reply & decrypt */
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){ if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
@ -210,24 +221,42 @@ tls_server_reply_cb(int s,
goto done; goto done;
} }
if (n == 0){ if (n == 0){
clicon_debug(1, "%s closed", __FUNCTION__);
goto done;
}
buf[n] = 0;
if (seq == 1)
gettimeofday(&t0, NULL);
gettimeofday(&tn, NULL);
timersub(&tn, &t0, &td);
fprintf(stdout, "OK %d %lu\n%s\n", seq++, td.tv_sec, buf);
SSL_shutdown(ssl); SSL_shutdown(ssl);
SSL_free(ssl); SSL_free(ssl);
clixon_event_unreg_fd(s, tls_server_reply_cb); clixon_event_unreg_fd(s, tls_server_reply_cb);
fprintf(stdout, "Close %d remote %lu\n", seq, td.tv_sec);
close(s); close(s);
free(sd); free(sd);
if (_connects == 1) if (_connects == 0)
;
else if (_connects == 1)
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */ clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
else else
_connects--; _connects--;
goto ok;
}
seq++;
buf[n] = 0;
gettimeofday(&tn, NULL);
timersub(&tn, &t0, &td);
fprintf(stdout, "OK %d %lu\n%s\n", seq, td.tv_sec, buf);
if (!_keep_open){
SSL_shutdown(ssl);
SSL_free(ssl);
clixon_event_unreg_fd(s, tls_server_reply_cb);
fprintf(stdout, "Close %d local %lu\n", seq, td.tv_sec);
close(s);
if (_connects == 0)
;
else if (_connects == 1){
if (!_keep_open)
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
}
else
_connects--;
free(sd);
}
ok:
retval = 0; retval = 0;
done: done:
if (expbuf) if (expbuf)
@ -330,6 +359,42 @@ tls_write_file(FILE *fp,
return retval; return retval;
} }
static int
tls_timeout_cb(int fd,
void *arg)
{
tls_accept_handle *ta = (tls_accept_handle *)arg;
fprintf(stderr, "Timeout on socket:%d\n", ta->ta_ss);
exit(200);
}
/*! Timeout in seconds after each accept, if fired just exit
*/
static int
tls_client_timeout(void *arg)
{
int retval = -1;
struct timeval now;
struct timeval t;
struct timeval t1 = {0, 0};
/* Unregister existing timeout */
clixon_event_unreg_timeout(tls_timeout_cb, arg);
/* Set timeout */
gettimeofday(&now, NULL);
t1.tv_sec = _timeout_s;
timeradd(&now, &t1, &t);
if (clixon_event_reg_timeout(t,
tls_timeout_cb,
arg,
"tls client timeout") < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Callhome-server accept socket /*! Callhome-server accept socket
*/ */
static int static int
@ -367,6 +432,10 @@ tls_server_accept_cb(int ss,
/* register callback for reply */ /* register callback for reply */
if (clixon_event_reg_fd(s, tls_server_reply_cb, sd, "tls server reply") < 0) if (clixon_event_reg_fd(s, tls_server_reply_cb, sd, "tls server reply") < 0)
goto done; goto done;
/* Unregister old + register new timeout */
if (tls_client_timeout(ta) < 0)
goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -439,14 +508,15 @@ usage(char *argv0)
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n" "\t-D <level> \tDebug\n"
"\t-f <file> \tHTTP input file (overrides stdin)\n" "\t-f <file> \tHTTP input file (overrides stdin)\n"
"\t-e <file> \tFile including HTTP output (otherwise echo to stdout)\n"
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n" "\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n" "\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
"\t-p <port> \tPort (default %d)\n" "\t-p <port> \tPort (default %d)\n"
"\t-c <path> \tcert\n" "\t-c <path> \tcert\n"
"\t-C <path> \tcacert\n" "\t-C <path> \tcacert\n"
"\t-k <path> \tkey\n" "\t-k <path> \tkey\n"
"\t-n <nr> \tExpected incoming connections, 0 means no limit. Default: 1\n" "\t-n <nr> \tQuit after this many incoming connections, 0 means no limit. Default: 1\n"
"\t-o \tKeep open after receiving first reply. Otherwise close directly after receiving 1st reply\n"
"\t-t <sec> \tTimeout in seconds after each accept, if fired just exit. Default: 60s\n"
, ,
argv0, argv0,
RESTCONF_CH_TLS); RESTCONF_CH_TLS);
@ -525,6 +595,14 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
_connects = atoi(optarg); _connects = atoi(optarg);
break; break;
case 'o': /* keep open, do not close after first reply */
_keep_open = 1;
break;
case 't': /* timeout */
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
_timeout_s = atoi(optarg);
break;
default: default:
usage(argv[0]); usage(argv[0]);
break; break;
@ -569,6 +647,8 @@ main(int argc,
ta->ta_f = fp; ta->ta_f = fp;
if (clixon_event_reg_fd(ss, tls_server_accept_cb, ta, "tls server accept") < 0) if (clixon_event_reg_fd(ss, tls_server_accept_cb, ta, "tls server accept") < 0)
goto done; goto done;
if (tls_client_timeout(ta) < 0)
goto done;
if (clixon_event_loop(h) < 0) if (clixon_event_loop(h) < 0)
goto done; goto done;
retval = 0; retval = 0;

View file

@ -25,34 +25,8 @@ module clixon-restconf {
3. Related to (2), options that should not be settable in a datastore should be 3. Related to (2), options that should not be settable in a datastore should be
in clixon-config in clixon-config
***** BEGIN LICENSE BLOCK ***** Some of this spec if in-lined from ietf-restconf-server@2022-05-24.yang
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) ";
This file is part of CLIXON
Licensed under the Apache License, Version 2.0 (the \"License\");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an \"AS IS\" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the \"GPL\"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****";
revision 2022-08-01 { revision 2022-08-01 {
description description
"Added socket/call-home container "Added socket/call-home container
@ -303,10 +277,22 @@ module clixon-restconf {
"Indicates periodic connects"; "Indicates periodic connects";
leaf period { leaf period {
type uint32; /* XXX: note uit16 in std */ type uint32; /* XXX: note uit16 in std */
units "seconds"; /* XXX: note minutes in standard */ units "seconds"; /* XXX: note minutes in draft */
default "60"; default "3600"; /* XXX: same: 60min in draft */
description description
"Duration of time between periodic connections."; "Duration of time between periodic connections.";
}
leaf idle-timeout {
type uint16;
units "seconds";
default "120"; // two minutes
description
"Specifies the maximum number of seconds that
the underlying TCP session may remain idle.
A TCP session will be dropped if it is idle
for an interval longer than this number of
seconds. If set to zero, then the server
will never drop a session because it is idle.";
} }
} }
} }