From c269d094e46c89169e99775ba4dd4706c22a53a8 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 12 Jan 2021 14:08:35 +0100 Subject: [PATCH] * Experimental IPC API, `clixon_client`, to support a loose integration model * Many systems using other tools employ such a model, and this API is an effort to make a usage of clixon easier * see https://clixon-docs.readthedocs.io/en/latest/overview.html#loose-integration * This is work-in-progress and is expected to change --- CHANGELOG.md | 6 + lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_client.h | 70 ++++++ lib/src/Makefile.in | 2 +- lib/src/clixon_client.c | 470 +++++++++++++++++++++++++++++++++++++ 5 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 lib/clixon/clixon_client.h create mode 100644 lib/src/clixon_client.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb056be..ae6ca989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ## 4.10.0 Expected: February 2021 +### New features + ### C/CLI-API changes on existing features Developers may need to change their code @@ -51,6 +53,10 @@ Users may have to change how they access the system ### Minor changes +* Experimental IPC API, `clixon_client`, to support a loose integration model + * Many systems using other tools employ such a model, and this API is an effort to make a usage of clixon easier + * see https://clixon-docs.readthedocs.io/en/latest/overview.html#loose-integration + * This is work-in-progress and is expected to change * Use [https://github.com/clicon/libevhtp](https://github.com/clicon/libevhtp) instead of [https://github.com/criticalstack/libevhtp](https://github.com/criticalstack/libevhtp) as a source of the evhtp source * Added callback to process-control RPC feature in clixon-lib.yang to manage processes * WHen an RPC comes in, be able to look at configuration diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 27a44328..7e96ead2 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -105,6 +105,7 @@ extern "C" { #include #include #include +#include /* * Global variables generated by Makefile diff --git a/lib/clixon/clixon_client.h b/lib/clixon/clixon_client.h new file mode 100644 index 00000000..f576632e --- /dev/null +++ b/lib/clixon/clixon_client.h @@ -0,0 +1,70 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020-2021 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 ***** + */ + +#ifndef _CLIXON_CLIENT_H +#define _CLIXON_CLIENT_H + +/* + * Prototypes + */ + +#ifdef __cplusplus +extern "C" { +#endif + +int clixon_client_connect(void *h, const struct sockaddr* srv, int srv_sz); +int clixon_client_close(int sock); +int clixon_client_session_start(void *h, const char *db); +int clixon_client_session_end(void *h); +int clixon_client_subscribe(int sock, int priority, int nspace, + int *spoint, const char *fmt, ...); +int clixon_client_subscribe_done(int sock); +int clixon_client_read_subscription_socket(int sock, int sub_points[], int *resultlen); +int clixon_client_num_instances(int sock, const char *xnamespace, const char *xpath); +int clixon_client_get_bool(int sock, int *rval, const char *xnamespace, const char *xpath); +int clixon_client_get_str(int sock, char *rval, int n, const char *xnamespace, const char *xpath); +int clixon_client_get_u_int8(int sock, uint8_t *rval, const char *xnamespace, const char *xpath); +int clixon_client_get_u_int16(int sock, uint16_t *rval, const char *xnamespace, const char *xpath); +int clixon_client_get_u_int32(int sock, uint32_t *rval, const char *xnamespace, const char *xpath); +int clixon_client_get_u_int64(int sock, uint64_t *rval, const char *xnamespace, const char *xpath); + +void *clixon_client_init(const char *name, FILE *estream, const int debug, const char *config_file); +int clixon_client_terminate(void *h); + +#ifdef __cplusplus +} +#endif + +#endif /* _CLIXON_CLIENT_H */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 834aba13..1a341519 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -83,7 +83,7 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_proto.c clixon_proto_client.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c clixon_xpath_optimize.c \ clixon_sha1.c clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ - clixon_netconf_lib.c clixon_stream.c clixon_nacm.c + clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ diff --git a/lib/src/clixon_client.c b/lib/src/clixon_client.c new file mode 100644 index 00000000..84565344 --- /dev/null +++ b/lib/src/clixon_client.c @@ -0,0 +1,470 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020-2021 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 ***** + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +#include "clixon_queue.h" +#include "clixon_string.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_log.h" +#include "clixon_err.h" +#include "clixon_yang.h" +#include "clixon_options.h" +#include "clixon_xml.h" +#include "clixon_xml_nsctx.h" +#include "clixon_xml_io.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_netconf_lib.h" +#include "clixon_proto.h" +#include "clixon_proto_client.h" +#include "clixon_client.h" + +/*! Initialize Clixon client API + * @param[in] name Name of client (NYI) + * @param[in] estream Error/debug file (NULL: syslog) + * @param[in] debug Clixon debug flag + * @param[in] config_file Clixon configuration file (eg for CLICON_SOCK) + * @see clixon_client_close + */ +clicon_handle +clixon_client_init(const char *name, + FILE *estream, + const int debug, + const char *config_file) +{ + clicon_handle h; + + clicon_log_init("clixon", LOG_DEBUG, CLICON_LOG_STDERR); + clicon_debug_init(debug, estream); + fprintf(stderr, "fprintf %s\n", __FUNCTION__); + /* Initiate CLICON handle. CLIgen is also initialized */ + if ((h = clicon_handle_init()) == NULL) + return NULL; + /* Set clixon config file - reuse the one in the main installation */ + clicon_option_str_set(h, "CLICON_CONFIGFILE", (char*)config_file); + /* Find, read and parse configfile */ + if (clicon_options_main(h) < 0) + return NULL; + return h; +} + +/*! Deallocate everything from client_init + * @see clixon_client_init + */ +int +clixon_client_terminate(clicon_handle h) +{ + clicon_debug(1, "%s", __FUNCTION__); + clicon_handle_exit(h); + return 0; +} + +/*! Connect client to clixon backend according to config and return a socket + * @param[in] h Clixon handle + * @retval s Socket, need to be closed by caller + * @see clixon_client_close Close the socket returned here + */ +int +clixon_client_connect(clicon_handle h, + const struct sockaddr* srv, + int srv_sz) +{ + int s = -1; + + clicon_debug(1, "%s", __FUNCTION__); + if (clicon_rpc_connect(h, &s) < 0) + return -1; + return s; +} + +/*! Connect client to clixon backend according to config and return a socket + * @param[in] s Socket, opened by connect call + * @see clixon_client_connect + */ +int +clixon_client_close(int s) +{ + clicon_debug(1, "%s", __FUNCTION__); + close(s); + return 0; +} + +int +clixon_client_subscribe(int sock, + int priority, + int nspace, + int *spoint, + const char *fmt, ...) +{ + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +int +clixon_client_subscribe_done(int sock) +{ + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +int +clixon_client_read_subscription_socket(int sock, + int sub_points[], + int *resultlen) +{ + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +/*! Internal function to construct a get-config and query a value from the backend + * + * @param[in] sock Stream socket + * @param[in] xpath XPath + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[out] val String value + */ +static int +clixon_client_get_xdata(int sock, + const char *namespace, + const char *xpath, + cxobj **xdata) +{ + int retval = -1; + char *retdata = NULL; + cxobj *xret = NULL; + cxobj *xd; + cbuf *cb = NULL; + struct clicon_msg *msg = NULL; + const char *db = "running"; + cvec *nsc = NULL; + + if ((cb = cbuf_new()) == NULL) + goto done; + cprintf(cb, "<%s/>", db); + if (xpath && strlen(xpath)){ + cprintf(cb, "<%s:filter %s:type=\"xpath\" xmlns=\"%s\" %s:select=\"%s\"", + NETCONF_BASE_PREFIX, + NETCONF_BASE_PREFIX, + namespace, + NETCONF_BASE_PREFIX, + xpath); + if (xml_nsctx_cbuf(cb, nsc) < 0) + goto done; + cprintf(cb, "/>"); + } + cprintf(cb, ""); + clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cb)); + if ((msg = clicon_msg_encode(0, "%s", cbuf_get(cb))) == NULL) + goto done; + if (clicon_rpc(sock, msg, &retdata) < 0) + goto done; + if (clixon_xml_parse_string(retdata, YB_NONE, NULL, &xret, NULL) < 0) + goto done; + if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL){ + xd = xml_parent(xd); /* point to rpc-reply */ + clixon_netconf_error(xd, "Get config", NULL); + goto done; /* Not fatal */ + } + else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){ + if ((xd = xml_new("data", NULL, CX_ELMNT)) == NULL) + goto done; + } + else{ + if (xml_rm(xd) < 0) + goto done; + } + *xdata = xd; + retval = 0; + done: + if (xret) + xml_free(xret); + if (cb) + cbuf_free(cb); + if (msg) + free(msg); + return retval; +} + +static int +clixon_client_get_val(int sock, + const char *namespace, + const char *xpath, + char **val) +{ + int retval = -1; + cxobj *xdata = NULL; + cxobj *xobj; + + if (val == NULL){ + clicon_err(OE_XML, EINVAL, "Expected val"); + goto done; + } +#if 1 /* XXX: Problem is xpath with indexes used in original need to change to [0] in the 2nd access */ + if (clixon_client_get_xdata(sock, namespace, "/", &xdata) < 0) + goto done; +#else + if (clixon_client_get_xdata(sock, namespace, xpath, &xdata) < 0) + goto done; +#endif + if ((xobj = xpath_first(xdata, NULL, "%s", xpath)) == NULL){ + clicon_err(OE_XML, ENOENT, "Expected local xpath result"); + goto done; + } + *val = xml_body(xobj); + retval = 0; + done: + return retval; +} + +/*! Get number of list entries + * @param[in] sock Stream socket + * @note there is no way using netconf to get this number: instead the whole list is retrieved. + */ +int +clixon_client_num_instances(int sock, + const char *namespace, + const char *xpath) +{ + int retval = -1; + cxobj *xdata = NULL; + cxobj **vec = NULL; + size_t veclen; + + clicon_debug(1, "%s", __FUNCTION__); +#if 1 /* XXX: Problem is xpath with indexes used in original need to change to [0] in the 2nd access */ + if (clixon_client_get_xdata(sock, namespace, "/", &xdata) < 0) + goto done; +#else + if (clixon_client_get_xdata(sock, namespace, xpath, &xdata) < 0) + goto done; +#endif + if (xpath_vec(xdata, NULL, "%s", &vec, &veclen, xpath) < 0) + goto done; + retval = veclen; + done: + if (vec) + free(vec); + return retval; +} + +int +clixon_client_get_bool(int sock, + int *rval, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + char *reason = NULL; + int ret; + uint8_t val0=0; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + if ((ret = parse_bool(val, &val0, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_bool"); + goto done; + } + if (ret == 0){ + clicon_err(OE_XML, EINVAL, "%s", reason); + goto done; + } + *rval = (int)val0; + retval = 0; + done: + if (reason) + free(reason); + return retval; +} + +/*! Get string using get-config + * @param[in] sock Stream socket + * @param[out] rval Return value string + * @param[in] n Length of string + * @param[in] xpath XPath + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + */ +int +clixon_client_get_str(int sock, + char *rval, + int n, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + strncpy(rval, val, n-1); + rval[n-1]= '\0'; + retval = 0; + done: + return retval; +} + +int +clixon_client_get_u_int8(int sock, + uint8_t *rval, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + char *reason = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + if ((ret = parse_uint8(val, rval, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_bool"); + goto done; + } + if (ret == 0){ + clicon_err(OE_XML, EINVAL, "%s", reason); + goto done; + } + retval = 0; + done: + if (reason) + free(reason); + return retval; +} + +int +clixon_client_get_u_int16(int sock, + uint16_t *rval, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + char *reason = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + if ((ret = parse_uint16(val, rval, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_bool"); + goto done; + } + if (ret == 0){ + clicon_err(OE_XML, EINVAL, "%s", reason); + goto done; + } + retval = 0; + done: + if (reason) + free(reason); + return retval; +} + +int +clixon_client_get_u_int32(int sock, + uint32_t *rval, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + char *reason = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + if ((ret = parse_uint32(val, rval, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_bool"); + goto done; + } + if (ret == 0){ + clicon_err(OE_XML, EINVAL, "%s", reason); + goto done; + } + retval = 0; + done: + if (reason) + free(reason); + return retval; +} + +int +clixon_client_get_u_int64(int sock, + uint64_t *rval, + const char *namespace, + const char *xpath) +{ + int retval = -1; + char *val = NULL; + char *reason = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + if (clixon_client_get_val(sock, namespace, xpath, &val) < 0) + goto done; + if ((ret = parse_uint64(val, rval, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_bool"); + goto done; + } + if (ret == 0){ + clicon_err(OE_XML, EINVAL, "%s", reason); + goto done; + } + retval = 0; + done: + if (reason) + free(reason); + return retval; +}