Added CLIXON_CLIENT_SSH to client API to communicate remotely via SSH netconf sub-system

configure: stringified SSH_BIN
C-API: Added `sock_flags` parameter to `clixon_proc_socket()`
This commit is contained in:
Olof Hagsand 2022-12-08 13:26:39 +01:00
parent 100f15b699
commit 6baa904039
10 changed files with 171 additions and 95 deletions

View file

@ -41,6 +41,16 @@
## 6.1.0 ## 6.1.0
Expected: beginning of 2023 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 ### Corrected Bugs
* Fixed [Netconf monitoring](https://github.com/clicon/clixon/issues/370) * Fixed [Netconf monitoring](https://github.com/clicon/clixon/issues/370)

2
configure vendored
View file

@ -4607,7 +4607,7 @@ fi
cat >>confdefs.h <<_ACEOF cat >>confdefs.h <<_ACEOF
#define SSH_BIN $SSH_BIN #define SSH_BIN "$SSH_BIN"
_ACEOF _ACEOF

View file

@ -149,7 +149,7 @@ fi
# SSH binary path # SSH binary path
AC_PATH_PROG(SSH_BIN, ssh) 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 # Get "bison" from bison -y or other string
if test "$YACC" = "${YACC##bison}" ; then if test "$YACC" = "${YACC##bison}" ; then

View file

@ -42,11 +42,25 @@
typedef void *clixon_handle; typedef void *clixon_handle;
typedef void *clixon_client_handle; typedef void *clixon_client_handle;
/* Connection type as parameter to connect */ /* Connection type as parameter to connect
*/
typedef enum { typedef enum {
CLIXON_CLIENT_IPC, /* Internal IPC API, only experimental use */ /* Internal IPC API, connect directly on local UNIX domain socket to backend
CLIXON_CLIENT_NETCONF, /* External Netconf */ * or using IP according to CLICON_SOCK_FAMILY setting
CLIXON_CLIENT_SSH /* NYI External Netconf over SSH */ * 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; } clixon_client_type;
/* /*
@ -59,7 +73,9 @@ extern "C" {
clixon_handle clixon_client_init(const char *config_file); clixon_handle clixon_client_init(const char *config_file);
int clixon_client_terminate(clixon_handle h); 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_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_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); int clixon_client_get_str(clixon_client_handle ch, char *rval, int n, const char *xnamespace, const char *xpath);

View file

@ -124,7 +124,7 @@ enum error_option{ /* edit-config */
/* NETCONF framing /* NETCONF framing
*/ */
enum framing_type{ 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 */ NETCONF_SSH_CHUNKED, /* RFC 6242 Chunked framing */
}; };
typedef enum framing_type netconf_framing_type; typedef enum framing_type netconf_framing_type;

View file

@ -58,7 +58,7 @@ typedef int (proc_cb_t)(clicon_handle h, process_entry_t *pe, proc_operation *op
/* /*
* Prototypes * 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_socket_close(pid_t pid, int sock);
int clixon_proc_background(char **argv, const char *netns, pid_t *pid); 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); int clixon_process_pid(clicon_handle h, const char *name, pid_t *pid);

View file

@ -146,7 +146,7 @@ clixon_client_terminate(clicon_handle h)
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
*/ */
static int int
clixon_client_lock(int sock, clixon_client_lock(int sock,
const int lock, const int lock,
const char *db) const char *db)
@ -204,15 +204,14 @@ clixon_client_lock(int sock,
/*! Internal function to construct the encoding and hello message /*! Internal function to construct the encoding and hello message
* *
* @param[in] sock Socket * @param[in] sock Socket to netconf server
* @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) * @param[in] version Netconf version for capability announcement
* @param[in] xpath XPath
* @param[out] xdata XML data tree (may or may not include the intended data)
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
*/ */
static int int
clixon_client_hello(int sock) clixon_client_hello(int sock,
int version)
{ {
int retval = -1; int retval = -1;
cbuf *msg = NULL; cbuf *msg = NULL;
@ -222,9 +221,11 @@ clixon_client_hello(int sock)
clicon_err(OE_PLUGIN, errno, "cbuf_new"); clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done; goto done;
} }
cprintf(msg, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); // cprintf(msg, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
cprintf(msg, "<hello xmlns=\"%s\">", NETCONF_BASE_NAMESPACE); cprintf(msg, "<hello xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
cprintf(msg, "<capabilities><capability>%s</capability></capabilities>", NETCONF_BASE_CAPABILITY_1_1); cprintf(msg, "<capabilities>");
cprintf(msg, "<capability>%s</capability>", version==0?NETCONF_BASE_CAPABILITY_1_0:NETCONF_BASE_CAPABILITY_1_1);
cprintf(msg, "</capabilities>");
cprintf(msg, "</hello>"); cprintf(msg, "</hello>");
cprintf(msg, "]]>]]>"); cprintf(msg, "]]>]]>");
if (clicon_msg_send1(sock, msg) < 0) if (clicon_msg_send1(sock, msg) < 0)
@ -237,25 +238,112 @@ clixon_client_hello(int sock)
return retval; 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;i<nr;i++)
clicon_debug(1, "%s: argv[%d]:%s", __FUNCTION__, i, argv[i]);
if (clixon_proc_socket(argv, SOCK_STREAM, &cch->cch_pid, &cch->cch_socket) < 0){
goto done;
}
retval = 0;
done:
return retval;
}
/*! Connect client to clixon backend according to config and return a socket /*! Connect client to clixon backend according to config and return a socket
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] socktype Type of socket, internal/external/netconf/ssh * @param[in] socktype Type of socket, internal/external/netconf/ssh
* @param[in] dest Destination for some types
* @retval ch Clixon session handler * @retval ch Clixon session handler
* @retval NULL Error * @retval NULL Error
* @see clixon_client_disconnect Close the socket returned here * @see clixon_client_disconnect Close the socket returned here
*/ */
clixon_client_handle clixon_client_handle
clixon_client_connect(clicon_handle h, clixon_client_connect(clicon_handle h,
clixon_client_type socktype) clixon_client_type socktype,
const char *dest)
{ {
struct clixon_client_handle *cch = NULL; 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); size_t sz = sizeof(struct clixon_client_handle);
char dbgstr[8];
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if ((cch = malloc(sz)) == NULL){ if ((cch = malloc(sz)) == NULL){
@ -271,48 +359,19 @@ clixon_client_connect(clicon_handle h,
goto err; goto err;
break; break;
case CLIXON_CLIENT_NETCONF: case CLIXON_CLIENT_NETCONF:
nr = 7; if (clixon_client_connect_netconf(h, cch) < 0)
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)
goto err; goto err;
break; break;
#ifdef SSH_BIN
case CLIXON_CLIENT_SSH: 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; break;
} /* switch */ } /* switch */
/* lock */
if (clixon_client_lock(cch->cch_socket, 1, "running") < 0)
goto err;
cch->cch_locked = 1;
done: done:
clicon_debug(1, "%s retval:%p", __FUNCTION__, cch); clicon_debug(1, "%s retval:%p", __FUNCTION__, cch);
return cch; return cch;
@ -347,13 +406,12 @@ clixon_client_disconnect(clixon_client_handle ch)
case CLIXON_CLIENT_IPC: case CLIXON_CLIENT_IPC:
close(cch->cch_socket); close(cch->cch_socket);
break; break;
case CLIXON_CLIENT_SSH:
case CLIXON_CLIENT_NETCONF: case CLIXON_CLIENT_NETCONF:
if (clixon_proc_socket_close(cch->cch_pid, if (clixon_proc_socket_close(cch->cch_pid,
cch->cch_socket) < 0) cch->cch_socket) < 0)
goto done; goto done;
break; break;
case CLIXON_CLIENT_SSH:
break;
} }
free(cch); free(cch);
retval = 0; 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) * @param[out] xdata XML data tree (may or may not include the intended data)
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note configurable netconf framing type, now hardwired to 0
*/ */
static int static int
clixon_client_get_xdata(int sock, clixon_client_get_xdata(int sock,
@ -445,9 +504,11 @@ clixon_client_get_xdata(int sock,
cprintf(msg, "/>"); cprintf(msg, "/>");
} }
cprintf(msg, "</get-config></rpc>"); cprintf(msg, "</get-config></rpc>");
if (netconf_output_encap(NETCONF_SSH_CHUNKED, msg) < 0) if (netconf_output_encap(0, msg) < 0) // XXX configurable session
goto done; 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; goto done;
if (eof){ if (eof){
close(sock); close(sock);

View file

@ -164,14 +164,17 @@ clixon_proc_sigint(int sig)
/*! Fork a child, exec a child and setup socket to child and return to caller /*! Fork a child, exec a child and setup socket to child and return to caller
* @param[in] argv NULL-terminated Argument vector * @param[in] argv NULL-terminated Argument vector
* @param[in] doerr If non-zero, stderr will be directed to the pipe as well. * @param[in] sock_flags Socket type/flags, typically SOCK_DGRAM or SOCK_STREAM, see
* @param[out] s Socket * @param[out] pid Process-id of child
* @param[out] sock Socket
* @retval O OK * @retval O OK
* @retval -1 Error. * @retval -1 Error.
* @see clixon_proc_socket_close close sockets, kill child and wait for child termination * @see clixon_proc_socket_close close sockets, kill child and wait for child termination
* @see for flags usage see man sockerpair(2)
*/ */
int int
clixon_proc_socket(char **argv, clixon_proc_socket(char **argv,
int sock_flags,
pid_t *pid, pid_t *pid,
int *sock) int *sock)
{ {
@ -182,12 +185,6 @@ clixon_proc_socket(char **argv,
sigset_t oset; sigset_t oset;
int sig = 0; int sig = 0;
#ifdef __APPLE__
int sock_flags = SOCK_DGRAM;
#else
int sock_flags = SOCK_DGRAM | SOCK_CLOEXEC;
#endif
if (argv == NULL){ if (argv == NULL){
clicon_err(OE_UNIX, EINVAL, "argv is NULL"); clicon_err(OE_UNIX, EINVAL, "argv is NULL");
goto done; goto done;
@ -197,18 +194,6 @@ clixon_proc_socket(char **argv,
goto done; 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); sigprocmask(0, NULL, &oset);
set_signal(SIGINT, clixon_proc_sigint, &oldhandler); set_signal(SIGINT, clixon_proc_sigint, &oldhandler);
sig++; sig++;

View file

@ -433,6 +433,7 @@ clicon_msg_rcv(int s,
* @param[out] eof Set if eof encountered * @param[out] eof Set if eof encountered
* @see netconf_input_cb() * @see netconf_input_cb()
* @see clicon_msg_rcv using IPC message struct * @see clicon_msg_rcv using IPC message struct
* @note only NETCONF version 1.0 EOM framing
*/ */
int int
clicon_msg_rcv1(int s, clicon_msg_rcv1(int s,
@ -502,7 +503,7 @@ clicon_msg_send1(int s,
int retval = -1; int retval = -1;
if (atomicio((ssize_t (*)(int, void *, size_t))write, 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_err(OE_CFG, errno, "atomicio");
clicon_log(LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno)); clicon_log(LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno));
goto done; goto done;

View file

@ -87,6 +87,7 @@ main(int argc,
int retval = -1; int retval = -1;
clixon_handle h = NULL; /* clixon handle */ clixon_handle h = NULL; /* clixon handle */
clixon_client_handle ch = NULL; /* clixon client handle */ clixon_client_handle ch = NULL; /* clixon client handle */
int s;
clicon_log_init("client", LOG_DEBUG, CLICON_LOG_STDERR); // debug clicon_log_init("client", LOG_DEBUG, CLICON_LOG_STDERR); // debug
clicon_debug_init($debug, NULL); // debug clicon_debug_init($debug, NULL); // debug
@ -95,9 +96,11 @@ main(int argc,
if ((h = clixon_client_init("$cfg")) == NULL) if ((h = clixon_client_init("$cfg")) == NULL)
return -1; return -1;
/* Make a connection over netconf or ssh/netconf */ /* 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; return -1;
/* Here are read functions depending on an example YANG /* Here are read functions depending on an example YANG
* (Need an example YANG and XML input to confd) * (Need an example YANG and XML input to confd)
*/ */