Restconf callhome: idle-timeout for periodic callhome
This commit is contained in:
parent
9a5504eed0
commit
ba9b313295
10 changed files with 426 additions and 268 deletions
|
|
@ -42,7 +42,13 @@ Expected: September 2022
|
|||
### New features
|
||||
|
||||
* 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
|
||||
|
||||
|
|
|
|||
|
|
@ -863,157 +863,3 @@ restconf_socket_init(const char *netns0,
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_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 **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_ */
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@
|
|||
|
||||
static int session_id_context = 1;
|
||||
|
||||
|
||||
/*! Set restconf native handle
|
||||
* @param[in] h Clicon handle
|
||||
* @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){
|
||||
while ((rsock = rh->rh_sockets) != NULL){
|
||||
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){
|
||||
clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client);
|
||||
|
|
@ -710,26 +711,16 @@ openssl_init_socket(clicon_handle h,
|
|||
cvec *nsc)
|
||||
{
|
||||
int retval = -1;
|
||||
char *description = NULL;
|
||||
char *netns = NULL;
|
||||
char *address = NULL;
|
||||
char *addrtype = NULL;
|
||||
int callhome = 0;
|
||||
uint16_t ssl = 0;
|
||||
uint16_t port = 0;
|
||||
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, &description, &netns, &address, &addrtype, &port, &ssl, &callhome, &periodic, &period, &max_attempts) < 0)
|
||||
goto done;
|
||||
|
||||
/*
|
||||
* Create per-socket openssl handle
|
||||
* See restconf_native_terminate for freeing
|
||||
|
|
@ -740,20 +731,13 @@ openssl_init_socket(clicon_handle h,
|
|||
}
|
||||
memset(rsock, 0, sizeof *rsock);
|
||||
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");
|
||||
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
||||
if (restconf_socket_extract(h, xs, nsc, rsock, &netns, &address, &addrtype, &port) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (callhome){
|
||||
if (!ssl){
|
||||
if (rsock->rs_callhome){
|
||||
if (!rsock->rs_ssl){
|
||||
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -786,7 +770,7 @@ openssl_init_socket(clicon_handle h,
|
|||
rsock->rs_port = port;
|
||||
INSQ(rsock, rh->rh_sockets);
|
||||
|
||||
if (callhome){
|
||||
if (rsock->rs_callhome){
|
||||
rsock->rs_ss = -1; /* Not applicable from callhome */
|
||||
if (restconf_callhome_timer(rsock, 0) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -309,8 +309,8 @@ restconf_connection_sanity(clicon_handle h,
|
|||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cberr, "HTTP cert verification failed: %s",
|
||||
X509_verify_cert_error_string(code));
|
||||
cprintf(cberr, "HTTP cert verification failed: %s[%ld]",
|
||||
X509_verify_cert_error_string(code), code);
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", cbuf_get(cberr)) < 0)
|
||||
goto done;
|
||||
if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
|
||||
|
|
@ -975,16 +975,20 @@ restconf_connection_close(clicon_handle h,
|
|||
{
|
||||
int retval = -1;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, rsock->rs_description);
|
||||
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)
|
||||
if (rsock->rs_callhome){
|
||||
if (rsock->rs_periodic &&
|
||||
restconf_idle_timer_unreg(rsock) < 0)
|
||||
goto done;
|
||||
if (restconf_callhome_timer(rsock, 1) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||
|
|
@ -1328,6 +1332,81 @@ restconf_ssl_accept_client(clicon_handle h,
|
|||
return retval;
|
||||
} /* 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
|
||||
*
|
||||
* @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
|
||||
* returns directly.
|
||||
*/
|
||||
int
|
||||
static int
|
||||
restconf_callhome_cb(int fd,
|
||||
void *arg)
|
||||
{
|
||||
|
|
@ -1348,14 +1427,12 @@ restconf_callhome_cb(int fd,
|
|||
size_t sa_len;
|
||||
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");
|
||||
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)
|
||||
|
|
@ -1379,6 +1456,9 @@ restconf_callhome_cb(int fd,
|
|||
rsock->rs_attempts = 0;
|
||||
if (restconf_ssl_accept_client(h, s, rsock) < 0)
|
||||
goto done;
|
||||
if (rsock->rs_periodic && rsock->rs_idle_timeout &&
|
||||
restconf_idle_timer(rsock) < 0)
|
||||
goto done;
|
||||
}
|
||||
clicon_debug(1, "%s connect done", __FUNCTION__);
|
||||
retval = 0;
|
||||
|
|
@ -1386,16 +1466,23 @@ restconf_callhome_cb(int fd,
|
|||
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
|
||||
*
|
||||
* 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)
|
||||
* @param[in] new if periodic: 1: Force a new period
|
||||
* @see restconf_callhome_timer_unreg
|
||||
*/
|
||||
int
|
||||
restconf_callhome_timer(restconf_socket *rsock,
|
||||
int new)
|
||||
int status)
|
||||
{
|
||||
int retval = -1;
|
||||
struct timeval now;
|
||||
|
|
@ -1403,15 +1490,16 @@ restconf_callhome_timer(restconf_socket *rsock,
|
|||
struct timeval t1 = {0, 0};
|
||||
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);
|
||||
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){
|
||||
if ((status == 1) || 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;
|
||||
|
|
@ -1432,10 +1520,7 @@ restconf_callhome_timer(restconf_socket *rsock,
|
|||
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
|
||||
|
|
@ -1453,3 +1538,159 @@ restconf_callhome_timer(restconf_socket *rsock,
|
|||
cbuf_free(cb);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ typedef struct {
|
|||
int rs_periodic; /* 0: persistent, 1: periodic (if callhome) */
|
||||
uint32_t rs_period; /* Period in s (if callhome & periodic) */
|
||||
uint8_t rs_max_attempts; /* max connect attempts (if callhome) */
|
||||
|
||||
uint16_t rs_idle_timeout; /* Max underlying TCP session remains idle (if callhome and periodic) */
|
||||
uint64_t rs_start; /* First period start, next is start+periods*period */
|
||||
uint64_t rs_period_nr; /* Dynamic succeeding or timed out periods.
|
||||
Set in restconf_callhome_timer*/
|
||||
|
|
@ -165,9 +165,12 @@ 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, 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);
|
||||
int restconf_idle_timer_unreg(restconf_socket *rsock);
|
||||
int restconf_idle_timer(restconf_socket *rsock);
|
||||
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_ */
|
||||
|
||||
|
|
|
|||
|
|
@ -286,6 +286,8 @@ clixon_event_reg_timeout(struct timeval t,
|
|||
* different from clixon_event_unreg_fd.
|
||||
* @param[in] fn Function to call at time t
|
||||
* @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_unreg_fd
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ cat <<EOF > $cfg
|
|||
<persistent/>
|
||||
</connection-type>
|
||||
<reconnect-strategy>
|
||||
<max-attempts>3</max-attempts>
|
||||
<max-attempts>1</max-attempts>
|
||||
</reconnect-strategy>
|
||||
</call-home>
|
||||
<address>127.0.0.1</address>
|
||||
|
|
@ -92,10 +92,11 @@ cat <<EOF > $cfg
|
|||
<connection-type>
|
||||
<periodic>
|
||||
<period>10</period>
|
||||
<idle-timeout>5</idle-timeout>
|
||||
</periodic>
|
||||
</connection-type>
|
||||
<reconnect-strategy>
|
||||
<max-attempts>3</max-attempts>
|
||||
<max-attempts>1</max-attempts>
|
||||
</reconnect-strategy>
|
||||
</call-home>
|
||||
<address>127.0.0.1</address>
|
||||
|
|
@ -290,13 +291,13 @@ fi
|
|||
new "wait restconf"
|
||||
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"
|
||||
|
||||
# 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"
|
||||
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")
|
||||
|
||||
let t=t1-t0
|
||||
|
|
@ -307,18 +308,18 @@ 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
|
||||
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")
|
||||
|
||||
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"
|
||||
new "Check periodic interval ($t) is in interval [10-21]"
|
||||
if [ $t -lt 10 -o $t -ge 21 ]; then
|
||||
err1 "timer in interval [10-21] 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"
|
||||
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")
|
||||
|
||||
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"
|
||||
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
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
/* clixon */
|
||||
#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
|
||||
|
||||
|
|
@ -88,6 +88,12 @@ typedef struct {
|
|||
/* Expected connects */
|
||||
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
|
||||
* @param[in] sa Socketaddress
|
||||
* @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
|
||||
* 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
|
||||
tls_server_reply_cb(int s,
|
||||
|
|
@ -199,10 +208,12 @@ tls_server_reply_cb(int s,
|
|||
char *expbuf = NULL;
|
||||
struct timeval tn;
|
||||
struct timeval td;
|
||||
static int seq = 1;
|
||||
static int seq = 0;
|
||||
static struct timeval t0 = {0,};
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if (seq == 0)
|
||||
gettimeofday(&t0, NULL);
|
||||
ssl = sd->sd_ssl;
|
||||
/* get reply & decrypt */
|
||||
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
|
||||
|
|
@ -210,24 +221,42 @@ tls_server_reply_cb(int s,
|
|||
goto done;
|
||||
}
|
||||
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_free(ssl);
|
||||
clixon_event_unreg_fd(s, tls_server_reply_cb);
|
||||
fprintf(stdout, "Close %d remote %lu\n", seq, td.tv_sec);
|
||||
close(s);
|
||||
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 */
|
||||
else
|
||||
_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;
|
||||
done:
|
||||
if (expbuf)
|
||||
|
|
@ -330,6 +359,42 @@ tls_write_file(FILE *fp,
|
|||
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
|
||||
*/
|
||||
static int
|
||||
|
|
@ -367,6 +432,10 @@ tls_server_accept_cb(int ss,
|
|||
/* register callback for reply */
|
||||
if (clixon_event_reg_fd(s, tls_server_reply_cb, sd, "tls server reply") < 0)
|
||||
goto done;
|
||||
/* Unregister old + register new timeout */
|
||||
if (tls_client_timeout(ta) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -439,14 +508,15 @@ usage(char *argv0)
|
|||
"\t-h \t\tHelp\n"
|
||||
"\t-D <level> \tDebug\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-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
|
||||
"\t-p <port> \tPort (default %d)\n"
|
||||
"\t-c <path> \tcert\n"
|
||||
"\t-C <path> \tcacert\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,
|
||||
RESTCONF_CH_TLS);
|
||||
|
|
@ -525,6 +595,14 @@ main(int argc,
|
|||
usage(argv[0]);
|
||||
_connects = atoi(optarg);
|
||||
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:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
|
|
@ -569,6 +647,8 @@ main(int argc,
|
|||
ta->ta_f = fp;
|
||||
if (clixon_event_reg_fd(ss, tls_server_accept_cb, ta, "tls server accept") < 0)
|
||||
goto done;
|
||||
if (tls_client_timeout(ta) < 0)
|
||||
goto done;
|
||||
if (clixon_event_loop(h) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
|
|
|
|||
|
|
@ -25,34 +25,8 @@ module clixon-restconf {
|
|||
3. Related to (2), options that should not be settable in a datastore should be
|
||||
in clixon-config
|
||||
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
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 *****";
|
||||
|
||||
Some of this spec if in-lined from ietf-restconf-server@2022-05-24.yang
|
||||
";
|
||||
revision 2022-08-01 {
|
||||
description
|
||||
"Added socket/call-home container
|
||||
|
|
@ -303,10 +277,22 @@ module clixon-restconf {
|
|||
"Indicates periodic connects";
|
||||
leaf period {
|
||||
type uint32; /* XXX: note uit16 in std */
|
||||
units "seconds"; /* XXX: note minutes in standard */
|
||||
default "60";
|
||||
units "seconds"; /* XXX: note minutes in draft */
|
||||
default "3600"; /* XXX: same: 60min in draft */
|
||||
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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue