diff --git a/CHANGELOG.md b/CHANGELOG.md index e892b8f6..2abf271a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,16 @@ ## 6.1.0 Expected: beginning of 2023 +### C/CLI-API changes on existing features +Developers may need to change their code + +* C-API + * Added `sock_flags` parameter to `clixon_proc_socket()` + +### Minor features + +* Added `CLIXON_CLIENT_SSH` to client API to communicate remotely via SSH netconf sub-system + ### Corrected Bugs * Fixed [Netconf monitoring](https://github.com/clicon/clixon/issues/370) diff --git a/configure b/configure index efbdb17e..6a341253 100755 --- a/configure +++ b/configure @@ -4607,7 +4607,7 @@ fi cat >>confdefs.h <<_ACEOF -#define SSH_BIN $SSH_BIN +#define SSH_BIN "$SSH_BIN" _ACEOF diff --git a/configure.ac b/configure.ac index 803eccc3..93b11da0 100644 --- a/configure.ac +++ b/configure.ac @@ -149,7 +149,7 @@ fi # SSH binary path AC_PATH_PROG(SSH_BIN, ssh) -AC_DEFINE_UNQUOTED(SSH_BIN, $SSH_BIN, [SSH binary]) +AC_DEFINE_UNQUOTED(SSH_BIN, "$SSH_BIN", [SSH binary]) # Get "bison" from bison -y or other string if test "$YACC" = "${YACC##bison}" ; then diff --git a/lib/clixon/clixon_client.h b/lib/clixon/clixon_client.h index 2b8172a8..0feb3a84 100644 --- a/lib/clixon/clixon_client.h +++ b/lib/clixon/clixon_client.h @@ -42,11 +42,25 @@ typedef void *clixon_handle; typedef void *clixon_client_handle; -/* Connection type as parameter to connect */ +/* Connection type as parameter to connect + */ typedef enum { - CLIXON_CLIENT_IPC, /* Internal IPC API, only experimental use */ - CLIXON_CLIENT_NETCONF, /* External Netconf */ - CLIXON_CLIENT_SSH /* NYI External Netconf over SSH */ + /* Internal IPC API, connect directly on local UNIX domain socket to backend + * or using IP according to CLICON_SOCK_FAMILY setting + * see https://clixon-docs.readthedocs.io/en/latest/netconf.html#ipc + * Must be local on device + */ + CLIXON_CLIENT_IPC, + /* Regular NETCONF via local netconf binary + * Fork clixon_netconf locally which in turn communicates with backend + * Must be local on device + */ + CLIXON_CLIENT_NETCONF, + /* Regular NETCONF using ssh sub-system via local SSH (openssh) client binary + * Fork ssh locally which in turn communicates remotely to device + * Must have openssh installed locally and device must have ssh sub-subsystem + */ + CLIXON_CLIENT_SSH } clixon_client_type; /* @@ -59,7 +73,9 @@ extern "C" { clixon_handle clixon_client_init(const char *config_file); int clixon_client_terminate(clixon_handle h); -clixon_client_handle clixon_client_connect(clixon_handle h, clixon_client_type socktype); +int clixon_client_lock(int sock, const int lock, const char *db); +int clixon_client_hello(int sock, int version); +clixon_client_handle clixon_client_connect(clixon_handle h, clixon_client_type socktype, const char *dest); int clixon_client_disconnect(clixon_client_handle ch); int clixon_client_get_bool(clixon_client_handle ch, int *rval, const char *xnamespace, const char *xpath); int clixon_client_get_str(clixon_client_handle ch, char *rval, int n, const char *xnamespace, const char *xpath); diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 1e6d3cba..ea89dd75 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -124,7 +124,7 @@ enum error_option{ /* edit-config */ /* NETCONF framing */ enum framing_type{ - NETCONF_SSH_EOM, /* RFC 4742, RFC 6242 hello msg (end-of-msg: ]]>]]>)*/ + NETCONF_SSH_EOM=0, /* RFC 4742, RFC 6242 hello msg (end-of-msg: ]]>]]>)*/ NETCONF_SSH_CHUNKED, /* RFC 6242 Chunked framing */ }; typedef enum framing_type netconf_framing_type; diff --git a/lib/clixon/clixon_proc.h b/lib/clixon/clixon_proc.h index d2e1c3b7..e57adcf4 100644 --- a/lib/clixon/clixon_proc.h +++ b/lib/clixon/clixon_proc.h @@ -58,7 +58,7 @@ typedef int (proc_cb_t)(clicon_handle h, process_entry_t *pe, proc_operation *op /* * Prototypes */ -int clixon_proc_socket(char **argv, pid_t *pid, int *sock); +int clixon_proc_socket(char **argv, int sock_flags, pid_t *pid, int *sock); int clixon_proc_socket_close(pid_t pid, int sock); int clixon_proc_background(char **argv, const char *netns, pid_t *pid); int clixon_process_pid(clicon_handle h, const char *name, pid_t *pid); diff --git a/lib/src/clixon_client.c b/lib/src/clixon_client.c index e66848eb..ac84f6c1 100644 --- a/lib/src/clixon_client.c +++ b/lib/src/clixon_client.c @@ -146,7 +146,7 @@ clixon_client_terminate(clicon_handle h) * @retval 0 OK * @retval -1 Error */ -static int +int clixon_client_lock(int sock, const int lock, const char *db) @@ -204,15 +204,14 @@ clixon_client_lock(int sock, /*! Internal function to construct the encoding and hello message * - * @param[in] sock Socket - * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) - * @param[in] xpath XPath - * @param[out] xdata XML data tree (may or may not include the intended data) - * @retval 0 OK - * @retval -1 Error + * @param[in] sock Socket to netconf server + * @param[in] version Netconf version for capability announcement + * @retval 0 OK + * @retval -1 Error */ -static int -clixon_client_hello(int sock) +int +clixon_client_hello(int sock, + int version) { int retval = -1; cbuf *msg = NULL; @@ -222,9 +221,11 @@ clixon_client_hello(int sock) clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - cprintf(msg, ""); + // cprintf(msg, "\n"); cprintf(msg, "", NETCONF_BASE_NAMESPACE); - cprintf(msg, "%s", NETCONF_BASE_CAPABILITY_1_1); + cprintf(msg, ""); + cprintf(msg, "%s", version==0?NETCONF_BASE_CAPABILITY_1_0:NETCONF_BASE_CAPABILITY_1_1); + cprintf(msg, ""); cprintf(msg, ""); cprintf(msg, "]]>]]>"); if (clicon_msg_send1(sock, msg) < 0) @@ -237,25 +238,112 @@ clixon_client_hello(int sock) return retval; } +/*! + */ +static int +clixon_client_connect_netconf(clicon_handle h, + struct clixon_client_handle *cch) +{ + int retval = -1; + int nr; + int i; + char **argv = NULL; + char *netconf_bin = NULL; + struct stat st = {0,}; + char dbgstr[8]; + + nr = 7; + if (clicon_debug_get() != 0) + nr += 2; + if ((argv = calloc(nr, sizeof(char *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + i = 0; + if ((netconf_bin = getenv("CLIXON_NETCONF_BIN")) == NULL) + netconf_bin = CLIXON_NETCONF_BIN; + if (stat(netconf_bin, &st) < 0){ + clicon_err(OE_NETCONF, errno, "netconf binary %s. Set with CLIXON_NETCONF_BIN=", + netconf_bin); + goto done; + } + argv[i++] = netconf_bin; + argv[i++] = "-q"; + argv[i++] = "-f"; + argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); + argv[i++] = "-l"; /* log to syslog */ + argv[i++] = "s"; + if (clicon_debug_get() != 0){ + argv[i++] = "-D"; + snprintf(dbgstr, sizeof(dbgstr)-1, "%d", clicon_debug_get()); + argv[i++] = dbgstr; + } + argv[i++] = NULL; + assert(i==nr); + if (clixon_proc_socket(argv, SOCK_DGRAM, &cch->cch_pid, &cch->cch_socket) < 0){ + goto done; + } + retval = 0; + done: + return retval; +} + +/*! + */ +static int +clixon_client_connect_ssh(clicon_handle h, + struct clixon_client_handle *cch, + const char *dest) +{ + int retval = -1; + int nr; + int i; + char **argv = NULL; + char *ssh_bin = SSH_BIN; + struct stat st = {0,}; + + clicon_debug(1, "%s", __FUNCTION__); + nr = 5; + if ((argv = calloc(nr, sizeof(char *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + i = 0; + if (stat(ssh_bin, &st) < 0){ + clicon_err(OE_NETCONF, errno, "ssh binary %s", ssh_bin); + goto done; + } + argv[i++] = ssh_bin; + argv[i++] = (char*)dest; + argv[i++] = "-s"; + argv[i++] = "netconf"; + argv[i++] = NULL; + assert(i==nr); + for (i=0;icch_pid, &cch->cch_socket) < 0){ + goto done; + } + retval = 0; + done: + return retval; +} + /*! Connect client to clixon backend according to config and return a socket * @param[in] h Clixon handle * @param[in] socktype Type of socket, internal/external/netconf/ssh + * @param[in] dest Destination for some types * @retval ch Clixon session handler * @retval NULL Error * @see clixon_client_disconnect Close the socket returned here */ clixon_client_handle clixon_client_connect(clicon_handle h, - clixon_client_type socktype) + clixon_client_type socktype, + const char *dest) { struct clixon_client_handle *cch = NULL; - char **argv = NULL; - int nr; - int i; - char *netconf_bin = NULL; - struct stat st = {0,}; size_t sz = sizeof(struct clixon_client_handle); - char dbgstr[8]; clicon_debug(1, "%s", __FUNCTION__); if ((cch = malloc(sz)) == NULL){ @@ -271,48 +359,19 @@ clixon_client_connect(clicon_handle h, goto err; break; case CLIXON_CLIENT_NETCONF: - nr = 7; - if (clicon_debug_get() != 0) - nr += 2; - if ((argv = calloc(nr, sizeof(char *))) == NULL){ - clicon_err(OE_UNIX, errno, "calloc"); - goto err; - } - i = 0; - if ((netconf_bin = getenv("CLIXON_NETCONF_BIN")) == NULL) - netconf_bin = CLIXON_NETCONF_BIN; - if (stat(netconf_bin, &st) < 0){ - clicon_err(OE_NETCONF, errno, "netconf binary %s. Set with CLIXON_NETCONF_BIN=", - netconf_bin); - goto err; - } - argv[i++] = netconf_bin; - argv[i++] = "-q"; - argv[i++] = "-f"; - argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); - argv[i++] = "-l"; /* log to syslog */ - argv[i++] = "s"; - if (clicon_debug_get() != 0){ - argv[i++] = "-D"; - snprintf(dbgstr, sizeof(dbgstr)-1, "%d", clicon_debug_get()); - argv[i++] = dbgstr; - } - argv[i++] = NULL; - assert(i==nr); - if (clixon_proc_socket(argv, &cch->cch_pid, &cch->cch_socket) < 0){ - goto err; - } - /* Start with encoding and hello message */ - if (clixon_client_hello(cch->cch_socket) < 0) + if (clixon_client_connect_netconf(h, cch) < 0) goto err; break; +#ifdef SSH_BIN case CLIXON_CLIENT_SSH: + if (clixon_client_connect_ssh(h, cch, dest) < 0) + goto err; +#else + clicon_err(OE_UNIX, 0, "No ssh bin"); + goto done; +#endif break; } /* switch */ - /* lock */ - if (clixon_client_lock(cch->cch_socket, 1, "running") < 0) - goto err; - cch->cch_locked = 1; done: clicon_debug(1, "%s retval:%p", __FUNCTION__, cch); return cch; @@ -347,13 +406,12 @@ clixon_client_disconnect(clixon_client_handle ch) case CLIXON_CLIENT_IPC: close(cch->cch_socket); break; + case CLIXON_CLIENT_SSH: case CLIXON_CLIENT_NETCONF: if (clixon_proc_socket_close(cch->cch_pid, cch->cch_socket) < 0) goto done; break; - case CLIXON_CLIENT_SSH: - break; } free(cch); retval = 0; @@ -403,6 +461,7 @@ clixon_xml_bottom(cxobj *xtop, * @param[out] xdata XML data tree (may or may not include the intended data) * @retval 0 OK * @retval -1 Error + * @note configurable netconf framing type, now hardwired to 0 */ static int clixon_client_get_xdata(int sock, @@ -445,9 +504,11 @@ clixon_client_get_xdata(int sock, cprintf(msg, "/>"); } cprintf(msg, ""); - if (netconf_output_encap(NETCONF_SSH_CHUNKED, msg) < 0) + if (netconf_output_encap(0, msg) < 0) // XXX configurable session goto done; - if (clicon_rpc1(sock, msg, msgret, &eof) < 0) + if (clicon_msg_send1(sock, msg) < 0) + goto done; + if (clicon_msg_rcv1(sock, msgret, &eof) < 0) goto done; if (eof){ close(sock); diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c index b26ae094..421b72ef 100644 --- a/lib/src/clixon_proc.c +++ b/lib/src/clixon_proc.c @@ -163,15 +163,18 @@ clixon_proc_sigint(int sig) } /*! Fork a child, exec a child and setup socket to child and return to caller - * @param[in] argv NULL-terminated Argument vector - * @param[in] doerr If non-zero, stderr will be directed to the pipe as well. - * @param[out] s Socket - * @retval O OK - * @retval -1 Error. + * @param[in] argv NULL-terminated Argument vector + * @param[in] sock_flags Socket type/flags, typically SOCK_DGRAM or SOCK_STREAM, see + * @param[out] pid Process-id of child + * @param[out] sock Socket + * @retval O OK + * @retval -1 Error. * @see clixon_proc_socket_close close sockets, kill child and wait for child termination + * @see for flags usage see man sockerpair(2) */ int clixon_proc_socket(char **argv, + int sock_flags, pid_t *pid, int *sock) { @@ -182,12 +185,6 @@ clixon_proc_socket(char **argv, sigset_t oset; int sig = 0; -#ifdef __APPLE__ - int sock_flags = SOCK_DGRAM; -#else - int sock_flags = SOCK_DGRAM | SOCK_CLOEXEC; -#endif - if (argv == NULL){ clicon_err(OE_UNIX, EINVAL, "argv is NULL"); goto done; @@ -197,18 +194,6 @@ clixon_proc_socket(char **argv, goto done; } -#ifdef __APPLE__ - if (fcntl(sp[0], O_CLOEXEC)) { - clicon_err(OE_UNIX, errno, "fcntl, sp[0]"); - goto done; - } - - if (fcntl(sp[1], O_CLOEXEC)) { - clicon_err(OE_UNIX, errno, "fcntl, sp[1]"); - goto done; - } -#endif - sigprocmask(0, NULL, &oset); set_signal(SIGINT, clixon_proc_sigint, &oldhandler); sig++; diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index e0f0276b..e2e257c4 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -433,6 +433,7 @@ clicon_msg_rcv(int s, * @param[out] eof Set if eof encountered * @see netconf_input_cb() * @see clicon_msg_rcv using IPC message struct + * @note only NETCONF version 1.0 EOM framing */ int clicon_msg_rcv1(int s, @@ -502,7 +503,7 @@ clicon_msg_send1(int s, int retval = -1; if (atomicio((ssize_t (*)(int, void *, size_t))write, - s, cbuf_get(cb), cbuf_len(cb)+1) < 0){ + s, cbuf_get(cb), cbuf_len(cb)) < 0){ clicon_err(OE_CFG, errno, "atomicio"); clicon_log(LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno)); goto done; diff --git a/test/test_client.sh b/test/test_client.sh index 1a7dc794..746ce4c6 100755 --- a/test/test_client.sh +++ b/test/test_client.sh @@ -85,8 +85,9 @@ main(int argc, char **argv) { int retval = -1; - clixon_handle h = NULL; /* clixon handle */ + clixon_handle h = NULL; /* clixon handle */ clixon_client_handle ch = NULL; /* clixon client handle */ + int s; clicon_log_init("client", LOG_DEBUG, CLICON_LOG_STDERR); // debug clicon_debug_init($debug, NULL); // debug @@ -95,9 +96,11 @@ main(int argc, if ((h = clixon_client_init("$cfg")) == NULL) return -1; /* Make a connection over netconf or ssh/netconf */ - if ((ch = clixon_client_connect(h, CLIXON_CLIENT_NETCONF)) == NULL) + if ((ch = clixon_client_connect(h, CLIXON_CLIENT_NETCONF, NULL)) == NULL) return -1; - + s = clixon_client_socket_get(ch); + if (clixon_client_hello(s, 0) < 0) + return -1; /* Here are read functions depending on an example YANG * (Need an example YANG and XML input to confd) */