Restconf RFC8071 call-home first working prototype

This commit is contained in:
Olof hagsand 2022-07-28 12:41:54 +02:00
parent a3b94f4781
commit 7d8ddf7697
18 changed files with 1115 additions and 122 deletions

View file

@ -39,6 +39,10 @@
## 5.9.0
Expected: September 2022
### New features
* RESTCONF call home according to RFC 8071
## 5.8.0
28 July 2022

View file

@ -455,6 +455,8 @@ restconf_http1_path_root(clicon_handle h,
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (subject)
free(subject);
if (xerr)
xml_free(xerr);
if (cvv)

View file

@ -820,6 +820,7 @@ restconf_config_init(clicon_handle h,
goto done;
}
/*! Create and bind restconf socket
*
* @param[in] netns0 Network namespace, special value "default" is same as NULL
@ -840,10 +841,8 @@ restconf_socket_init(const char *netns0,
int *ss)
{
int retval = -1;
struct sockaddr * sa;
struct sockaddr_in6 sin6 = { 0 };
struct sockaddr_in sin = { 0 };
size_t sin_len;
struct sockaddr sa = {0,};
size_t sa_len;
const char *netns;
clicon_debug(1, "%s %s %s %s %hu", __FUNCTION__, netns0, addrtype, addrstr, port);
@ -852,27 +851,9 @@ restconf_socket_init(const char *netns0,
netns = NULL;
else
netns = netns0;
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
sin_len = sizeof(struct sockaddr_in6);
sin6.sin6_port = htons(port);
sin6.sin6_family = AF_INET6;
inet_pton(AF_INET6, addrstr, &sin6.sin6_addr);
sa = (struct sockaddr *)&sin6;
}
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
sin_len = sizeof(struct sockaddr_in);
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(addrstr);
sa = (struct sockaddr *)&sin;
}
else{
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
return -1;
}
if (clixon_netns_socket(netns, sa, sin_len, backlog, flags, addrstr, ss) < 0)
if (clixon_inet2sin(addrtype, addrstr, port, &sa, &sa_len) < 0)
goto done;
if (clixon_netns_socket(netns, &sa, sa_len, backlog, flags, addrstr, ss) < 0)
goto done;
clicon_debug(1, "%s ss=%d", __FUNCTION__, *ss);
retval = 0;
@ -888,8 +869,9 @@ restconf_socket_init(const char *netns0,
* @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
* @param[out] ssl
* @param[out] port TCP Port
* @param[out] ssl SSL enabled?
* @param[out] callhome Callhome enabled?
*/
int
restconf_socket_extract(clicon_handle h,
@ -899,7 +881,8 @@ restconf_socket_extract(clicon_handle h,
char **address,
char **addrtype,
uint16_t *port,
uint16_t *ssl)
uint16_t *ssl,
int *callhome)
{
int retval = -1;
cxobj *x;
@ -979,6 +962,10 @@ restconf_socket_extract(clicon_handle h,
goto done;
}
}
if ((x = xpath_first(xs, nsc, "call-home")) != NULL)
*callhome = 1;
else
*callhome = 0;
retval = 0;
done:
if (cv)

View file

@ -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 restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome);
#endif /* _RESTCONF_LIB_H_ */

View file

@ -583,7 +583,7 @@ ssl_alpn_check(clicon_handle h,
int ret;
cbuf *cberr = NULL;
clicon_debug(1, "%s %s", __FUNCTION__, alpn);
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;
@ -642,23 +642,20 @@ ssl_alpn_check(clicon_handle h,
return retval;
} /* ssl_alpn_check */
/*! Accept new socket client
/*! Accept new socket client (ssl not ip)
* @param[in] fd Socket (unix or ip)
* @param[in] arg typecast clicon_handle
* @see openssl_init_socket where this callback is registered
*/
static int
restconf_accept_client(int fd,
void *arg)
restconf_ssl_accept_client(clicon_handle h,
int s,
restconf_socket *rsock)
{
int retval = -1;
restconf_socket *rsock;
restconf_native_handle *rh = NULL;
restconf_conn *rc = NULL;
clicon_handle h;
int s;
struct sockaddr from = {0,};
socklen_t len;
char *name = NULL;
int ret;
int e;
@ -668,30 +665,18 @@ restconf_accept_client(int fd,
unsigned int alpnlen = 0;
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
clicon_debug(1, "%s %d", __FUNCTION__, fd);
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 ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__,
rsock->rs_addrtype,
rsock->rs_addrstr,
rsock->rs_port);
h = rsock->rs_h;
#if 1
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done;
}
len = sizeof(from);
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
clicon_err(OE_UNIX, errno, "accept");
goto done;
}
#endif
/*
* Register callbacks for actual data socket
*/
@ -919,6 +904,50 @@ restconf_accept_client(int fd,
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
* @see openssl_init_socket where this callback is registered
*/
static int
restconf_accept_client(int fd,
void *arg)
{
int retval = -1;
restconf_socket *rsock;
clicon_handle h;
int s;
struct sockaddr from = {0,};
socklen_t len;
char *name = NULL;
clicon_debug(1, "%s %d", __FUNCTION__, fd);
if ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__,
rsock->rs_addrtype,
rsock->rs_addrstr,
rsock->rs_port);
h = rsock->rs_h;
len = sizeof(from);
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
clicon_err(OE_UNIX, errno, "accept");
goto done;
}
/* Accept SSL */
if (restconf_ssl_accept_client(h, s, rsock) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
if (name)
free(name);
return retval;
} /* restconf_accept_client */
/*!
@ -1019,10 +1048,63 @@ restconf_clixon_backend(clicon_handle h,
goto done;
}
/*! Periodically try to connect to callhome client
*/
int
restconf_callhome_timer(int fd,
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 *rs;
struct sockaddr sa = {0,};
size_t sa_len;
int s;
clicon_debug(1, "%s", __FUNCTION__);
gettimeofday(&now, NULL);
if ((rs = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
h = rs->rs_h;
if (clixon_inet2sin(rs->rs_addrtype, rs->rs_addrstr, rs->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){
close(s);
/* Fail: Initiate new timer */
timeradd(&now, &t1, &t);
if (clixon_event_reg_timeout(t,
restconf_callhome_timer, /* this function */
rs,
"restconf callhome timer") < 0)
goto done;
}
else {
rs->rs_ss = s;
if (restconf_ssl_accept_client(h, rs->rs_ss, rs) < 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
* @param[in] nsc Namespace context
* @retval 0 OK
* @retval -1 Error
*/
static int
openssl_init_socket(clicon_handle h,
@ -1033,6 +1115,7 @@ openssl_init_socket(clicon_handle h,
char *netns = NULL;
char *address = NULL;
char *addrtype = NULL;
int callhome = 0;
uint16_t ssl = 0;
uint16_t port = 0;
int ss = -1;
@ -1041,23 +1124,9 @@ openssl_init_socket(clicon_handle h,
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) < 0)
if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl, &callhome) < 0)
goto done;
/* Open restconf socket and bind */
if (restconf_socket_init(netns, address, addrtype, port,
SOCKET_LISTEN_BACKLOG,
#ifdef RESTCONF_OPENSSL_NONBLOCKING
SOCK_NONBLOCK, /* Also 0 is possible */
#else /* blocking */
0,
#endif
&ss
) < 0)
goto done;
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done;
}
/*
* Create per-socket openssl handle
* See restconf_native_terminate for freeing
@ -1068,8 +1137,33 @@ openssl_init_socket(clicon_handle h,
}
memset(rsock, 0, sizeof *rsock);
rsock->rs_h = h;
rsock->rs_ssl = ssl; /* true/false */
if (callhome){
if (!ssl){
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
goto done;
}
}
else { /* listen/accept */
/* Open restconf socket and bind for later accept */
if (restconf_socket_init(netns, address, addrtype, port,
SOCKET_LISTEN_BACKLOG,
#ifdef RESTCONF_OPENSSL_NONBLOCKING
SOCK_NONBLOCK, /* Also 0 is possible */
#else /* blocking */
0,
#endif
&ss
) < 0)
goto done;
}
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done;
}
rsock->rs_ss = ss;
rsock->rs_ssl = ssl;
if ((rsock->rs_addrstr = strdup(address)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
@ -1081,29 +1175,24 @@ openssl_init_socket(clicon_handle h,
rsock->rs_port = port;
INSQ(rsock, rh->rh_sockets);
/* ss is a server socket that the clients connect to. The callback
therefore accepts clients on ss */
#ifdef RESTCONF_HTTP1_UNITTEST
{
restconf_conn *rc;
if ((rc = restconf_conn_new(h, 0)) == NULL)
goto done;
rc->rc_s = 0;
if (restconf_stream_data_new(rc, 0) == NULL)
goto done;
if (clixon_event_reg_fd(0, restconf_connection, rc, "restconf socket") < 0)
if (callhome){
if (restconf_callhome_timer(ss, rsock) < 0)
goto done;
}
else {
/* ss is a server socket that the clients connect to. The callback
therefore accepts clients on ss */
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
goto done;
}
#else
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
goto done;
#endif
retval = 0;
done:
return retval;
}
/*! Init openssl, open and register server socket (ready for accept)
*
* Given a fully populated configuration tree.
* @param[in] h Clicon handle
* @param[in] dbg0 Manually set debug flag, if set overrides configuration setting
* @param[in] xrestconf XML tree containing restconf config

View file

@ -702,12 +702,10 @@ restconf_http1_process(restconf_conn *rc,
*/
if ((ret = http1_check_content_length(h, sd, &status)) < 0)
goto done;
#ifndef RESTCONF_HTTP1_UNITTEST /* Ignore content-length */
if (status == 1){
(*readmore)++;
goto ok;
}
#endif
/* nginx compatible, set HTTPS parameter if SSL */
if (rc->rc_ssl)
if (restconf_param_set(h, "HTTPS", "https") < 0)
@ -715,15 +713,9 @@ restconf_http1_process(restconf_conn *rc,
/* main restconf processing */
if (restconf_http1_path_root(h, rc) < 0)
goto done;
#ifdef RESTCONF_HTTP1_UNITTEST
if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
1, rc->rc_ssl) < 0)
goto done;
#else
if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
rc->rc_s, rc->rc_ssl) < 0)
goto done;
#endif
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(sd->sd_outp_buf);
cbuf_reset(sd->sd_inbuf);
@ -951,16 +943,5 @@ restconf_connection(int s,
retval = 0;
done:
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
#ifdef RESTCONF_HTTP1_UNITTEST /* unit test */
if (rc){
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
restconf_conn_free(rc);
}
exit(0);
#endif
return retval;
} /* restconf_connection */

View file

@ -152,11 +152,6 @@
*/
#define USE_CONFIG_FLAG_CACHE
/*! Restconf native unit test for fuzzing of http/1 parser
* See test/fuzz/http1
*/
#undef RESTCONF_HTTP1_UNITTEST
/*! If backend is restarted, cli and netconf client will retry (once) and reconnect
* Note, if client has locked or had edits in progress, these will be lost
* A warning will be printed

View file

@ -97,4 +97,6 @@ int send_msg_reply(int s, char *data, uint32_t datalen);
int detect_endtag(char *tag, char ch, int *state);
int clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len);
#endif /* _CLIXON_PROTO_H_ */

View file

@ -172,6 +172,7 @@ clicon_sig_ignore_get(void)
* }
* clixon_event_reg_fd(fd, fn, (void*)42, "call fn on input on fd");
* @endcode
* @see clixon_event_unreg_fd
*/
int
clixon_event_reg_fd(int fd,

View file

@ -810,3 +810,49 @@ detect_endtag(char *tag,
*state = 0;
return retval;
}
/*! Given family, addr str, port, return sockaddr and length
*
* @param[in] addrtype Address family: inet:ipv4-address or inet:ipv6-address
* @param[in] addrstr IP address as string
* @param[in] port TCP port host byte order
* @param[out] sa sockaddr, should be allocated
* @param[out] salen length of sockaddr data
* @code
* struct sockaddr sa = {0,};
* size_t sa_len;
* if (clixon_inet2sin(inet:ipv4-address, "0.0.0.0", 80, &sa, &sa_len) < 0)
* err;
* @endcode
* Probably misplaced, need a clixon_network file?
*/
int
clixon_inet2sin(const char *addrtype,
const char *addrstr,
uint16_t port,
struct sockaddr *sa,
size_t *sa_len)
{
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin;
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
sin6 = (struct sockaddr_in6 *)sa;
*sa_len = sizeof(struct sockaddr_in6);
sin6->sin6_port = htons(port);
sin6->sin6_family = AF_INET6;
inet_pton(AF_INET6, addrstr, &sin6->sin6_addr);
}
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
sin = (struct sockaddr_in *)sa;
*sa_len = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
sin->sin_addr.s_addr = inet_addr(addrstr);
}
else{
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
return -1;
}
return 0;
}

View file

@ -75,7 +75,7 @@ DATASTORE_TOP="config"
CLIXON_AUTOCLI_REV="2022-02-11"
CLIXON_LIB_REV="2021-12-05"
CLIXON_CONFIG_REV="2022-03-21"
CLIXON_RESTCONF_REV="2022-03-21"
CLIXON_RESTCONF_REV="2022-08-01"
CLIXON_EXAMPLE_REV="2020-12-01"
# Length of TSL RSA key

View file

@ -61,6 +61,12 @@ function memonce(){
memerr=0
for test in $pattern; do
# Can happen if no pattern, eg pattern=foo but "foo" does not exist
if [ ! -f $test ]; then
echo -e "\e[31mNo such file: $test"
echo -ne "\e[0m"
exit -1
fi
if [ $testnr != 0 ]; then echo; fi
perfnr=1000 # Limit performance tests
testfile=$test

243
test/test_restconf_callhome.sh Executable file
View file

@ -0,0 +1,243 @@
#!/usr/bin/env bash
# test of Restconf callhome
# See RFC 8071 NETCONF Call Home and RESTCONF Call Home
# No NACM for now
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
: ${clixon_restconf_callhome_client:=clixon_restconf_callhome_client}
cfg=$dir/conf_yang.xml
fyang=$dir/clixon-example.yang
clispec=$dir/spec.cli
certdir=$dir/certs
cakey=$certdir/ca_key.pem
cacert=$certdir/ca_cert.pem
srvkey=$certdir/srv_key.pem
srvcert=$certdir/srv_cert.pem
users="andy" # generate certs for some users
RCPROTO=https
# Callhome stream is HTTP/1.1, other communication is HTTP/2
HVER=2
HVERCH=1.1
test -d $certdir || mkdir $certdir
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLISPEC_DIR>$dir</CLICON_CLISPEC_DIR>
<CLICON_YANG_LIBRARY>false</CLICON_YANG_LIBRARY>
<CLICON_SOCK>$dir/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<restconf>
<enable>true</enable>
<auth-type>client-certificate</auth-type>
<pretty>false</pretty>
<server-cert-path>$srvcert</server-cert-path>
<server-key-path>$srvkey</server-key-path>
<server-ca-cert-path>$cacert</server-ca-cert-path>
<debug>1</debug>
<socket>
<namespace>default</namespace>
<call-home>
<connection-type>
<persistent/>
</connection-type>
</call-home>
<address>127.0.0.1</address>
<port>4336</port>
<ssl>true</ssl>
</socket>
<socket>
<namespace>default</namespace>
<address>0.0.0.0</address>
<port>443</port>
<ssl>true</ssl>
</socket>
</restconf>
<autocli>
<module-default>false</module-default>
<list-keyword-default>kw-nokey</list-keyword-default>
<treeref-state-default>false</treeref-state-default>
<rule>
<name>include clixon-example</name>
<operation>enable</operation>
<module-name>clixon-example</module-name>
</rule>
<rule>
<name>include ietf-netconf-server</name>
<operation>enable</operation>
<module-name>ietf-netconf-server</module-name>
</rule>
<rule>
<name>include ietf-keystore</name>
<operation>enable</operation>
<module-name>ietf-keystore</module-name>
</rule>
<rule>
<name>include ietf-truststore</name>
<operation>enable</operation>
<module-name>ietf-truststore</module-name>
</rule>
</autocli>
</clixon-config>
EOF
cat <<EOF > $fyang
module clixon-example{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
/* Generic config data */
container table{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
}
EOF
cat <<EOF > $clispec
CLICON_MODE="example";
CLICON_PROMPT="%U@%H %W> ";
CLICON_PLUGIN="example_cli";
# Autocli syntax tree operations
set @datamodel, cli_auto_set();
merge @datamodel, cli_auto_merge();
create @datamodel, cli_auto_create();
delete("Delete a configuration item") @datamodel, cli_auto_del();
validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit();
show("Show a particular state of the system"){
configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{
xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false);
cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", false, false, "set ");
netconf("Show configuration as netconf edit-config operation"), cli_auto_show("datamodel", "candidate", "netconf", false, false);
text("Show configuration as text"), cli_auto_show("datamodel", "candidate", "text", false, false);
json("Show configuration as JSON"), cli_auto_show("datamodel", "candidate", "json", false, false);
}
state("Show configuration and state"), cli_auto_show("datamodel", "running", "xml", false, true);
}
EOF
# Create server certs
cacerts $cakey $cacert
servercerts $cakey $cacert $srvkey $srvcert
# Create client certs
for name in $users; do
cat<<EOF > $dir/$name.cnf
[req]
prompt = no
distinguished_name = dn
[dn]
CN = $name # This can be verified using SSL_set1_host
emailAddress = $name@foo.bar
O = Clixon
L = Stockholm
C = SE
EOF
# Create client key
openssl genpkey -algorithm RSA -out "$certdir/$name.key" || err "Generate client key"
# Generate CSR (signing request)
openssl req -new -config $dir/$name.cnf -key $certdir/$name.key -out $certdir/$name.csr
# Sign by CA
openssl x509 -req -extfile $dir/$name.cnf -days 7 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt || err "Generate signing client cert"
done
# hardcoded to andy
cat <<EOF > $dir/startup_db
<${DATASTORE_TOP}>
</${DATASTORE_TOP}>
EOF
# Callhome request from client
cat <<EOF > $dir/data
GET /restconf/data HTTP/$HVERCH
Host: localhost
Accept: application/yang-data+xml
EOF
new "test params: -f $cfg"
# Bring your own backend
if [ $BE -ne 0 ]; then
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s startup -f $cfg"
start_backend -s startup -f $cfg
fi
new "wait backend"
wait_backend
if [ $RC -ne 0 ]; then
new "kill old restconf daemon"
stop_restconf_pre
new "start restconf daemon"
start_restconf -f $cfg
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"
new "Send GET via callhome client"
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"
# Kill old
if [ $RC -ne 0 ]; then
new "Kill restconf daemon"
stop_restconf
fi
if [ $BE -ne 0 ]; then
new "Kill backend"
# Check if premature kill
pid=$(pgrep -u root -f clixon_backend)
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
fi
rm -rf $dir
new "endtest"
endtest

View file

@ -97,6 +97,7 @@ APPSRC += clixon_netconf_ssh_callhome.c
APPSRC += clixon_netconf_ssh_callhome_client.c
ifdef with_restconf
APPSRC += clixon_util_stream.c # Needs curl
APPSRC += clixon_restconf_callhome_client.c
endif
ifdef with_http2
APPSRC += clixon_util_ssl.c # requires http/2
@ -156,13 +157,13 @@ ifdef with_restconf
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
clixon_util_ssl: clixon_util_ssl.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
clixon_restconf_callhome_client: clixon_restconf_callhome_client.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
endif
#clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
# $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
distclean: clean
rm -f Makefile *~ .depend

View file

@ -105,6 +105,39 @@ callhome_connect(struct sockaddr *sa,
return retval;
}
/* @see clixon_inet2sin */
static int
inet2sin(const char *addrtype,
const char *addrstr,
uint16_t port,
struct sockaddr *sa,
size_t *sa_len)
{
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin;
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
sin6 = (struct sockaddr_in6 *)sa;
*sa_len = sizeof(struct sockaddr_in6);
sin6->sin6_port = htons(port);
sin6->sin6_family = AF_INET6;
inet_pton(AF_INET6, addrstr, &sin6->sin6_addr);
}
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
sin = (struct sockaddr_in *)sa;
*sa_len = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
sin->sin_addr.s_addr = inet_addr(addrstr);
}
else{
fprintf(stderr, "Unexpected addrtype: %s\n", addrtype);
return -1;
}
return 0;
}
static int
ssh_server_exec(int s,
char *sshdbin,
@ -220,10 +253,8 @@ main(int argc,
int c;
char *family = "ipv4";
char *addr = NULL;
struct sockaddr *sa;
struct sockaddr_in6 sin6 = { 0 };
struct sockaddr_in sin = { 0 };
size_t sin_len;
struct sockaddr sa = {0, };
size_t sa_len;
int dbg = 0;
uint16_t port = NETCONF_CH_SSH;
int s = -1;
@ -273,6 +304,10 @@ main(int argc,
usage(argv[0]);
goto done;
}
#if 1
if (inet2sin(family, addr, port, &sa, &sa_len) < 0)
goto done;
#else
if (strcmp(family, "ipv6") == 0){
sin_len = sizeof(struct sockaddr_in6);
sin6.sin6_port = htons(port);
@ -291,7 +326,8 @@ main(int argc,
fprintf(stderr, "-f <%s> is invalid family\n", family);
goto done;
}
if (callhome_connect(sa, sin_len, &s) < 0)
#endif
if (callhome_connect(&sa, sa_len, &s) < 0)
goto done;
/* For some reason this sshd returns -1 which is unclear why */
if (ssh_server_exec(s, sshdbin, sshdconfigfile, clixonconfigfile, dbg) < 0)

View file

@ -0,0 +1,537 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 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 *****
See RFC 8071 NETCONF Call Home and RESTCONF Call Home
device/server client
+-----------------+ 1) tcp connect +-----------------+
| clixon_restconf | ----------------> | callhome-client | <------ 3) HTTP
| | 2) tls | |
+-----------------+ <--------------- +-----------------+
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <signal.h>
#include <netdb.h> /* gethostbyname */
#include <arpa/inet.h> /* inet_pton */
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <openssl/ssl.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
#define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:"
#define RESTCONF_CH_TLS 4336
/* User struct for context / accept */
typedef struct {
int ta_ss; /* accept socket */
SSL_CTX *ta_ctx; /* SSL context */
FILE *ta_f; /* Input data file */
} tls_accept_handle;
/* User connection-specific data handle */
typedef struct {
int sd_s; /* data socket */
SSL *sd_ssl; /* SSL connection data */
} tls_session_data;
/*! Create and bind stream socket
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queie of pending connections
* @param[out] sock Server socket (bound for accept)
*/
int
callhome_bind(struct sockaddr *sa,
size_t sin_len,
int backlog,
int *sock)
{
int retval = -1;
int s = -1;
int on = 1;
if (sock == NULL){
errno = EINVAL;
perror("sock");
goto done;
}
/* create inet socket */
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_KEEPALIVE");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_REUSEADDR");
goto done;
}
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
if (sa->sa_family == AF_INET6 &&
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
perror("setsockopt IPPROTO_IPV6");
goto done;
}
if (bind(s, sa, sin_len) == -1) {
perror("bind");
goto done;
}
if (listen(s, backlog) < 0){
perror("listen");
goto done;
}
if (sock)
*sock = s;
retval = 0;
done:
if (retval != 0 && s != -1)
close(s);
return retval;
}
/*! Client data socket
*/
static int
tls_input_cb(int s,
void *arg)
{
int retval = -1;
tls_session_data *sd = (tls_session_data *)arg;
SSL *ssl;
char buf[1024];
int n;
clicon_debug(1, "%s", __FUNCTION__);
ssl = sd->sd_ssl;
/* get reply & decrypt */
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
if (n == 0){
clicon_debug(1, "%s closed", __FUNCTION__);
goto done;
}
buf[n] = 0;
fprintf(stdout, "%s\n", buf);
SSL_shutdown(ssl);
SSL_free(ssl);
clixon_event_unreg_fd(s, tls_input_cb);
close(s);
free(sd);
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
done:
return retval;
}
/*! Create ssl connection, select alpn, connect and verify
*/
static int
tls_ssl_init_connect(SSL_CTX *ctx,
int s,
SSL **sslp)
{
int retval = -1;
SSL *ssl = NULL;
unsigned char protos[10];
int ret;
int verify;
int sslerr;
/* create new SSL connection state */
if ((ssl = SSL_new(ctx)) == NULL){
clicon_err(OE_SSL, 0, "SSL_new.");
goto done;
}
SSL_set_fd(ssl, s); /* attach the socket descriptor */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
protos[0] = 8;
strncpy((char*)&protos[1], "http/1.1", 9);
if ((retval = SSL_set_alpn_protos(ssl, protos, 9)) != 0){
clicon_err(OE_SSL, retval, "SSL_set_alpn_protos.");
goto done;
}
#if 0
SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
#endif
/* perform the connection
TLSEXT_TYPE_application_layer_protocol_negotiation
int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
unsigned int protos_len);
see
https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_alpn_select_cb.html
*/
if ((ret = SSL_connect(ssl)) < 1){
sslerr = SSL_get_error(ssl, ret);
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
switch (sslerr){
case SSL_ERROR_SSL: /* 1 */
goto done;
break;
default:
clicon_err(OE_XML, errno, "SSL_connect");
goto done;
break;
}
}
/* check certificate verification result */
verify = SSL_get_verify_result(ssl);
switch (verify) {
case X509_V_OK:
break;
default:
clicon_err(OE_SSL, errno, "verify problems: %d", verify);
goto done;
}
*sslp = ssl;
retval = 0;
done:
return retval;
}
/*! Read data from file/stdin and write to TLS data socket
*/
static int
tls_write_file(FILE *fp,
SSL *ssl)
{
int retval = -1;
char *buf = NULL;
int buflen = 1024; /* start size */
char ch;
int ret;
int sslerr;
size_t len = 0;
if ((buf = malloc(buflen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
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);
}
retval = 0;
done:
if (buf)
free(buf);
return retval;
}
/*! Callhome-server accept socket
*/
static int
tls_accept_cb(int ss,
void *arg)
{
int retval = -1;
tls_accept_handle *ta = (tls_accept_handle *)arg;
tls_session_data *sd = NULL;
int s;
struct sockaddr from = {0,};
socklen_t len;
SSL *ssl = NULL;
clicon_debug(1, "%s", __FUNCTION__);
len = sizeof(from);
if ((s = accept(ss, &from, &len)) < 0){
perror("accept");
goto done;
}
clicon_debug(1, "accepted");
if (tls_ssl_init_connect(ta->ta_ctx, s, &ssl) < 0)
goto done;
clicon_debug(1, "connected");
if ((sd = malloc(sizeof(*sd))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(sd, 0, sizeof(*sd));
sd->sd_s = s;
sd->sd_ssl = ssl;
/* Write HTTP request on socket */
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)
goto done;
retval = 0;
done:
return retval;
}
/*!
* out must be set to point to the selected protocol (which may be within in).
*/
static int
tls_proto_select_cb(SSL *s,
unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*! Verify tls auth
* @see tlsauth_verify_callback
* This code needs a "X509 store", see X509_STORE_new()
* crl_file / crl_dir
*/
static int
tls_auth_verify_callback(int preverify_ok,
X509_STORE_CTX *x509_ctx)
{
return 1; /* success */
}
static SSL_CTX *
tls_ctx_init(const char *cert_path,
const char *key_path,
const char *ca_cert_path)
{
SSL_CTX *ctx = NULL;
if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) {
clicon_err(OE_SSL, 0, "SSL_CTX_new");
goto done;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, tls_auth_verify_callback);
/* get peer certificate
nc_client_tls_update_opts */
if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_use_certificate_file");
goto done;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_use_PrivateKey_file");
goto done;
}
if (SSL_CTX_load_verify_locations(ctx, ca_cert_path, NULL) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_load_verify_locations");
goto done;
}
(void)SSL_CTX_set_next_proto_select_cb(ctx, tls_proto_select_cb, NULL);
return ctx;
done:
return NULL;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file> \tHHTP input file (overrides stdin)\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"
,
argv0,
RESTCONF_CH_TLS);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
clicon_handle h;
int c;
uint16_t port = RESTCONF_CH_TLS;
SSL_CTX *ctx = NULL;
int ss = -1;
int dbg = 0;
tls_accept_handle *ta = NULL;
char *input_filename = NULL;
char *ca_cert_path = NULL;
char *cert_path = NULL;
char *key_path = NULL;
FILE *fp = stdin; /* base file, stdin, can be overridden with -f */
size_t sa_len;
char *addr = "127.0.0.1";
char *family = "inet:ipv4-address";
struct sockaddr sa = {0,};
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
while ((c = getopt(argc, argv, UTIL_TLS_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'f':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
input_filename = optarg;
break;
case 'F':
family = optarg;
break;
case 'a':
addr = optarg;
break;
case 'p':
if (sscanf(optarg, "%hu", &port) != 1)
usage(argv[0]);
break;
case 'c':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
cert_path = optarg;
break;
case 'C':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
ca_cert_path = optarg;
break;
case 'k':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
key_path = optarg;
break;
default:
usage(argv[0]);
break;
}
if (cert_path == NULL || key_path == NULL || ca_cert_path == NULL){
fprintf(stderr, "-c <cert path> and -k <key path> -C <ca-cert> are mandatory\n");
usage(argv[0]);
}
clicon_debug_init(dbg, NULL);
if (input_filename){
if ((fp = fopen(input_filename, "r")) == NULL){
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
goto done;
}
}
if ((ctx = tls_ctx_init(cert_path, key_path, ca_cert_path)) == NULL)
goto done;
if (port == 0){
fprintf(stderr, "-p <port> is invalid\n");
usage(argv[0]);
goto done;
}
if (addr == NULL){
fprintf(stderr, "-a <addr> is NULL\n");
usage(argv[0]);
goto done;
}
if (clixon_inet2sin(family, addr, port, &sa, &sa_len) < 0)
goto done;
/* Bind port */
if (callhome_bind(&sa, sa_len, 1, &ss) < 0)
goto done;
clicon_debug(1, "bind");
if ((ta = malloc(sizeof(*ta))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(ta, 0, sizeof(*ta));
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)
goto done;
if (clixon_event_loop(h) < 0)
goto done;
retval = 0;
done:
if (ss != -1)
clixon_event_unreg_fd(ss, tls_accept_cb);
if (ta)
free(ta);
if (fp)
fclose(fp);
if (ss != -1)
close(ss);
if (ctx)
SSL_CTX_free(ctx); /* release context */
clicon_handle_exit(h); /* frees h and options (and streams) */
clixon_err_exit();
clicon_debug(1, "clixon_restconf_callhome_client pid:%u done", getpid());
clicon_log_exit(); /* Must be after last clicon_debug */
return retval;
}

View file

@ -46,7 +46,7 @@ YANGSPECS = clixon-config@2022-03-21.yang # 5.7
YANGSPECS += clixon-lib@2021-12-05.yang # 5.5
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
YANGSPECS += clixon-restconf@2022-03-21.yang # 5.7
YANGSPECS += clixon-restconf@2022-08-01.yang # 5.9
YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6
all:

View file

@ -26,7 +26,7 @@ module clixon-restconf {
in clixon-config
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON
@ -53,6 +53,17 @@ module clixon-restconf {
***** END LICENSE BLOCK *****";
revision 2022-08-01 {
description
"Added socket/call-home container
Released in Clixon 5.9";
}
revision 2022-03-21 {
description
"Added feature:
http-data - Limited static http server
Released in Clixon 5.7";
}
revision 2021-05-20 {
description
"Added log-destination for restconf
@ -74,7 +85,6 @@ module clixon-restconf {
description
"Initial release";
}
feature fcgi {
description
"This feature indicates that the restconf server supports the fast-cgi reverse
@ -89,6 +99,19 @@ module clixon-restconf {
"This feature allows the use of authentication-type none.";
}
feature http-data {
description
"This feature allows for a very limited static http-data function as
addition to RESTCONF.
It is limited to:
1. path: Local static files within WWW_DATA_ROOT
2. operation GET, HEAD, OPTIONS
3. query parameters not supported
4. indata should be NULL (no write operations)
5. Limited media: text/html, JavaScript, image, and css
6. Authentication as restconf
7. HTTP/1+2, TLS as restconf";
}
typedef http-auth-type {
type enumeration {
enum none {
@ -140,6 +163,14 @@ module clixon-restconf {
For example, if the restconf daemon is under systemd management, the restconf
daemon will only start if enable=true.";
}
leaf enable-http-data {
type boolean;
default "false";
if-feature "http-data";
description
"Enables Limited static http-data functionality.
enable must be true for this option to be meaningful.";
}
leaf auth-type {
type http-auth-type;
description
@ -240,6 +271,38 @@ module clixon-restconf {
default true;
description "Enable for HTTPS otherwise HTTP protocol";
}
/* Some of this in-lined from ietf-restconf-server@2022-05-24.yang */
container call-home {
presence
"Identifies that the server has been configured to initiate
call home connections.
If set, address/port refers to destination.";
description
"See RFC 8071 NETCONF Call Home and RESTCONF Call Home";
container connection-type {
description
"Indicates the RESTCONF server's preference for how the
RESTCONF connection is maintained.";
choice connection-type {
mandatory true;
description
"Selects between available connection types.";
case persistent-connection {
container persistent {
presence
"Indicates that a persistent connection is to be
maintained.";
}
}
case periodic-connection {
container periodic {
presence
"Indicates periodic connects";
}
}
}
}
}
}
}
container restconf {