diff --git a/CHANGELOG.md b/CHANGELOG.md index 65086bd3..a9dabbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,11 @@ Users may have to change how they access the system ### Minor changes +* Corrected client session handling to make internal IPC socket persistent + * Applies to cli/netconf/restconf/client-api code + * Previous behaviour: + * Close socket after each rpc, but now keeps the socket open until the client terminates + * Kept locks over socket life-cycle, but according to RFC 6241 7.5 a lock should be relaeased when session ends * Restconf evhtp using network namespaces implemented * Added validation of clixon-restconf.yang: server-key-path and server-cert-path must be present if ssl enabled. * Only if `CLICON_BACKEND_RESTCONF_PROCESS` is true diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 6fb96f1d..81e98e3e 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -91,7 +91,7 @@ backend_terminate(clicon_handle h) yang_stmt *yspec; char *pidfile = clicon_backend_pidfile(h); int sockfamily = clicon_sock_family(h); - char *sockpath = clicon_sock(h); + char *sockpath = clicon_sock_str(h); cxobj *x; struct stat st; int ss; @@ -552,6 +552,36 @@ restconf_pseudo_process_reg(clicon_handle h, return retval; } +/* Debug timer */ +int +backend_timer_setup(int fd, + void *arg) +{ + int retval = -1; + clicon_handle h = (clicon_handle)arg; + struct timeval now; + struct timeval t; + struct timeval t1 = {10, 0}; + + clicon_debug(1, "%s", __FUNCTION__); + gettimeofday(&now, NULL); + + backend_client_print(h, stderr); + xmldb_print(h, stderr); + fprintf(stderr, "\n"); + + /* Initiate new timer */ + timeradd(&now, &t1, &t); + if (clixon_event_reg_timeout(t, + backend_timer_setup, /* this function */ + h, /* clicon handle */ + "backend timer setup") < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! usage */ static void @@ -559,7 +589,7 @@ usage(clicon_handle h, char *argv0) { char *plgdir = clicon_backend_dir(h); - char *confsock = clicon_sock(h); + char *confsock = clicon_sock_str(h); char *confpid = clicon_backend_pidfile(h); char *group = clicon_sock_group(h); @@ -829,7 +859,7 @@ main(int argc, goto done; } sockfamily = clicon_sock_family(h); - if ((sock = clicon_sock(h)) == NULL){ + if ((sock = clicon_sock_str(h)) == NULL){ clicon_err(OE_FATAL, 0, "sock not set"); goto done; } @@ -1132,7 +1162,9 @@ main(int argc, /* Start session-id for clients */ clicon_session_id_set(h, 0); - + if (clicon_debug_get() && + backend_timer_setup(0, h) < 0) + goto done; if (stream_timer_setup(0, h) < 0) goto done; if (clixon_event_loop() < 0) diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c index 68f9b4b0..4e95b993 100644 --- a/apps/backend/backend_socket.c +++ b/apps/backend/backend_socket.c @@ -198,7 +198,7 @@ backend_socket_init(clicon_handle h) { char *sock; /* unix path or ip address string */ - if ((sock = clicon_sock(h)) == NULL){ + if ((sock = clicon_sock_str(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); return -1; } diff --git a/lib/clixon/clixon_client.h b/lib/clixon/clixon_client.h index d2524117..f21208a7 100644 --- a/lib/clixon/clixon_client.h +++ b/lib/clixon/clixon_client.h @@ -46,7 +46,7 @@ typedef void *clixon_client_handle; typedef enum { CLIXON_CLIENT_IPC, /* Internal IPC API, only experimental use */ CLIXON_CLIENT_NETCONF, /* External Netconf */ - CLIXON_CLIENT_SSH /* External Netconf over SSH */ + CLIXON_CLIENT_SSH /* NYI External Netconf over SSH */ } clixon_client_type; /* @@ -68,6 +68,9 @@ int clixon_client_get_uint16(clixon_client_handle ch, uint16_t *rval, const ch int clixon_client_get_uint32(clixon_client_handle ch, uint32_t *rval, const char *xnamespace, const char *xpath); int clixon_client_get_uint64(clixon_client_handle ch, uint64_t *rval, const char *xnamespace, const char *xpath); +/* Access functions */ +int clixon_client_socket_get(clixon_client_handle ch); + #ifdef __cplusplus } #endif diff --git a/lib/clixon/clixon_data.h b/lib/clixon/clixon_data.h index b26472a0..c9e6f1e4 100644 --- a/lib/clixon/clixon_data.h +++ b/lib/clixon/clixon_data.h @@ -102,10 +102,14 @@ int clicon_username_set(clicon_handle h, void *username); enum startup_status clicon_startup_status_get(clicon_handle h); int clicon_startup_status_set(clicon_handle h, enum startup_status status); -/* Set and get socket fd (ie backend server socket / restconf fcgx socket */ +/* Set and get server socket fd (ie backend server socket / restconf fcgi socket */ int clicon_socket_get(clicon_handle h); int clicon_socket_set(clicon_handle h, int s); +/* Set and get client socket fd (ie client cli / netconf / restconf / client-api socket */ +int clicon_client_socket_get(clicon_handle h); +int clicon_client_socket_set(clicon_handle h, int s); + /*! Set and get module state full and brief cached tree */ cxobj *clicon_modst_cache_get(clicon_handle h, int brief); int clicon_modst_cache_set(clicon_handle h, int brief, cxobj *xms); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 68df4064..3d341450 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -189,7 +189,7 @@ static inline int clicon_cli_tab_mode(clicon_handle h){ static inline char *clicon_cli_model_treename(clicon_handle h){ return clicon_option_str(h, "CLICON_CLI_MODEL_TREENAME"); } -static inline char *clicon_sock(clicon_handle h){ +static inline char *clicon_sock_str(clicon_handle h){ return clicon_option_str(h, "CLICON_SOCK"); } static inline char *clicon_sock_group(clicon_handle h){ diff --git a/lib/clixon/clixon_proc.h b/lib/clixon/clixon_proc.h index 68770ef5..ba3e56a2 100644 --- a/lib/clixon/clixon_proc.h +++ b/lib/clixon/clixon_proc.h @@ -49,8 +49,8 @@ typedef int (proc_cb_t)(clicon_handle h, process_entry_t *pe, char **operation); /* * Prototypes */ -int clixon_proc_socket(char **argv, pid_t *pid, int *fdin, int *fdout); -int clixon_proc_socket_close(pid_t pid, int fdin, int fdout); +int clixon_proc_socket(char **argv, 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_register(clicon_handle h, const char *name, const char *netns, proc_cb_t *callback, char **argv, int argc); int clixon_process_delete_all(clicon_handle h); diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 2eff72bc..ef0fc872 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -83,9 +83,9 @@ int clicon_rpc_connect_inet(clicon_handle h, uint16_t port, int *sock0); -int clicon_rpc(int fdin, int fdout, struct clicon_msg *msg, char **xret); +int clicon_rpc(int sock, struct clicon_msg *msg, char **xret); -int clicon_rpc1(int fdin, int fdout, cbuf *msgin, cbuf *msgret); +int clicon_rpc1(int sock, cbuf *msgin, cbuf *msgret); int clicon_msg_send(int s, struct clicon_msg *msg); diff --git a/lib/src/clixon_client.c b/lib/src/clixon_client.c index 4b8d7faa..c7e90ff2 100644 --- a/lib/src/clixon_client.c +++ b/lib/src/clixon_client.c @@ -84,9 +84,9 @@ struct clixon_client_handle{ uint32_t cch_magic; /* magic number */ clixon_client_type cch_type; /* Clixon socket type */ - int cch_fdin; /* Input file descriptor */ - int cch_fdout; /* Output file descriptor, Only applies for NETCONF/SSH */ + int cch_socket; /* Input/output socket */ int cch_pid; /* Sub-process-id Only applies for NETCONF/SSH */ + int cch_locked; /* State variable: 1 means locked */ }; /*! Check struct magic number for sanity checks @@ -104,10 +104,9 @@ clixon_client_handle_check(clixon_client_handle ch) } /*! 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, or NULL for default + * @retval h Clixon handler + * @retval NULL Error * @see clixon_client_close */ clixon_handle @@ -129,6 +128,7 @@ clixon_client_init(const char *config_file) } /*! Deallocate everything from client_init + * @param[in] h Clixon handle * @see clixon_client_init */ int @@ -139,12 +139,67 @@ clixon_client_terminate(clicon_handle h) return 0; } +/*! Send a lock request (internal) + * @param[in] sock Open socket + * @param[in] lock 0: unlock, 1: lock + * @param[in] db Datastore name + * @retval 0 OK + * @retval -1 Error + */ +static int +clixon_client_lock(int sock, + const int lock, + const char *db) +{ + int retval = -1; + cxobj *xret = NULL; + cxobj *xd; + cbuf *msg = NULL; + cbuf *msgret = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if (db == NULL){ + clicon_err(OE_XML, EINVAL, "Expected db"); + goto done; + } + if ((msg = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + if ((msgret = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + cprintf(msg, "" + "<%slock><%s/>", + NETCONF_BASE_NAMESPACE, + lock?"":"un", db, lock?"":"un"); + if (clicon_rpc1(sock, msg, msgret) < 0) + goto done; + if (clixon_xml_parse_string(cbuf_get(msgret), 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 */ + } + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); + if (msg) + cbuf_free(msg); + if (msgret) + cbuf_free(msgret); + return retval; +} + /*! Connect client to clixon backend according to config and return a socket - * @param[in] h Clixon handle - * @param[out] fdin Open netconf output file descr - * @param[out] fdout Open netconf output file descr. If socket same as fdin - * @retval cs Clixon session handler - * @retval NULL Error + * @param[in] h Clixon handle + * @param[in] socktype Type of socket, internal/external/netconf/ssh + * @retval ch Clixon session handler + * @retval NULL Error * @see clixon_client_disconnect Close the socket returned here */ clixon_client_handle @@ -170,9 +225,8 @@ clixon_client_connect(clicon_handle h, cch->cch_type = socktype; switch (socktype){ case CLIXON_CLIENT_IPC: - if (clicon_rpc_connect(h, &cch->cch_fdin) < 0) + if (clicon_rpc_connect(h, &cch->cch_socket) < 0) goto err; - cch->cch_fdout = cch->cch_fdin; break; case CLIXON_CLIENT_NETCONF: nr = 7; @@ -203,23 +257,29 @@ clixon_client_connect(clicon_handle h, } argv[i++] = NULL; assert(i==nr); - if (clixon_proc_socket(argv, &cch->cch_pid, &cch->cch_fdin, &cch->cch_fdout) < 0){ + if (clixon_proc_socket(argv, &cch->cch_pid, &cch->cch_socket) < 0){ goto err; } break; case CLIXON_CLIENT_SSH: 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; err: - free(cch); + if (cch) + clixon_client_disconnect(cch); cch = NULL; goto done; } /*! Connect client to clixon backend according to config and return a socket - * @param[in] cch Clixon client session handle + * @param[in] ch Clixon client session handle * @see clixon_client_connect where the handle is created * The handle is deallocated */ @@ -234,14 +294,17 @@ clixon_client_disconnect(clixon_client_handle ch) clicon_err(OE_XML, EINVAL, "Expected cch handle"); goto done; } + /* unlock (if locked) */ + if (cch->cch_locked) + ;// (void)clixon_client_lock(cch->cch_socket, 0, "running"); + switch(cch->cch_type){ case CLIXON_CLIENT_IPC: - close(cch->cch_fdin); + close(cch->cch_socket); break; case CLIXON_CLIENT_NETCONF: if (clixon_proc_socket_close(cch->cch_pid, - cch->cch_fdin, - cch->cch_fdout) < 0) + cch->cch_socket) < 0) goto done; break; case CLIXON_CLIENT_SSH: @@ -253,6 +316,11 @@ clixon_client_disconnect(clixon_client_handle ch) return retval; } +/*! Get the bottom-most leaf in an xml tree being a result of xpath + * @param[in] xtop Pointer to XML top-of-tree + * @param[out] xbotp Pointer to XML bottom node + * @retval 0 OK + */ static int clixon_xml_bottom(cxobj *xtop, cxobj **xbotp) @@ -283,14 +351,15 @@ clixon_xml_bottom(cxobj *xtop, /*! 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] sock Socket * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) - * @param[out] val String value + * @param[in] xpath XPath + * @param[out] xdata XML data tree (may or may not include the intended data) + * @retval 0 OK + * @retval -1 Error */ static int -clixon_client_get_xdata(int fdin, - int fdout, +clixon_client_get_xdata(int sock, const char *namespace, const char *xpath, cxobj **xdata) @@ -303,6 +372,7 @@ clixon_client_get_xdata(int fdin, const char *db = "running"; cvec *nsc = NULL; + clicon_debug(1, "%s", __FUNCTION__); if ((msg = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; @@ -327,7 +397,7 @@ clixon_client_get_xdata(int fdin, cprintf(msg, "/>"); } cprintf(msg, ""); - if (clicon_rpc1(fdin, fdout, msg, msgret) < 0) + if (clicon_rpc1(sock, msg, msgret) < 0) goto done; if (clixon_xml_parse_string(cbuf_get(msgret), YB_NONE, NULL, &xret, NULL) < 0) goto done; @@ -357,35 +427,61 @@ clixon_client_get_xdata(int fdin, return retval; } +/*! Generic get value of body + * @param[in] sock Open socket + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. + * @param[in] xpath XPath + * @param[out] val Output value + * @retval 0 OK + * @retval -1 Error + */ static int -clixon_client_get_val(int fdin, - int fdout, - const char *namespace, - const char *xpath, - char **val) +clixon_client_get_body_val(int sock, + const char *namespace, + const char *xpath, + char **val) { int retval = -1; cxobj *xdata = NULL; - cxobj *xobj; + cxobj *xobj = NULL; + clicon_debug(1, "%s", __FUNCTION__); if (val == NULL){ clicon_err(OE_XML, EINVAL, "Expected val"); goto done; } - if (clixon_client_get_xdata(fdin, fdout, namespace, xpath, &xdata) < 0) + if (clixon_client_get_xdata(sock, namespace, xpath, &xdata) < 0) goto done; + if (xdata == NULL){ + clicon_err(OE_XML, ENODATA, "No xml obj found"); + goto done; + } + /* Is this an error, maybe an "unset" retval ? */ + if (xml_child_nr_type(xdata, CX_ELMNT) == 0){ + clicon_err(OE_XML, ENODATA, "Value not found"); + goto done; + } if (clixon_xml_bottom(xdata, &xobj) < 0) goto done; if (xobj == NULL){ - clicon_err(OE_XML, EFAULT, "No bottom xml found"); + clicon_err(OE_XML, ENODATA, "No xml value found"); goto done; } *val = xml_body(xobj); retval = 0; done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } +/*! Client-api get boolean + * @param[in] ch Clixon client handle + * @param[out] rval Return value + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error + */ int clixon_client_get_bool(clixon_client_handle ch, int *rval, @@ -400,9 +496,8 @@ clixon_client_get_bool(clixon_client_handle ch, uint8_t val0=0; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; if ((ret = parse_bool(val, &val0, &reason)) < 0){ clicon_err(OE_XML, errno, "parse_bool"); @@ -420,12 +515,14 @@ clixon_client_get_bool(clixon_client_handle ch, return retval; } -/*! Get string using get-config - * @param[in] sock Stream socket +/*! Client-api get string + * @param[in] ch Clixon client handle * @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) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error */ int clixon_client_get_str(clixon_client_handle ch, @@ -439,9 +536,8 @@ clixon_client_get_str(clixon_client_handle ch, char *val = NULL; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; strncpy(rval, val, n-1); rval[n-1]= '\0'; @@ -450,6 +546,14 @@ clixon_client_get_str(clixon_client_handle ch, return retval; } +/*! Client-api get uint8 + * @param[in] ch Clixon client handle + * @param[out] rval Return value + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error + */ int clixon_client_get_uint8(clixon_client_handle ch, uint8_t *rval, @@ -463,9 +567,8 @@ clixon_client_get_uint8(clixon_client_handle ch, int ret; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; if ((ret = parse_uint8(val, rval, &reason)) < 0){ clicon_err(OE_XML, errno, "parse_bool"); @@ -482,6 +585,14 @@ clixon_client_get_uint8(clixon_client_handle ch, return retval; } +/*! Client-api get uint16 + * @param[in] ch Clixon client handle + * @param[out] rval Return value + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error + */ int clixon_client_get_uint16(clixon_client_handle ch, uint16_t *rval, @@ -495,9 +606,8 @@ clixon_client_get_uint16(clixon_client_handle ch, int ret; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; if ((ret = parse_uint16(val, rval, &reason)) < 0){ clicon_err(OE_XML, errno, "parse_bool"); @@ -514,6 +624,14 @@ clixon_client_get_uint16(clixon_client_handle ch, return retval; } +/*! Client-api get uint32 + * @param[in] ch Clixon client handle + * @param[out] rval Return value + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error + */ int clixon_client_get_uint32(clixon_client_handle ch, uint32_t *rval, @@ -527,10 +645,13 @@ clixon_client_get_uint32(clixon_client_handle ch, int ret; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; + if (val == NULL){ + clicon_err(OE_XML, EFAULT, "val is NULL"); + goto done; + } if ((ret = parse_uint32(val, rval, &reason)) < 0){ clicon_err(OE_XML, errno, "parse_bool"); goto done; @@ -541,11 +662,20 @@ clixon_client_get_uint32(clixon_client_handle ch, } retval = 0; done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (reason) free(reason); return retval; } +/*! Client-api get uint64 + * @param[in] ch Clixon client handle + * @param[out] rval Return value + * @param[in] namespace Default namespace used for non-prefixed entries in xpath. (Alt use nsc) + * @param[in] xpath XPath + * @retval 0 OK + * @retval -1 Error + */ int clixon_client_get_uint64(clixon_client_handle ch, uint64_t *rval, @@ -559,9 +689,8 @@ clixon_client_get_uint64(clixon_client_handle ch, int ret; clicon_debug(1, "%s", __FUNCTION__); - if (clixon_client_get_val(cch->cch_fdin, - cch->cch_fdout, - namespace, xpath, &val) < 0) + if (clixon_client_get_body_val(cch->cch_socket, + namespace, xpath, &val) < 0) goto done; if ((ret = parse_uint64(val, rval, &reason)) < 0){ clicon_err(OE_XML, errno, "parse_bool"); @@ -577,3 +706,17 @@ clixon_client_get_uint64(clixon_client_handle ch, free(reason); return retval; } + +/* Access functions */ +/*! Client-api get uint64 + * @param[in] ch Clixon client handle + * @retval s Open socket + * @retval -1 No/closed socket + */ +int +clixon_client_socket_get(clixon_client_handle ch) +{ + struct clixon_client_handle *cch = chandle(ch); + + return cch->cch_socket; +} diff --git a/lib/src/clixon_data.c b/lib/src/clixon_data.c index 1930b33c..39918876 100644 --- a/lib/src/clixon_data.c +++ b/lib/src/clixon_data.c @@ -520,7 +520,7 @@ clicon_startup_status_set(clicon_handle h, return 0; } -/*! Get socket fd (ie backend server socket / restconf fcgx socket) +/*! Get server socket fd (ie backend server socket / restconf fcgi socket) * @param[in] h Clicon handle * @retval -1 No open socket * @retval s Socket @@ -536,7 +536,7 @@ clicon_socket_get(clicon_handle h) return *(int*)p; } -/*! Set socket fd (ie backend server socket / restconf fcgx socket) +/*! Set server socket fd (ie backend server socket / restconf fcgi socket) * @param[in] h Clicon handle * @param[in] s Open socket (or -1 to close) * @retval 0 OK @@ -553,6 +553,39 @@ clicon_socket_set(clicon_handle h, return clicon_hash_add(cdat, "socket", &s, sizeof(int))==NULL?-1:0; } +/*! Get client socket fd (ie client cli / netconf / restconf / client-api socket + * @param[in] h Clicon handle + * @retval -1 No open socket + * @retval s Socket + */ +int +clicon_client_socket_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = clicon_hash_value(cdat, "client-socket", NULL)) == NULL) + return -1; + return *(int*)p; +} + +/*! Set client socket fd (ie client cli / netconf / restconf / client-api socket + * @param[in] h Clicon handle + * @param[in] s Open socket (or -1 to close) + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_client_socket_set(clicon_handle h, + int s) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (s == -1) + return clicon_hash_del(cdat, "client-socket"); + return clicon_hash_add(cdat, "client-socket", &s, sizeof(int))==NULL?-1:0; +} + /*! Get module state cache * @param[in] h Clicon handle * @param[in] brief 0: Full module state tree, 1: Brief tree (datastore) diff --git a/lib/src/clixon_netns.c b/lib/src/clixon_netns.c index 8743b6e6..707c35f9 100644 --- a/lib/src/clixon_netns.c +++ b/lib/src/clixon_netns.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "clixon_err.h" @@ -158,6 +159,14 @@ create_socket(struct sockaddr *sa, return retval; } +/*! Fork a child, create and bind a socket in a separate network namespace and send back to parent + * + * @param[in] netns Network namespace + * @param[in] sa Socketaddress + * @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len + * @param[in] backlog Listen backlog, queie of pending connections + * @param[out] sock Server socket (bound for accept) + */ int fork_netns_socket(const char *netns, struct sockaddr *sa, @@ -166,8 +175,9 @@ fork_netns_socket(const char *netns, int *sock) { int retval = -1; - int sp[2] = {0,}; + int sp[2] = {-1, -1}; pid_t child; + int status = 0; if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, sp) < 0){ clicon_err(OE_UNIX, errno, "socketpair"); @@ -209,7 +219,8 @@ fork_netns_socket(const char *netns, if (get_sock(sp[0], sock) < 0) goto done; close(sp[0]); - retval = 0; + if(waitpid(child, &status, 0) == child) + retval = WEXITSTATUS(status); done: return retval; } diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c index d2a5c3fb..b574e188 100644 --- a/lib/src/clixon_proc.c +++ b/lib/src/clixon_proc.c @@ -105,23 +105,21 @@ clixon_proc_sigint(int sig) kill(_clicon_proc_child, SIGINT); } -/*! +/*! 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. - * @note need to cleanup and wait on sub-process + * @see clixon_proc_socket_close close sockets, kill child and wait for child termination */ int clixon_proc_socket(char **argv, pid_t *pid, - int *fdin, - int *fdout) + int *sock) { int retval = -1; - int p2c[2] = { -1, -1 }; /* parent->child */ - int c2p[2] = { -1, -1 }; /* child->parent */ + int sp[2] = {-1, -1}; pid_t child; sigfn_t oldhandler = NULL; sigset_t oset; @@ -131,15 +129,10 @@ clixon_proc_socket(char **argv, clicon_err(OE_UNIX, EINVAL, "argv is NULL"); goto done; } - if (pipe2(p2c, O_DIRECT) == -1){ /* parent->child */ - clicon_err(OE_UNIX, errno, "pipe"); + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, sp) < 0){ + clicon_err(OE_UNIX, errno, "socketpair"); goto done; } - if (pipe2(c2p, O_DIRECT) == -1){ /* child->parent */ - clicon_err(OE_UNIX, errno, "pipe"); - goto done; - } - // clicon_debug(1, "%s p2c: %d -> %d c2p: %d <- %d", __FUNCTION__, p2c[1], p2c[0], c2p[0], c2p[1]); sigprocmask(0, NULL, &oset); set_signal(SIGINT, clixon_proc_sigint, &oldhandler); sig++; @@ -152,24 +145,18 @@ clixon_proc_socket(char **argv, clicon_signal_unblock(0); signal(SIGTSTP, SIG_IGN); + close(sp[0]); close(0); - - if (dup2(p2c[0], STDIN_FILENO) < 0){ - // if (dup(p2c[0]) < 0){ + if (dup2(sp[1], STDIN_FILENO) < 0){ perror("dup2"); return -1; } - close(p2c[1]); - close(p2c[0]); - close(1); - if (dup2(c2p[1], STDOUT_FILENO) < 0){ - // if (dup(c2p[1]) < 0){ + if (dup2(sp[1], STDOUT_FILENO) < 0){ perror("dup2"); return -1; } - close(c2p[1]); - close(c2p[0]); + close(sp[1]); if (execvp(argv[0], argv) < 0){ perror("execvp"); @@ -178,11 +165,9 @@ clixon_proc_socket(char **argv, exit(-1); /* Shouldnt reach here */ } /* Parent */ - close(p2c[0]); - close(c2p[1]); + close(sp[1]); *pid = child; - *fdout = p2c[1]; - *fdin = c2p[0]; + *sock = sp[0]; retval = 0; done: if (sig){ /* Restore sigmask and fn */ @@ -192,18 +177,18 @@ clixon_proc_socket(char **argv, return retval; } +/*! + * @see clixon_proc_socket which creates the child and sockets closed and killed here + */ int clixon_proc_socket_close(pid_t pid, - int fdin, - int fdout) + int sock) { - int retval = -1; - int status; + int retval = -1; + int status; - if (fdin != -1) - close(fdin); - if (fdout != -1) - close(fdout); /* Usually kills */ + if (sock != -1) + close(sock); /* usually kills */ kill(pid, SIGTERM); // usleep(100000); /* Wait for child to finish */ if(waitpid(pid, &status, 0) == pid) diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index d2a23873..663bf51f 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -435,6 +435,7 @@ clicon_msg_rcv1(int s, int poll; clicon_debug(1, "%s", __FUNCTION__); + *eof = 0; memset(buf, 0, sizeof(buf)); while (1){ if ((len = read(s, buf, sizeof(buf))) < 0){ @@ -446,7 +447,7 @@ clicon_msg_rcv1(int s, } } /* read */ if (len == 0){ /* EOF */ - // cc_closed++; + *eof = 1; close(s); goto ok; } @@ -473,8 +474,6 @@ clicon_msg_rcv1(int s, retval = 0; done: clicon_debug(1, "%s done", __FUNCTION__); - // if (cc_closed) - // retval = -1; return retval; } @@ -597,31 +596,29 @@ clicon_rpc_connect_inet(clicon_handle h, * errno set to ENOTCONN which means that socket is now closed probably * due to remote peer disconnecting. The caller may have to do something,... * - * @param[in] fdin Input file descriptor - * @param[in] fdout Output file descriptor (for socket same as fdin) + * @param[in] sock Socket / file descriptor * @param[in] msg CLICON msg data structure. It has fixed header and variable body. * @param[out] xret Returned data as netconf xml tree. * @retval 0 OK * @retval -1 Error */ int -clicon_rpc(int fdin, - int fdout, - struct clicon_msg *msg, - char **ret) +clicon_rpc(int sock, + struct clicon_msg *msg, + char **ret) { int retval = -1; struct clicon_msg *reply = NULL; int eof; char *data = NULL; - if (clicon_msg_send(fdout, msg) < 0) + if (clicon_msg_send(sock, msg) < 0) goto done; - if (clicon_msg_rcv(fdin, &reply, &eof) < 0) + if (clicon_msg_rcv(sock, &reply, &eof) < 0) goto done; if (eof){ clicon_err(OE_PROTO, ESHUTDOWN, "Unexpected close of CLICON_SOCK. Clixon backend daemon may have crashed."); - close(fdin); /* assume socket */ + close(sock); /* assume socket */ errno = ESHUTDOWN; goto done; } @@ -645,8 +642,7 @@ clicon_rpc(int fdin, * errno set to ENOTCONN which means that socket is now closed probably * due to remote peer disconnecting. The caller may have to do something,... * - * @param[in] fdin Input file descriptor - * @param[in] fdout Output file descriptor (for socket same as fdin) + * @param[in] sock Socket / file descriptor * @param[in] msgin CLICON msg data structure. It has fixed header and variable body. * @param[out] msgret Returned data as netconf xml tree. * @retval 0 OK @@ -654,22 +650,21 @@ clicon_rpc(int fdin, * see clicon_rpc using clicon_msg */ int -clicon_rpc1(int fdin, - int fdout, - cbuf *msg, - cbuf *msgret) +clicon_rpc1(int sock, + cbuf *msg, + cbuf *msgret) { int retval = -1; int eof; clicon_debug(1, "%s", __FUNCTION__); - if (clicon_msg_send1(fdout, msg) < 0) + if (clicon_msg_send1(sock, msg) < 0) goto done; - if (clicon_msg_rcv1(fdin, msgret, &eof) < 0) + if (clicon_msg_rcv1(sock, msgret, &eof) < 0) goto done; if (eof){ clicon_err(OE_PROTO, ESHUTDOWN, "Unexpected close of CLICON_SOCK. Clixon backend daemon may have crashed."); - close(fdin); /* assume socket */ + close(sock); errno = ESHUTDOWN; goto done; } diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 07012227..57bba49d 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -86,20 +86,20 @@ */ int clicon_rpc_connect(clicon_handle h, - int *sock0) + int *sockp) { int retval = -1; - char *sock = NULL; + char *sockstr = NULL; int port; - if ((sock = clicon_sock(h)) == NULL){ + if ((sockstr = clicon_sock_str(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); goto done; } /* What to do if inet socket? */ switch (clicon_sock_family(h)){ case AF_UNIX: - if (clicon_rpc_connect_unix(h, sock, sock0) < 0){ + if (clicon_rpc_connect_unix(h, sockstr, sockp) < 0){ #if 0 if (errno == ESHUTDOWN) /* Maybe could reconnect on a higher layer, but lets fail @@ -118,7 +118,7 @@ clicon_rpc_connect(clicon_handle h, clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set"); goto done; } - if (clicon_rpc_connect_inet(h, sock, port, sock0) < 0) + if (clicon_rpc_connect_inet(h, sockstr, port, sockp) < 0) goto done; break; } @@ -152,9 +152,12 @@ clicon_rpc_msg(clicon_handle h, #endif clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body); /* Create a socket and connect to it, either UNIX, IPv4 or IPv6 per config options */ - if (clicon_rpc_connect(h, &s) < 0) - goto done; - if (clicon_rpc(s, s, msg, &retdata) < 0) + if ((s = clicon_client_socket_get(h)) < 0){ + if (clicon_rpc_connect(h, &s) < 0) + goto done; + clicon_client_socket_set(h, s); + } + if (clicon_rpc(s, msg, &retdata) < 0) goto done; clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata); @@ -177,8 +180,6 @@ clicon_rpc_msg(clicon_handle h, } retval = 0; done: - if (s != -1) - close(s); if (retdata) free(retdata); if (xret) @@ -308,7 +309,6 @@ clicon_rpc_netconf_xml(clicon_handle h, return retval; } - /*! Get database configuration * Same as clicon_proto_change just with a cvec instead of lvec * @param[in] h CLICON handle @@ -786,10 +786,12 @@ clicon_rpc_get(clicon_handle h, return retval; } -/*! Close a (user) session +/*! Send a close a netconf user session. Socket is also closed if still open * @param[in] h CLICON handle * @retval 0 OK * @retval -1 Error and logged to syslog + * Session is implicitly created in eg clicon_rpc_netconf + * @note Maybe separate closing session and closing socket. */ int clicon_rpc_close_session(clicon_handle h) @@ -800,6 +802,7 @@ clicon_rpc_close_session(clicon_handle h) cxobj *xerr; char *username; uint32_t session_id; + int s; if (session_id_check(h, &session_id) < 0) goto done; @@ -810,6 +813,10 @@ clicon_rpc_close_session(clicon_handle h) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; + if ((s = clicon_client_socket_get(h)) >= 0){ + close(s); + clicon_client_socket_set(h, -1); + } if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ clixon_netconf_error(xerr, "Close session", NULL); goto done; diff --git a/test/test_client.sh b/test/test_client.sh index ae3c74be..14bcbab6 100755 --- a/test/test_client.sh +++ b/test/test_client.sh @@ -15,6 +15,7 @@ fyang=$dir/example-client.yang cfile=$dir/example-client.c pdir=$dir/plugin app=$dir/clixon-app +debug=0 if [ ! -d $pdir ]; then mkdir $pdir @@ -68,19 +69,21 @@ cat< $cfile #include #include #include +#include // debug -#include +#include // debug #include int main(int argc, char **argv) { + int retval = -1; clixon_handle h = NULL; /* clixon handle */ clixon_client_handle ch = NULL; /* clixon client handle */ -// clicon_log_init("client", LOG_DEBUG, CLICON_LOG_STDERR); // debug -// clicon_debug_init(1, NULL); // debug + clicon_log_init("client", LOG_DEBUG, CLICON_LOG_STDERR); // debug + clicon_debug_init($debug, NULL); // debug /* Provide a clixon config-file, get a clixon handle */ if ((h = clixon_client_init("$cfg")) == NULL) @@ -94,12 +97,15 @@ main(int argc, { uint32_t u = 0; if (clixon_client_get_uint32(ch, &u, "urn:example:clixon-client", "/table/parameter[name='a']/value") < 0) - return -1; + goto done; printf("%u\n", u); /* for test output */ } + retval = 0; + done: clixon_client_disconnect(ch); clixon_client_terminate(h); - return 0; + printf("done\n"); /* for test output */ + return retval; } EOF diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 97184000..60987b2d 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -236,6 +236,9 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" +new "netconf lock/unlock/lock" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>]]>]]>]]>]]>" "^]]>]]>]]>]]>]]>]]>$" + new "netconf lock/lock" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>]]>]]>" "^]]>]]>protocollock-denied" diff --git a/test/test_restconf_netns.sh b/test/test_restconf_netns.sh index eb0ec0ef..46ab49ef 100755 --- a/test/test_restconf_netns.sh +++ b/test/test_restconf_netns.sh @@ -167,7 +167,7 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' http: # Negative new "restconf get config on wrong port in netns:$netns" -expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://$vaddr:8888/restconf/data/clixon-example:table)" 7 +expectpart "$(sudo ip netns exec $netns curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://$vaddr:8888/restconf/data/clixon-example:table 2> /dev/null)" 7 if [ $RC -ne 0 ]; then new "Kill restconf daemon" @@ -192,4 +192,3 @@ new "endtest" endtest rm -rf $dir - diff --git a/util/clixon_util_socket.c b/util/clixon_util_socket.c index 038aeacd..75a8992e 100644 --- a/util/clixon_util_socket.c +++ b/util/clixon_util_socket.c @@ -176,7 +176,7 @@ main(int argc, else if (clicon_rpc_connect_inet(h, sockpath, 4535, &s) < 0) goto done; - if (clicon_rpc(s, s, msg, &retdata) < 0) + if (clicon_rpc(s, msg, &retdata) < 0) goto done; close(s); fprintf(stdout, "%s\n", retdata);