* Multi-socket feature (eg IPv4/IPv6 http/https) to restconf evhtp
* Added by-ref parameter to `ys_cv_validate()` returning which sub-yang spec was validated in a union.
This commit is contained in:
parent
707685f5ff
commit
6eb18da5e9
13 changed files with 683 additions and 382 deletions
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
### New features
|
||||
|
||||
* Initial NBMA functionality (thanks: @benavrhm): "ds" resource
|
||||
* Restconf configuration has a new configure model: `clixon-restconf.yang` enabling restconf daemon configuration from datastore instead of from config file.
|
||||
* Restconf config data, such as addresses, authentication type, etc, is read from the backend datastore instead of the clixon-config file on startup.
|
||||
* This is enabled by setting `CLIXON_RESTCONF_CONFIG` to true (or start clixon-restconf with `-b`), in which case restconf data can be set in the datastore.
|
||||
|
|
@ -48,6 +49,7 @@ Users may have to change how they access the system
|
|||
|
||||
Developers may need to change their code
|
||||
|
||||
* Added by-ref parameter to `ys_cv_validate()` returning which sub-yang spec was validated in a union.
|
||||
* Changed first parameter from `int fd` to `FILE *f` in the following functions:
|
||||
* clixon_xml_parse_file(), clixon_json_parse_file(), yang_parse_file()
|
||||
* See [Bytewise read() of files is slow #146](https://github.com/clicon/clixon/issues/146)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
|
||||
/*! Open an INET stream socket and bind it to a file descriptor
|
||||
*
|
||||
o * @param[in] h Clicon handle
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dst IPv4 address (see inet_pton(3))
|
||||
* @retval s Socket file descriptor (see socket(2))
|
||||
* @retval -1 Error
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@
|
|||
#include <sys/wait.h>
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h> /* chmod */
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/* evhtp */
|
||||
#include <evhtp/evhtp.h>
|
||||
|
|
@ -88,8 +91,9 @@
|
|||
|
||||
/* clixon evhtp handle */
|
||||
typedef struct {
|
||||
evhtp_t *eh_htp;
|
||||
struct event_base *eh_evbase;
|
||||
evhtp_t **eh_htpvec; /* One per socket */
|
||||
int eh_htplen; /* Number of sockets */
|
||||
struct event_base *eh_evbase; /* Change to list */
|
||||
evhtp_ssl_cfg_t *eh_ssl_config;
|
||||
} cx_evhtp_handle;
|
||||
|
||||
|
|
@ -105,12 +109,16 @@ static void
|
|||
evhtp_terminate(cx_evhtp_handle *eh)
|
||||
{
|
||||
evhtp_ssl_cfg_t *sc;
|
||||
int i;
|
||||
|
||||
if (eh == NULL)
|
||||
return;
|
||||
if (eh->eh_htp){
|
||||
evhtp_unbind_socket(eh->eh_htp);
|
||||
evhtp_free(eh->eh_htp);
|
||||
if (eh->eh_htpvec){
|
||||
for (i=0; i<eh->eh_htplen; i++){
|
||||
evhtp_unbind_socket(eh->eh_htpvec[i]);
|
||||
evhtp_free(eh->eh_htpvec[i]);
|
||||
}
|
||||
free(eh->eh_htpvec);
|
||||
}
|
||||
if (eh->eh_evbase)
|
||||
event_base_free(eh->eh_evbase);
|
||||
|
|
@ -554,9 +562,9 @@ cx_get_ssl_server_certs(clicon_handle h,
|
|||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
cx_get_ssl_client_certs(clicon_handle h,
|
||||
const char *server_ca_cert_path,
|
||||
evhtp_ssl_cfg_t *ssl_config)
|
||||
cx_get_ssl_client_ca_certs(clicon_handle h,
|
||||
const char *server_ca_cert_path,
|
||||
evhtp_ssl_cfg_t *ssl_config)
|
||||
{
|
||||
int retval = -1;
|
||||
struct stat f_stat;
|
||||
|
|
@ -643,7 +651,7 @@ static int
|
|||
cx_verify_certs(int pre_verify,
|
||||
evhtp_x509_store_ctx_t *store)
|
||||
{
|
||||
#ifdef NOTYET
|
||||
#if 0 //def NOTYET
|
||||
char buf[256];
|
||||
X509 * err_cert;
|
||||
int err;
|
||||
|
|
@ -667,6 +675,88 @@ cx_verify_certs(int pre_verify,
|
|||
return pre_verify;
|
||||
}
|
||||
|
||||
/*!
|
||||
*
|
||||
* @param[out] addr Address as string, eg "0.0.0.0", "::"
|
||||
* @param[in] addrtype One of inet:ipv4-address or inet:ipv6-address
|
||||
* @param[out] ss Server socket (bound for accept)
|
||||
*/
|
||||
static int
|
||||
restconf_socket_init(clicon_handle h,
|
||||
const char *addr,
|
||||
const char *addrtype,
|
||||
uint16_t port,
|
||||
int *ss)
|
||||
{
|
||||
int retval = -1;
|
||||
int s = -1;
|
||||
struct sockaddr * sa;
|
||||
struct sockaddr_in6 sin6 = { 0 };
|
||||
struct sockaddr_in sin = { 0 };
|
||||
size_t sin_len;
|
||||
int on = 1;
|
||||
|
||||
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
||||
sin_len = sizeof(struct sockaddr_in6);
|
||||
sin6.sin6_port = htons(port);
|
||||
sin6.sin6_family = AF_INET6;
|
||||
|
||||
evutil_inet_pton(AF_INET6, addr, &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(addr);
|
||||
|
||||
sa = (struct sockaddr *)&sin;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
|
||||
return -1;
|
||||
}
|
||||
/* create inet socket */
|
||||
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
||||
clicon_err(OE_UNIX, errno, "socket");
|
||||
goto done;
|
||||
}
|
||||
// evutil_make_socket_closeonexec(s); // XXX
|
||||
// evutil_make_socket_nonblocking(s); // XXX
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
|
||||
clicon_err(OE_UNIX, errno, "setsockopt SO_KEEPALIVE");
|
||||
goto done;
|
||||
}
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
|
||||
clicon_err(OE_UNIX, errno, "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) {
|
||||
clicon_err(OE_UNIX, errno, "setsockopt IPPROTO_IPV6");
|
||||
goto done;
|
||||
}
|
||||
if (bind(s, sa, sin_len) == -1) {
|
||||
clicon_err(OE_UNIX, errno, "bind port %u", port);
|
||||
goto done;
|
||||
}
|
||||
if (listen(s, SOCKET_LISTEN_BACKLOG) < 0){
|
||||
clicon_err(OE_UNIX, errno, "listen");
|
||||
goto done;
|
||||
}
|
||||
if (ss)
|
||||
*ss = s;
|
||||
retval = 0;
|
||||
done:
|
||||
if (retval != 0 && s != -1)
|
||||
evutil_closesocket(s);
|
||||
return retval;
|
||||
// return evhtp_bind_sockaddr(htp, sa, sin_len, SOCKET_LISTEN_BACKLOG);
|
||||
}
|
||||
|
||||
|
||||
/*! Usage help routine
|
||||
* @param[in] argv0 command line
|
||||
* @param[in] h Clicon handle
|
||||
|
|
@ -701,31 +791,35 @@ usage(clicon_handle h,
|
|||
exit(0);
|
||||
}
|
||||
|
||||
/*! Main routine for libevhtp restconf
|
||||
*/
|
||||
/*! Phase 2 of start per-socket config
|
||||
/*! Extract socket info from backend config
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xs socket config
|
||||
* @param[in] nsc Namespace context
|
||||
* @param[out] namespace
|
||||
* @param[out] address
|
||||
* @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
|
||||
*/
|
||||
static int
|
||||
cx_evhtp_socket(clicon_handle h,
|
||||
cxobj *xs,
|
||||
cvec *nsc,
|
||||
char **namespace,
|
||||
char **address,
|
||||
uint16_t *port,
|
||||
uint16_t *ssl)
|
||||
cx_evhtp_socket_extract(clicon_handle h,
|
||||
cxobj *xs,
|
||||
cvec *nsc,
|
||||
char **namespace,
|
||||
char **address,
|
||||
char **addrtype,
|
||||
uint16_t *port,
|
||||
uint16_t *ssl)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x;
|
||||
char *str = NULL;
|
||||
char *reason = NULL;
|
||||
int ret;
|
||||
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");
|
||||
|
|
@ -736,7 +830,35 @@ cx_evhtp_socket(clicon_handle h,
|
|||
clicon_err(OE_XML, EINVAL, "Mandatory address not given");
|
||||
goto done;
|
||||
}
|
||||
*address = xml_body(x);
|
||||
/* 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;
|
||||
*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){
|
||||
|
|
@ -767,7 +889,110 @@ cx_evhtp_socket(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Phase 2 of evhtp init, config has been retrieved from backend
|
||||
static int
|
||||
cx_htp_add(cx_evhtp_handle *eh,
|
||||
evhtp_t *htp)
|
||||
{
|
||||
eh->eh_htplen++;
|
||||
if ((eh->eh_htpvec = realloc(eh->eh_htpvec, eh->eh_htplen*sizeof(htp))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "realloc");
|
||||
return -1;
|
||||
}
|
||||
eh->eh_htpvec[eh->eh_htplen-1] = htp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Phase 2 of backend evhtp init, config single socket
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] eh Evhtp handle
|
||||
* @param[in] ssl_enable Server is SSL enabled
|
||||
* @param[in] xs XML config of single restconf socket
|
||||
* @param[in] nsc Namespace context
|
||||
*/
|
||||
static int
|
||||
cx_evhtp_socket(clicon_handle h,
|
||||
cx_evhtp_handle *eh,
|
||||
int ssl_enable,
|
||||
cxobj *xs,
|
||||
cvec *nsc,
|
||||
char *server_cert_path,
|
||||
char *server_key_path,
|
||||
char *server_ca_cert_path,
|
||||
int auth_type_client_certificate)
|
||||
{
|
||||
int retval = -1;
|
||||
char *namespace = NULL;
|
||||
char *address = NULL;
|
||||
char *addrtype = NULL;
|
||||
uint16_t ssl = 0;
|
||||
uint16_t port = 0;
|
||||
int ss;
|
||||
evhtp_t *htp = NULL;
|
||||
|
||||
/* This is socket create a new evhtp_t instance */
|
||||
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
|
||||
#ifndef EVHTP_DISABLE_EVTHR /* threads */
|
||||
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||
#endif
|
||||
/* Callback before the connection is accepted. */
|
||||
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||
/* Callback right after a connection is accepted. */
|
||||
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Generic callback called if no other callbacks are matched */
|
||||
evhtp_set_gencb(htp, cx_gencb, h);
|
||||
|
||||
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
||||
if (cx_evhtp_socket_extract(h, xs, nsc, &namespace, &address, &addrtype, &port, &ssl) < 0)
|
||||
goto done;
|
||||
/* Sanity checks of socket parameters */
|
||||
if (ssl){
|
||||
if (ssl_enable == 0 || server_cert_path==NULL || server_key_path == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "Enabled SSL server requires server_cert_path and server_key_path");
|
||||
goto done;
|
||||
}
|
||||
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
|
||||
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Open restconf socket and bind */
|
||||
if (restconf_socket_init(h, address, addrtype, port, &ss) < 0)
|
||||
goto done;
|
||||
/* ss is a server socket that the clients connect to. The callback
|
||||
therefore accepts clients on ss */
|
||||
/* XXX address in evhtp should be prefixed with eg "ipv4:" */
|
||||
evutil_make_socket_closeonexec(ss); // XXX
|
||||
evutil_make_socket_nonblocking(ss); // XXX
|
||||
if (evhtp_accept_socket(htp, ss, SOCKET_LISTEN_BACKLOG) < 0) {
|
||||
/* accept_socket() does not close the descriptor
|
||||
* on error, but this function does.
|
||||
*/
|
||||
evutil_closesocket(ss);
|
||||
goto done;
|
||||
}
|
||||
if (cx_htp_add(eh, htp) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Phase 2 of backend evhtp init, config has been retrieved from backend
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xconfig XML config
|
||||
* @param[in] nsc Namespace context
|
||||
|
|
@ -780,30 +1005,29 @@ cx_evhtp_init(clicon_handle h,
|
|||
cxobj *xconfig,
|
||||
cvec *nsc,
|
||||
cx_evhtp_handle *eh)
|
||||
|
||||
{
|
||||
int retval = -1;
|
||||
int auth_type_client_certificate = 0;
|
||||
uint16_t port = 0;
|
||||
cxobj *xrestconf;
|
||||
cxobj **vec = NULL;
|
||||
size_t veclen;
|
||||
char *auth_type = NULL;
|
||||
char *server_cert_path = NULL;
|
||||
char *server_key_path = NULL;
|
||||
char *server_ca_cert_path = NULL;
|
||||
int retval = -1;
|
||||
cxobj *xrestconf;
|
||||
cxobj **vec = NULL;
|
||||
size_t veclen;
|
||||
char *server_cert_path = NULL;
|
||||
char *server_key_path = NULL;
|
||||
char *server_ca_cert_path = NULL;
|
||||
char *auth_type = NULL;
|
||||
int auth_type_client_certificate = 0;
|
||||
//XXX char *client_cert_ca = NULL;
|
||||
cxobj *x;
|
||||
char *namespace = NULL;
|
||||
char *address = NULL;
|
||||
uint16_t use_ssl_server = 0;
|
||||
|
||||
cxobj *x;
|
||||
int i;
|
||||
int ssl_enable = 0;
|
||||
|
||||
/* Extract socket fields from xconfig */
|
||||
if ((xrestconf = xpath_first(xconfig, nsc, "restconf")) == NULL){
|
||||
clicon_err(OE_CFG, ENOENT, "restconf top symbol not found");
|
||||
goto done;
|
||||
}
|
||||
/* get common fields */
|
||||
if ((x = xpath_first(xrestconf, nsc, "ssl-enable")) != NULL)
|
||||
ssl_enable = (strcmp(xml_body(x),"true")==0);
|
||||
if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) /* XXX: leaf-list? */
|
||||
auth_type = xml_body(x);
|
||||
if (auth_type && strcmp(auth_type, "client-certificate") == 0)
|
||||
|
|
@ -814,42 +1038,9 @@ cx_evhtp_init(clicon_handle h,
|
|||
server_key_path = xml_body(x);
|
||||
if ((x = xpath_first(xrestconf, nsc, "server-ca-cert-path")) != NULL)
|
||||
server_ca_cert_path = xml_body(x);
|
||||
/* get the list of socket config-data */
|
||||
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
|
||||
goto done;
|
||||
/* Accept only a single socket XXX */
|
||||
if (veclen != 1){
|
||||
clicon_err(OE_XML, EINVAL, "Only single socket supported"); /* XXX warning: accept more? */
|
||||
goto done;
|
||||
}
|
||||
if (cx_evhtp_socket(h, vec[0], nsc, &namespace, &address, &port, &use_ssl_server) < 0)
|
||||
goto done;
|
||||
if (use_ssl_server &&
|
||||
(server_cert_path==NULL || server_key_path == NULL)){
|
||||
clicon_err(OE_XML, EINVAL, "Enabled SSL server requires server_cert_path and server_key_path");
|
||||
goto done;
|
||||
}
|
||||
if (auth_type_client_certificate){
|
||||
if (!use_ssl_server){
|
||||
clicon_err(OE_XML, EINVAL, "Client certificate authentication type requires SSL");
|
||||
goto done;
|
||||
}
|
||||
if (server_ca_cert_path == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "Client certificate authentication type requires server-ca-cert-path");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Init evhtp */
|
||||
if ((eh->eh_evbase = event_base_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||
goto done;
|
||||
}
|
||||
/* create a new evhtp_t instance */
|
||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
if (use_ssl_server){
|
||||
|
||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||
if (ssl_enable){
|
||||
/* Init evhtp ssl config struct */
|
||||
if ((eh->eh_ssl_config = malloc(sizeof(evhtp_ssl_cfg_t))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
|
|
@ -858,12 +1049,12 @@ cx_evhtp_init(clicon_handle h,
|
|||
memset(eh->eh_ssl_config, 0, sizeof(evhtp_ssl_cfg_t));
|
||||
eh->eh_ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
|
||||
|
||||
if (cx_get_ssl_server_certs(h, server_cert_path,
|
||||
server_key_path,
|
||||
eh->eh_ssl_config) < 0)
|
||||
/* Read server ssl files cert and key */
|
||||
if (cx_get_ssl_server_certs(h, server_cert_path, server_key_path, eh->eh_ssl_config) < 0)
|
||||
goto done;
|
||||
/* If client auth get client CA cert */
|
||||
if (auth_type_client_certificate)
|
||||
if (cx_get_ssl_client_certs(h, server_ca_cert_path, eh->eh_ssl_config) < 0)
|
||||
if (cx_get_ssl_client_ca_certs(h, server_ca_cert_path, eh->eh_ssl_config) < 0)
|
||||
goto done;
|
||||
eh->eh_ssl_config->x509_verify_cb = cx_verify_certs; /* Is extra verification necessary? */
|
||||
if (auth_type_client_certificate){
|
||||
|
|
@ -873,52 +1064,15 @@ cx_evhtp_init(clicon_handle h,
|
|||
}
|
||||
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
|
||||
}
|
||||
assert(SSL_VERIFY_NONE == 0);
|
||||
|
||||
/* Init evhtp */
|
||||
if ((eh->eh_evbase = event_base_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||
goto done;
|
||||
}
|
||||
/* create a new evhtp_t instance */
|
||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||
if (use_ssl_server){
|
||||
if (evhtp_ssl_init(eh->eh_htp, eh->eh_ssl_config) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#ifndef EVHTP_DISABLE_EVTHR /* threads */
|
||||
evhtp_use_threads_wexit(eh->eh_htp, NULL, NULL, 4, NULL);
|
||||
#endif
|
||||
/* Callback before the connection is accepted. */
|
||||
evhtp_set_pre_accept_cb(eh->eh_htp, cx_pre_accept, h);
|
||||
/* Callback right after a connection is accepted. */
|
||||
evhtp_set_post_accept_cb(eh->eh_htp, cx_post_accept, h);
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(eh->eh_htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(eh->eh_htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Generic callback called if no other callbacks are matched */
|
||||
evhtp_set_gencb(eh->eh_htp, cx_gencb, h);
|
||||
|
||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
||||
address, /* string address, eg ipv4:<ipv4addr> */
|
||||
port, /* port */
|
||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
||||
) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_bind_socket");
|
||||
/* get the list of socket config-data */
|
||||
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
|
||||
goto done;
|
||||
for (i=0; i<veclen; i++){
|
||||
if (cx_evhtp_socket(h, eh, ssl_enable, vec[i], nsc,
|
||||
server_cert_path, server_key_path, server_ca_cert_path,
|
||||
auth_type_client_certificate) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -929,10 +1083,11 @@ cx_evhtp_init(clicon_handle h,
|
|||
|
||||
/*! Read config from backend */
|
||||
int
|
||||
restconf_config_backend(clicon_handle h,
|
||||
int argc,
|
||||
char **argv,
|
||||
int drop_privileges)
|
||||
restconf_config_backend(clicon_handle h,
|
||||
cx_evhtp_handle *eh,
|
||||
int argc,
|
||||
char **argv,
|
||||
int drop_privileges)
|
||||
{
|
||||
int retval = -1;
|
||||
char *argv0 = argv[0];
|
||||
|
|
@ -945,8 +1100,8 @@ restconf_config_backend(clicon_handle h,
|
|||
size_t cligen_bufthreshold;
|
||||
cvec *nsc = NULL;
|
||||
cxobj *xconfig = NULL;
|
||||
cxobj *xerr = NULL;
|
||||
uint32_t id = 0; /* Session id, to poll backend up */
|
||||
cx_evhtp_handle *eh = NULL;
|
||||
|
||||
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
||||
xml_nsctx_namespace_netconf_default(h);
|
||||
|
|
@ -1052,13 +1207,16 @@ restconf_config_backend(clicon_handle h,
|
|||
goto done;
|
||||
if (clicon_rpc_get_config(h, NULL, "running", "/restconf", nsc, &xconfig) < 0)
|
||||
goto done;
|
||||
/* Initialize evhtp handle - fill it in cx_evhtp_init */
|
||||
if ((eh = malloc(sizeof *eh)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
if ((xerr = xpath_first(xconfig, NULL, "/rpc-error")) != NULL){
|
||||
clixon_netconf_error(xerr, "Get backend restconf config", NULL);
|
||||
goto done;
|
||||
}
|
||||
memset(eh, 0, sizeof *eh);
|
||||
_EVHTP_HANDLE = eh; /* global */
|
||||
/* Init evhtp, common stuff */
|
||||
if ((eh->eh_evbase = event_base_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (cx_evhtp_init(h, xconfig, nsc, eh) < 0)
|
||||
goto done;
|
||||
/* Drop privileges after evhtp and server key/cert read */
|
||||
|
|
@ -1069,7 +1227,6 @@ restconf_config_backend(clicon_handle h,
|
|||
}
|
||||
/* libevent main loop */
|
||||
event_base_loop(eh->eh_evbase, 0); /* XXX: replace with clixon_event_loop() */
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
if (xconfig)
|
||||
|
|
@ -1083,6 +1240,7 @@ restconf_config_backend(clicon_handle h,
|
|||
/*! Read config locally */
|
||||
int
|
||||
restconf_config_local(clicon_handle h,
|
||||
cx_evhtp_handle *eh,
|
||||
int argc,
|
||||
char **argv,
|
||||
uint16_t port,
|
||||
|
|
@ -1101,7 +1259,7 @@ restconf_config_local(clicon_handle h,
|
|||
size_t cligen_bufthreshold;
|
||||
char *restconf_ipv4_addr = NULL;
|
||||
char *restconf_ipv6_addr = NULL;
|
||||
cx_evhtp_handle *eh = NULL;
|
||||
evhtp_t *htp;
|
||||
|
||||
/* port = defaultport unless explicitly set -P */
|
||||
if (port == 0){
|
||||
|
|
@ -1111,13 +1269,6 @@ restconf_config_local(clicon_handle h,
|
|||
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
|
||||
xml_nsctx_namespace_netconf_default(h);
|
||||
|
||||
/* Initialize evhtp handle */
|
||||
if ((eh = malloc(sizeof *eh)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(eh, 0, sizeof *eh);
|
||||
_EVHTP_HANDLE = eh; /* global */
|
||||
/* Check server ssl certs */
|
||||
if (use_ssl){
|
||||
/* Init evhtp ssl config struct */
|
||||
|
|
@ -1148,39 +1299,6 @@ restconf_config_local(clicon_handle h,
|
|||
clicon_err(OE_UNIX, errno, "event_base_new");
|
||||
goto done;
|
||||
}
|
||||
/* create a new evhtp_t instance */
|
||||
if ((eh->eh_htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||
if (use_ssl){
|
||||
if (evhtp_ssl_init(eh->eh_htp, eh->eh_ssl_config) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#ifndef EVHTP_DISABLE_EVTHR
|
||||
evhtp_use_threads_wexit(eh->eh_htp, NULL, NULL, 4, NULL);
|
||||
#endif
|
||||
/* Callback before the connection is accepted. */
|
||||
evhtp_set_pre_accept_cb(eh->eh_htp, cx_pre_accept, h);
|
||||
|
||||
/* Callback right after a connection is accepted. */
|
||||
evhtp_set_post_accept_cb(eh->eh_htp, cx_post_accept, h);
|
||||
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(eh->eh_htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(eh->eh_htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Generic callback called if no other callbacks are matched */
|
||||
evhtp_set_gencb(eh->eh_htp, cx_gencb, h);
|
||||
|
||||
/* bind to a socket, optionally with specific protocol support formatting
|
||||
*/
|
||||
|
|
@ -1193,12 +1311,47 @@ restconf_config_local(clicon_handle h,
|
|||
}
|
||||
if (restconf_ipv4_addr != NULL && strlen(restconf_ipv4_addr)){
|
||||
cbuf *cb;
|
||||
|
||||
/* create a new evhtp_t instance */
|
||||
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||
if (use_ssl){
|
||||
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#ifndef EVHTP_DISABLE_EVTHR
|
||||
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||
#endif
|
||||
/* Callback before the connection is accepted. */
|
||||
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||
|
||||
/* Callback right after a connection is accepted. */
|
||||
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Generic callback called if no other callbacks are matched */
|
||||
evhtp_set_gencb(htp, cx_gencb, h);
|
||||
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "ipv4:%s", restconf_ipv4_addr);
|
||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
||||
if (evhtp_bind_socket(htp, /* evhtp handle */
|
||||
cbuf_get(cb), /* string address, eg ipv4:<ipv4addr> */
|
||||
port, /* port */
|
||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
||||
|
|
@ -1208,16 +1361,51 @@ restconf_config_local(clicon_handle h,
|
|||
}
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (cx_htp_add(eh, htp) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Eeh can only bind one */
|
||||
if (0 && restconf_ipv6_addr != NULL && strlen(restconf_ipv6_addr)){
|
||||
if (restconf_ipv6_addr != NULL && strlen(restconf_ipv6_addr)){
|
||||
cbuf *cb;
|
||||
/* create a new evhtp_t instance */
|
||||
if ((htp = evhtp_new(eh->eh_evbase, NULL)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
/* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */
|
||||
if (use_ssl){
|
||||
if (evhtp_ssl_init(htp, eh->eh_ssl_config) < 0){
|
||||
clicon_err(OE_UNIX, errno, "evhtp_new");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#ifndef EVHTP_DISABLE_EVTHR
|
||||
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
|
||||
#endif
|
||||
/* Callback before the connection is accepted. */
|
||||
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
|
||||
|
||||
/* Callback right after a connection is accepted. */
|
||||
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
|
||||
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Callback to be executed for all /restconf api calls */
|
||||
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
|
||||
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
|
||||
goto done;
|
||||
}
|
||||
/* Generic callback called if no other callbacks are matched */
|
||||
evhtp_set_gencb(htp, cx_gencb, h);
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "ipv6:%s", restconf_ipv6_addr);
|
||||
if (evhtp_bind_socket(eh->eh_htp, /* evhtp handle */
|
||||
if (evhtp_bind_socket(htp, /* evhtp handle */
|
||||
cbuf_get(cb), /* string address, eg ipv6:<ipv6addr> */
|
||||
port, /* port */
|
||||
SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */
|
||||
|
|
@ -1227,6 +1415,8 @@ restconf_config_local(clicon_handle h,
|
|||
}
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (cx_htp_add(eh, htp) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (drop_privileges){
|
||||
|
|
@ -1329,7 +1519,7 @@ restconf_config_local(clicon_handle h,
|
|||
clicon_debug(1, "restconf_main_evhtp done");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc,
|
||||
char **argv)
|
||||
|
|
@ -1491,10 +1681,16 @@ main(int argc,
|
|||
/* port = defaultport unless explicitly set -P */
|
||||
if (port == 0)
|
||||
port = defaultport;
|
||||
if ((eh = malloc(sizeof *eh)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(eh, 0, sizeof *eh);
|
||||
_EVHTP_HANDLE = eh; /* global */
|
||||
|
||||
if (clicon_option_bool(h, "CLICON_RESTCONF_CONFIG") == 0){
|
||||
/* Read config locally */
|
||||
if (restconf_config_local(h, argc, argv,
|
||||
if (restconf_config_local(h, eh, argc, argv,
|
||||
port,
|
||||
ssl_verify_clients,
|
||||
use_ssl,
|
||||
|
|
@ -1504,12 +1700,10 @@ main(int argc,
|
|||
}
|
||||
else {
|
||||
/* Read config from backend */
|
||||
if (restconf_config_backend(h, argc, argv, drop_privileges) < 0)
|
||||
if (restconf_config_backend(h, eh, argc, argv, drop_privileges) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
event_base_loop(eh->eh_evbase, 0);
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "restconf_main_evhtp done");
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
#
|
||||
# Build, compile and test a clixon-system container
|
||||
# This is used in CI
|
||||
# NOTE: restconf config is controlled at install time with ./configure --with-restconf=... option
|
||||
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ int yang2cv_type(char *ytype, enum cv_type *cv_type);
|
|||
char *cv2yang_type(enum cv_type cv_type);
|
||||
yang_stmt *yang_find_identity(yang_stmt *ys, char *identity);
|
||||
yang_stmt *yang_find_identity_nsc(yang_stmt *yspec, char *identity, cvec *nsc);
|
||||
int ys_cv_validate(clicon_handle h, cg_var *cv, yang_stmt *ys, char **reason);
|
||||
int ys_cv_validate(clicon_handle h, cg_var *cv, yang_stmt *ys, yang_stmt **ysub, char **reason);
|
||||
int clicon_type2cv(char *type, char *rtype, yang_stmt *ys, enum cv_type *cvtype);
|
||||
int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype,
|
||||
int *options, cvec **cvv,
|
||||
|
|
|
|||
|
|
@ -1024,7 +1024,7 @@ xml_yang_validate_add(clicon_handle h,
|
|||
goto fail;
|
||||
}
|
||||
}
|
||||
if ((ret = ys_cv_validate(h, cv, yt, &reason)) < 0)
|
||||
if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ compile_pattern2regexp(clicon_handle h,
|
|||
/*! Resolve types: populate type caches
|
||||
* @param[in] ys This is a type statement
|
||||
* @param[in] arg Not used
|
||||
* Typically only called once when loading te yang type system.
|
||||
* Typically only called once when loading the yang type system.
|
||||
* @note unions not cached
|
||||
*/
|
||||
int
|
||||
|
|
@ -741,7 +741,7 @@ cv_validate1(clicon_handle h,
|
|||
|
||||
/* Forward */
|
||||
static int ys_cv_validate_union(clicon_handle h,yang_stmt *ys, char **reason,
|
||||
yang_stmt *yrestype, char *type, char *val);
|
||||
yang_stmt *yrestype, char *type, char *val, yang_stmt **ysubp);
|
||||
|
||||
/*!
|
||||
* @param[out] reason If given and return val is 0, contains a malloced string
|
||||
|
|
@ -767,6 +767,7 @@ ys_cv_validate_union_one(clicon_handle h,
|
|||
char *restype;
|
||||
enum cv_type cvtype;
|
||||
cg_var *cvt=NULL;
|
||||
yang_stmt *ysubt = NULL;
|
||||
|
||||
if ((regexps = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvec_new");
|
||||
|
|
@ -781,7 +782,7 @@ ys_cv_validate_union_one(clicon_handle h,
|
|||
goto done;
|
||||
restype = yrt?yang_argument_get(yrt):NULL;
|
||||
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
|
||||
if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val)) < 0)
|
||||
if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val, &ysubt)) < 0)
|
||||
goto done;
|
||||
}
|
||||
else {
|
||||
|
|
@ -828,18 +829,23 @@ ys_cv_validate_union_one(clicon_handle h,
|
|||
}
|
||||
|
||||
/*! Validate union
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] ys Yang statement (union)
|
||||
* @param[out] reason If given, and return value is 0, contains malloced string
|
||||
* @param[in] val Value to match
|
||||
* @param[out] ysubp Sub-type of ys that matches val
|
||||
* @retval -1 Error (fatal), with errno set to indicate error
|
||||
* @retval 0 Validation not OK, malloced reason is returned. Free reason with free()
|
||||
* @retval 1 Validation OK
|
||||
*/
|
||||
static int
|
||||
ys_cv_validate_union(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
char **reason,
|
||||
yang_stmt *yrestype,
|
||||
char *type, /* orig type */
|
||||
char *val)
|
||||
yang_stmt *ys,
|
||||
char **reason,
|
||||
yang_stmt *yrestype,
|
||||
char *type, /* orig type */
|
||||
char *val,
|
||||
yang_stmt **ysubp)
|
||||
{
|
||||
int retval = 1; /* valid */
|
||||
yang_stmt *yt = NULL;
|
||||
|
|
@ -859,8 +865,13 @@ ys_cv_validate_union(clicon_handle h,
|
|||
reason1 = *reason;
|
||||
*reason = NULL;
|
||||
}
|
||||
if (retval == 1) /* Enough that one type validates value */
|
||||
/* Enough that one type validates value, return that value
|
||||
*/
|
||||
if (retval == 1) {
|
||||
if (ysubp)
|
||||
*ysubp = yt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (retval == 0 && reason1){
|
||||
|
|
@ -877,6 +888,7 @@ ys_cv_validate_union(clicon_handle h,
|
|||
* @param[in] h Clicon handle
|
||||
* @param[in] cv A cligen variable to validate. This is a correctly parsed cv.
|
||||
* @param[in] ys A yang statement, must be leaf or leaf-list.
|
||||
* @param[out] ysub Sub-type that matches val (in case of union, otherwise ys)
|
||||
* @param[out] reason If given, and if return value is 0, contains malloced
|
||||
* string describing reason why validation failed.
|
||||
* @retval -1 Error (fatal), with errno set to indicate error
|
||||
|
|
@ -889,6 +901,7 @@ int
|
|||
ys_cv_validate(clicon_handle h,
|
||||
cg_var *cv,
|
||||
yang_stmt *ys,
|
||||
yang_stmt **ysub,
|
||||
char **reason)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -948,7 +961,7 @@ ys_cv_validate(clicon_handle h,
|
|||
*/
|
||||
if ((val = cv_string_get(cv)) == NULL)
|
||||
val = "";
|
||||
if ((retval2 = ys_cv_validate_union(h, ys, reason, yrestype, origtype, val)) < 0)
|
||||
if ((retval2 = ys_cv_validate_union(h, ys, reason, yrestype, origtype, val, ysub)) < 0)
|
||||
goto done;
|
||||
retval = retval2; /* invalid (0) with latest reason or valid 1 */
|
||||
}
|
||||
|
|
@ -969,6 +982,8 @@ ys_cv_validate(clicon_handle h,
|
|||
if ((retval = cv_validate1(h, cv, cvtype, options, cvv,
|
||||
regexps, yrestype, restype, reason)) < 0)
|
||||
goto done;
|
||||
if (ysub)
|
||||
*ysub = ys;
|
||||
}
|
||||
done:
|
||||
if (origtype)
|
||||
|
|
|
|||
67
test/certs.sh
Normal file
67
test/certs.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env bash
|
||||
# Create server certs
|
||||
# Assume: the following variables set:
|
||||
# $dir, $certdir, $srvkey, $srvcert, $cakey, $cacert
|
||||
# and that $certdir exists
|
||||
|
||||
# 1. CA
|
||||
cat<<EOF > $dir/ca.cnf
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
serial = ca-serial
|
||||
crl = ca-crl.pem
|
||||
database = ca-database.txt
|
||||
name_opt = CA_default
|
||||
cert_opt = CA_default
|
||||
default_crl_days = 9999
|
||||
default_md = md5
|
||||
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
days = 1
|
||||
distinguished_name = req_distinguished_name
|
||||
attributes = req_attributes
|
||||
prompt = no
|
||||
output_password = password
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = SE
|
||||
L = Stockholm
|
||||
O = Clixon
|
||||
OU = clixon
|
||||
CN = ca
|
||||
emailAddress = olof@hagsand.se
|
||||
|
||||
[ req_attributes ]
|
||||
challengePassword = test
|
||||
|
||||
EOF
|
||||
|
||||
# Generate CA cert
|
||||
openssl req -x509 -days 1 -config $dir/ca.cnf -keyout $cakey -out $cacert
|
||||
|
||||
cat<<EOF > $dir/srv.cnf
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
req_extensions = ext
|
||||
[dn]
|
||||
CN = www.clicon.org # localhost
|
||||
emailAddress = olof@hagsand.se
|
||||
O = Clixon
|
||||
L = Stockholm
|
||||
C = SE
|
||||
[ext]
|
||||
subjectAltName = DNS:clicon.org
|
||||
EOF
|
||||
|
||||
# Generate server key
|
||||
openssl genrsa -out $srvkey 2048
|
||||
|
||||
# Generate CSR (signing request)
|
||||
openssl req -new -config $dir/srv.cnf -key $srvkey -out $certdir/srv_csr.pem
|
||||
|
||||
# Sign server cert by CA
|
||||
openssl x509 -req -extfile $dir/srv.cnf -days 1 -passin "pass:password" -in $certdir/srv_csr.pem -CA $cacert -CAkey $cakey -CAcreateserial -out $srvcert
|
||||
|
|
@ -42,6 +42,7 @@ if [ -f ./config.sh ]; then
|
|||
fi
|
||||
|
||||
# Sanity nginx running on systemd platforms
|
||||
# ./lib.sh: line 45: systemctl: command not found
|
||||
if systemctl > /dev/null; then
|
||||
nginxactive=$(systemctl show nginx |grep ActiveState=active)
|
||||
if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
||||
|
|
@ -273,8 +274,8 @@ wait_backend(){
|
|||
# @see wait_restconf
|
||||
start_restconf(){
|
||||
# Start in background
|
||||
if [ $RCPROTO = https ]; then
|
||||
EXTRA="-s" # server certs
|
||||
if [ $RCPROTO = https -a "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||
EXTRA="-s" # server certs ONLY evhtp
|
||||
else
|
||||
EXTRA=
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
# Restconf basic functionality
|
||||
# also uri encoding using eth/0/0
|
||||
# Assume http server setup, such as nginx described in apps/restconf/README.md
|
||||
# Restconf basic functionality also uri encoding using eth/0/0
|
||||
# Note there are many variants: (1)fcgi/evhtp, (2) http/https, (3) IPv4/IPv6, (4)local or backend-config
|
||||
# (1) fcgi/evhtp
|
||||
# This is compile-time --with-restconf=fcgi or evhtp, so either or
|
||||
# - fcgi: Assume http server setup, such as nginx described in apps/restconf/README.md
|
||||
# - evhtp: test both local config and get config from backend
|
||||
# (2) http/https
|
||||
# - fcgi: relies on nginx has https setup
|
||||
# - evhtp: generate self-signed server certs
|
||||
# (3) IPv4/IPv6 (only loopback 127.0.0.1 / ::1)
|
||||
# - The tests runs through both
|
||||
# (4) local/backend config. Evhtp only
|
||||
# - The tests runs through both (if compiled with evhtp)
|
||||
# See also test_restconf2.sh
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
|
@ -29,24 +40,62 @@ cat <<EOF > $cfg
|
|||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
|
||||
EOF
|
||||
|
||||
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||
# Create server certs
|
||||
certdir=$dir/certs
|
||||
srvkey=$certdir/srv_key.pem
|
||||
srvcert=$certdir/srv_cert.pem
|
||||
cakey=$certdir/ca_key.pem # needed?
|
||||
cacert=$certdir/ca_cert.pem
|
||||
test -d $certdir || mkdir $certdir
|
||||
. ./certs.sh
|
||||
cat <<EOF >> $cfg
|
||||
<CLICON_SSL_SERVER_CERT>$srvcert</CLICON_SSL_SERVER_CERT>
|
||||
<CLICON_SSL_SERVER_KEY>$srvkey</CLICON_SSL_SERVER_KEY>
|
||||
<CLICON_SSL_CA_CERT>$srvcert</CLICON_SSL_CA_CERT>
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF >> $cfg
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
|
||||
state='{"clixon-example:state":{"op":\["41","42","43"\]}'
|
||||
|
||||
if [ ${RCPROTO} = "https" ]; then
|
||||
ssl=true
|
||||
port=443
|
||||
else
|
||||
ssl=false
|
||||
port=80
|
||||
fi
|
||||
# For backend config, create 4 sockets, all combinations IPv4/IPv6 + http/https
|
||||
RESTCONFCONFIG=$(cat <<EOF
|
||||
<restconf xmlns="https://clicon.org/restconf">
|
||||
<ssl-enable>true</ssl-enable>
|
||||
<auth-type>password</auth-type>
|
||||
<server-cert-path>$srvcert</server-cert-path>
|
||||
<server-key-path>$srvkey</server-key-path>
|
||||
<server-ca-cert-path>$cakey</server-ca-cert-path>
|
||||
<socket><namespace>default</namespace><address>0.0.0.0</address><port>80</port><ssl>false</ssl></socket>
|
||||
<socket><namespace>default</namespace><address>::</address><port>80</port><ssl>false</ssl></socket>
|
||||
<socket><namespace>default</namespace><address>0.0.0.0</address><port>443</port><ssl>true</ssl></socket>
|
||||
<socket><namespace>default</namespace><address>::</address><port>443</port><ssl>true</ssl></socket>
|
||||
</restconf>
|
||||
EOF
|
||||
)
|
||||
|
||||
# Restconf test routine with arguments:
|
||||
# 1. proto:http/https
|
||||
# 2: addr: 127.0.0.1/::1 # IPv4 or IPv6
|
||||
# 3: config: local / backend config (evhtp only)
|
||||
testrun()
|
||||
{
|
||||
USEBACKEND=$1
|
||||
|
||||
proto=$1 # http/https
|
||||
addr=$2 # 127.0.0.1/::1
|
||||
config=$3 # local/backend
|
||||
|
||||
RCPROTO=$proto # for start/wait of restconf
|
||||
echo "proto:$proto"
|
||||
echo "addr:$addr"
|
||||
echo "config:$config"
|
||||
|
||||
new "test params: -f $cfg -- -s"
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
|
|
@ -63,10 +112,10 @@ testrun()
|
|||
new "wait backend"
|
||||
wait_backend
|
||||
|
||||
if $USEBACKEND; then
|
||||
if [ $config = backend ] ; then # Create a backend config
|
||||
# restconf backend config
|
||||
new "netconf edit config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><restconf xmlns=\"https://clicon.org/restconf\"><socket><namespace>default</namespace><address>0.0.0.0</address><port>$port</port><ssl>$ssl</ssl></socket><auth-type>password</auth-type></restconf></config></edit-config></rpc>]]>]]>"
|
||||
"^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config>$RESTCONFCONFIG</config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf commit"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
@ -76,7 +125,7 @@ testrun()
|
|||
new "kill old restconf daemon"
|
||||
stop_restconf_pre
|
||||
|
||||
if $USEBACKEND; then
|
||||
if [ $config = backend ] ; then # Add -b option
|
||||
new "start restconf daemon -b"
|
||||
start_restconf -f $cfg -b
|
||||
else
|
||||
|
|
@ -86,23 +135,23 @@ testrun()
|
|||
fi
|
||||
new "wait restconf"
|
||||
wait_restconf
|
||||
|
||||
|
||||
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||
|
||||
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}'
|
||||
|
||||
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
|
||||
# Get XML instead of JSON?
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2019-01-04</yang-library-version></restconf>'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $proto://$addr/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2019-01-04</yang-library-version></restconf>'
|
||||
|
||||
# Should be alphabetically ordered
|
||||
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]'
|
||||
|
||||
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
|
||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations)
|
||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/operations)
|
||||
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -110,10 +159,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2019-01-04"}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2019-01-04"}'
|
||||
|
||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
|
||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version)
|
||||
ret=$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $proto://$addr/restconf/yang-library-version)
|
||||
expect="<yang-library-version>2019-01-04</yang-library-version>"
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -121,48 +170,48 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
|
||||
|
||||
new "restconf schema resource, mod-state top-level"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-example","revision":"2020-03-11","namespace":"urn:example:clixon","conformance-type":"implement"},{"name":"clixon-lib","revision":"2020-04-23","'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-yang-library:modules-state)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-example","revision":"2020-03-11","namespace":"urn:example:clixon","conformance-type":"implement"},{"name":"clixon-lib","revision":"2020-04-23","'
|
||||
|
||||
new "restconf options. RFC 8040 4.1"
|
||||
expectpart "$(curl $CURLOPTS -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
||||
expectpart "$(curl $CURLOPTS -X OPTIONS $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
|
||||
|
||||
# -I means HEAD
|
||||
new "restconf HEAD. RFC 8040 4.2"
|
||||
expectpart "$(curl $CURLOPTS -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
|
||||
expectpart "$(curl $CURLOPTS -I -H "Accept: application/yang-data+json" $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
|
||||
|
||||
new "restconf empty rpc JSON"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf empty rpc XML"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf empty rpc, default media type should fail"
|
||||
expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||
expectpart "$(curl $CURLOPTS -X POST -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||
|
||||
new "restconf empty rpc, default media type should fail (JSON)"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||
|
||||
new "restconf empty rpc with extra args (should fail)"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $proto://$addr/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
|
||||
|
||||
# Irritiating to get debugs on the terminal
|
||||
#new "restconf debug rpc"
|
||||
#expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
||||
#expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $proto://$addr/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf get empty config + state json"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
|
||||
new "restconf get empty config + state json with wrong module name"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
|
||||
|
||||
#'HTTP/1.1 404 Not Found'
|
||||
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}'
|
||||
|
||||
new "restconf get empty config + state xml"
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state)
|
||||
expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -170,11 +219,11 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf get data type json"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||
|
||||
new "restconf get state operation"
|
||||
# Cant get shell macros to work, inline matching from lib.sh
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)
|
||||
expect='<op xmlns="urn:example:clixon">42</op>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -182,11 +231,11 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf get state operation type json"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}'
|
||||
|
||||
new "restconf get state operation type xml"
|
||||
# Cant get shell macros to work, inline matching from lib.sh
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
|
||||
ret=$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $proto://$addr/restconf/data/clixon-example:state/op=42)
|
||||
expect='<op xmlns="urn:example:clixon">42</op>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -194,88 +243,88 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf GET datastore"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
|
||||
# Exact match
|
||||
new "restconf Add subtree eth/0/0 to datastore using POST"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/ietf-interfaces:interfaces"
|
||||
|
||||
new "restconf Re-add subtree eth/0/0 which should give error"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
|
||||
new "restconf Check interfaces eth/0/0 added"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
|
||||
new "restconf delete interfaces"
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf Check empty config"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state"
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/1.1 200 OK" "$state"
|
||||
|
||||
new "restconf Add interfaces subtree eth/0/0 using POST"
|
||||
expectpart "$(curl $CURLOPTS -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl $CURLOPTS -X POST $proto://$addr/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf Check eth/0/0 added config"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
|
||||
new "restconf Check eth/0/0 GET augmented state level 1"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
|
||||
|
||||
new "restconf Check eth/0/0 GET augmented state level 2"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
|
||||
|
||||
new "restconf Check eth/0/0 added state XXXXXXX"
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
|
||||
|
||||
new "restconf Re-post eth/0/0 which should generate error"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||
|
||||
new "Add leaf description using POST"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "Add nothing using POST (expect fail)"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
||||
|
||||
new "restconf Check description added"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
|
||||
new "restconf delete eth/0/0"
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "Check deleted eth/0/0"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "$state"
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data)" 0 "HTTP/1.1 200 OK" "$state"
|
||||
|
||||
new "restconf Re-Delete eth/0/0 using none should generate error"
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X DELETE $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 409 Conflict" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}'
|
||||
|
||||
new "restconf Add subtree eth/0/0 using PUT"
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf get subtree"
|
||||
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
|
||||
new "restconf rpc using POST json"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 "HTTP/1.1 200 OK" '{"clixon-example:output":{"x":"42","y":"42"}}'
|
||||
|
||||
if ! $YANG_UNKNOWN_ANYDATA ; then
|
||||
new "restconf rpc using POST json wrong"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
|
||||
fi
|
||||
|
||||
new "restconf rpc non-existing rpc without namespace"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||
|
||||
new "restconf rpc non-existing rpc"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
|
||||
|
||||
new "restconf rpc missing name"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
|
||||
|
||||
new "restconf rpc missing input"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{}' $proto://$addr/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
|
||||
|
||||
new "restconf rpc using POST xml"
|
||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)
|
||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example)
|
||||
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -283,10 +332,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf rpc using wrong prefix"
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $proto://$addr/restconf/operations/wrong:example)" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}'
|
||||
|
||||
new "restconf local client rpc using POST xml"
|
||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc)
|
||||
ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $proto://$addr/restconf/operations/clixon-example:client-rpc)
|
||||
expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
|
||||
match=`echo $ret | grep --null -Eo "$expect"`
|
||||
if [ -z "$match" ]; then
|
||||
|
|
@ -294,10 +343,10 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
|
||||
new "restconf Add subtree without key (expected error)"
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
|
||||
|
||||
new "restconf Add subtree with too many keys (expected error)"
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
|
||||
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 "HTTP/1.1 400 Bad Request" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}'
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
|
|
@ -316,10 +365,25 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
|||
fi
|
||||
}
|
||||
|
||||
new "Use local restconf config"
|
||||
testrun false
|
||||
|
||||
new "Get restconf config from backend"
|
||||
testrun true
|
||||
# Go thru all combinations of IPv4/IPv6, http/https, local/backend config
|
||||
protos="http"
|
||||
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||
# http only relevant for evhtp (for fcgi: need nginx config)
|
||||
protos="$protos https"
|
||||
fi
|
||||
for proto in $protos; do
|
||||
for addr in 127.0.0.1 "\[::1\]"; do
|
||||
configs="local"
|
||||
if [ "${WITH_RESTCONF}" = "evhtp" ]; then
|
||||
# backend config retrieval only implemented for evhtp
|
||||
configs="$configs backend"
|
||||
fi
|
||||
echo "configs:$configs"
|
||||
for config in $configs; do
|
||||
new "restconf test: proto:$proto addr:$addr config:$config"
|
||||
testrun $proto $addr $config
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
rm -rf $dir
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
# Restconf basic functionality
|
||||
# Assume http server setup, such as nginx described in apps/restconf/README.md
|
||||
# Systematic tests of restconf operations GET/POST/PUT/DELETE
|
||||
# See also test_restconf.sh
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ fyang=$dir/example.yang
|
|||
|
||||
cfg=$dir/conf.xml
|
||||
|
||||
# Local for test here
|
||||
certdir=$dir/certs
|
||||
srvkey=$certdir/srv_key.pem
|
||||
srvcert=$certdir/srv_cert.pem
|
||||
cakey=$certdir/ca_key.pem # needed?
|
||||
|
||||
cacert=$certdir/ca_cert.pem
|
||||
|
||||
users="andy guest" # generate certs for some users in nacm.sh
|
||||
|
|
@ -117,72 +119,13 @@ EOF
|
|||
)
|
||||
|
||||
if $genkeys; then
|
||||
# Create certs
|
||||
# 1. CA
|
||||
cat<<EOF > $dir/ca.cnf
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
serial = ca-serial
|
||||
crl = ca-crl.pem
|
||||
database = ca-database.txt
|
||||
name_opt = CA_default
|
||||
cert_opt = CA_default
|
||||
default_crl_days = 9999
|
||||
default_md = md5
|
||||
# Server certs
|
||||
. ./certs.sh
|
||||
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
days = 1
|
||||
distinguished_name = req_distinguished_name
|
||||
attributes = req_attributes
|
||||
prompt = no
|
||||
output_password = password
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = SE
|
||||
L = Stockholm
|
||||
O = Clixon
|
||||
OU = clixon
|
||||
CN = ca
|
||||
emailAddress = olof@hagsand.se
|
||||
|
||||
[ req_attributes ]
|
||||
challengePassword = test
|
||||
|
||||
EOF
|
||||
|
||||
# Generate CA cert
|
||||
openssl req -x509 -days 1 -config $dir/ca.cnf -keyout $cakey -out $cacert
|
||||
|
||||
cat<<EOF > $dir/srv.cnf
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
req_extensions = ext
|
||||
[dn]
|
||||
CN = www.clicon.org # localhost
|
||||
emailAddress = olof@hagsand.se
|
||||
O = Clixon
|
||||
L = Stockholm
|
||||
C = SE
|
||||
[ext]
|
||||
subjectAltName = DNS:clicon.org
|
||||
EOF
|
||||
|
||||
# Generate server key
|
||||
openssl genrsa -out $srvkey 2048
|
||||
|
||||
# Generate CSR (signing request)
|
||||
openssl req -new -config $dir/srv.cnf -key $srvkey -out $certdir/srv_csr.pem
|
||||
|
||||
# Sign server cert by CA
|
||||
openssl x509 -req -extfile $dir/srv.cnf -days 1 -passin "pass:password" -in $certdir/srv_csr.pem -CA $cacert -CAkey $cakey -CAcreateserial -out $srvcert
|
||||
|
||||
# create client certs
|
||||
for name in $users; do
|
||||
cat<<EOF > $dir/$name.cnf
|
||||
# create client certs
|
||||
for name in $users; do
|
||||
cat<<EOF > $dir/$name.cnf
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
|
|
@ -193,15 +136,15 @@ O = Clixon
|
|||
L = Stockholm
|
||||
C = SE
|
||||
EOF
|
||||
# Create client key
|
||||
openssl genrsa -out "$certdir/$name.key" 2048
|
||||
# Create client key
|
||||
openssl genrsa -out "$certdir/$name.key" 2048
|
||||
|
||||
# Generate CSR (signing request)
|
||||
openssl req -new -config $dir/$name.cnf -key $certdir/$name.key -out $certdir/$name.csr
|
||||
# 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 1 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt
|
||||
done
|
||||
# Sign by CA
|
||||
openssl x509 -req -extfile $dir/$name.cnf -days 1 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt
|
||||
done # client key
|
||||
|
||||
fi # genkeys
|
||||
|
||||
|
|
@ -226,16 +169,18 @@ testrun()
|
|||
cat <<EOF > $dir/startup_db
|
||||
<config>
|
||||
<restconf xmlns="https://clicon.org/restconf">
|
||||
<auth-type>$authtype</auth-type>
|
||||
<ssl-enable>true</ssl-enable>
|
||||
<server-cert-path>$srvcert</server-cert-path>
|
||||
<server-key-path>$srvkey</server-key-path>
|
||||
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
||||
|
||||
<socket>
|
||||
<namespace>default</namespace>
|
||||
<address>0.0.0.0</address>
|
||||
<port>$port</port>
|
||||
<ssl>$ssl</ssl>
|
||||
</socket>
|
||||
<auth-type>$authtype</auth-type>
|
||||
<server-cert-path>$srvcert</server-cert-path>
|
||||
<server-key-path>$srvkey</server-key-path>
|
||||
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
||||
</restconf>
|
||||
$RULES
|
||||
</config>
|
||||
|
|
@ -259,6 +204,9 @@ EOF
|
|||
start_backend -s startup -f $cfg
|
||||
fi
|
||||
|
||||
new "wait for backend"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
stop_restconf_pre
|
||||
|
|
@ -271,9 +219,6 @@ EOF
|
|||
fi
|
||||
fi
|
||||
|
||||
new "wait for backend"
|
||||
wait_backend
|
||||
|
||||
new "wait for restconf"
|
||||
wait_restconf --key $certdir/andy.key --cert $certdir/andy.crt
|
||||
|
||||
|
|
|
|||
|
|
@ -57,10 +57,6 @@ module clixon-restconf {
|
|||
description
|
||||
"PAM password auth";
|
||||
}
|
||||
enum none {
|
||||
description
|
||||
"No authentication, no security.";
|
||||
}
|
||||
}
|
||||
description
|
||||
"Enumeration of HTTP authorization types.";
|
||||
|
|
@ -91,31 +87,22 @@ module clixon-restconf {
|
|||
presence "Enables RESTCONF";
|
||||
description
|
||||
"HTTP daemon configuration.";
|
||||
list socket {
|
||||
key "namespace address port";
|
||||
leaf namespace {
|
||||
type string;
|
||||
description "indicates a namespace for instance. On platforms where namespaces are not suppported, always 'default'";
|
||||
}
|
||||
leaf address {
|
||||
type inet:ip-address;
|
||||
description "IP address to bind to";
|
||||
}
|
||||
leaf port {
|
||||
type inet:port-number;
|
||||
description "IP port to bind to";
|
||||
}
|
||||
leaf ssl {
|
||||
type boolean;
|
||||
default true;
|
||||
description "Enable for HTTPS otherwise HTTP protocol";
|
||||
}
|
||||
leaf ssl-enable {
|
||||
description
|
||||
"Enable ssl server functionality.
|
||||
Setting to false means the following are invalid:
|
||||
- auth-type=client-certificate
|
||||
- socket entries with ssl=true
|
||||
Also, the following are not releveant: server-cert-path, server-key-path,
|
||||
server-ca-cert-path";
|
||||
type boolean;
|
||||
default false;
|
||||
}
|
||||
leaf auth-type {
|
||||
leaf-list auth-type {
|
||||
type http-auth-type;
|
||||
description
|
||||
"The authentication type.
|
||||
Note client-certificate applies only if socket has ssl enabled";
|
||||
Note client-certificate applies only if ssl-enable is true and socket has ssl";
|
||||
}
|
||||
leaf server-cert-path {
|
||||
type string;
|
||||
|
|
@ -141,6 +128,26 @@ module clixon-restconf {
|
|||
default "/etc/ssl/certs/clixon-ca_crt.pem";
|
||||
/* CLICON_SSL_CA_CERT */
|
||||
}
|
||||
list socket {
|
||||
key "namespace address port";
|
||||
leaf namespace {
|
||||
type string;
|
||||
description "indicates a namespace for instance. On platforms where namespaces are not suppported, always 'default'";
|
||||
}
|
||||
leaf address {
|
||||
type inet:ip-address;
|
||||
description "IP address to bind to";
|
||||
}
|
||||
leaf port {
|
||||
type inet:port-number;
|
||||
description "IP port to bind to";
|
||||
}
|
||||
leaf ssl {
|
||||
type boolean;
|
||||
default true;
|
||||
description "Enable for HTTPS otherwise HTTP protocol";
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc restconf-control {
|
||||
input {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue