Added proc socket and text proto rpc

This commit is contained in:
Olof hagsand 2021-01-22 14:41:47 +01:00
parent 68371d4fc5
commit 94c6bd9c49
9 changed files with 214 additions and 290 deletions

View file

@ -64,7 +64,7 @@ echo "$STORE" > /usr/local/var/example/running_db
# - test_install.sh since you dont have the make environment
cat <<EOF > /usr/local/bin/test/site.sh
# Add your local site specific env variables (or tests) here.
SKIPLIST="test_api.sh test_c++.sh test_yangmodels.sh test_openconfig.sh test_install.sh test_privileges.sh"
SKIPLIST="test_api.sh test_client.sh test_c++.sh test_yangmodels.sh test_openconfig.sh test_install.sh test_privileges.sh"
RCPROTO=https
#IETFRFC=
EOF

View file

@ -88,7 +88,7 @@ EOF
# - test_order.sh XXX this is a bug need debugging
cat <<EOF > /usr/local/bin/test/site.sh
# Add your local site specific env variables (or tests) here.
SKIPLIST="test_api.sh test_c++.sh test_yangmodels.sh test_openconfig.sh test_install.sh test_privileges.sh"
SKIPLIST="test_api.sh test_client.sh test_c++.sh test_yangmodels.sh test_openconfig.sh test_install.sh test_privileges.sh"
#IETFRFC=
EOF

View file

@ -46,6 +46,7 @@
Note that its contents is different dependending on if invoked from a
cli/backend/netconf or other plugin. But this is hidden under-the-hood.
*/
typedef void *clixon_handle;
typedef void *clicon_handle;
/* The dynamicically loadable plugin object handle (should be in clixon_plugin.h) */

View file

@ -49,9 +49,9 @@ typedef int (proc_cb_t)(clicon_handle h, process_entry_t *pe, char **operation);
/*
* Prototypes
*/
int clixon_proc_run(char **argv, void (outcb)(char *), int doerr);
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_background(char **argv, const char *netns, pid_t *pid);
int clixon_proc_daemon(char **argv, 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);
int clixon_process_operation(clicon_handle h, const char *name, char *op, const int wrapit, uint32_t *pid);

View file

@ -83,12 +83,18 @@ int clicon_rpc_connect_inet(clicon_handle h,
uint16_t port,
int *sock0);
int clicon_rpc(int s, struct clicon_msg *msg, char **xret);
int clicon_rpc(int fdin, int fdout, struct clicon_msg *msg, char **xret);
int clicon_rpc1(int fdin, int fdout, cbuf *msgin, cbuf *msgret);
int clicon_msg_send(int s, struct clicon_msg *msg);
int clicon_msg_send1(int s, cbuf *cb);
int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof);
int clicon_msg_rcv1(int s, cbuf *cb, int *eof);
int send_msg_notify_xml(clicon_handle h, int s, cxobj *xev);
int send_msg_reply(int s, char *data, uint32_t datalen);

View file

@ -105,35 +105,23 @@ clixon_proc_sigint(int sig)
kill(_clicon_proc_child, SIGINT);
}
/*! Fork a child process, setup a pipe between parent and child.
* Allowing parent to read the output of the child.
* @param[in] doerr If non-zero, stderr will be directed to the pipe as well.
* The pipe for the parent to write
* to the child is closed and cannot be used.
*
* When child process is done with the pipe setup, execute the specified
* command, execv(argv[0], argv).
*
* When parent is done with the pipe setup it will read output from the child
* until eof. The read output will be sent to the specified output callback,
* 'outcb' function.
*
/*!
* @param[in] argv NULL-terminated Argument vector
* @param[in] outcb
* @param[in] doerr
* @retval number Matches (processes affected).
* @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
*/
int
clixon_proc_run(char **argv,
void (outcb)(char *),
int doerr)
clixon_proc_socket(char **argv,
pid_t *pid,
int *fdin,
int *fdout)
{
int retval = -1;
char buf[512];
int outfd[2] = { -1, -1 };
int n;
int status;
int p2c[2] = { -1, -1 }; /* parent->child */
int c2p[2] = { -1, -1 }; /* child->parent */
pid_t child;
sigfn_t oldhandler = NULL;
sigset_t oset;
@ -143,66 +131,60 @@ clixon_proc_run(char **argv,
clicon_err(OE_UNIX, EINVAL, "argv is NULL");
goto done;
}
if (pipe(outfd) == -1){
if (pipe2(p2c, O_DIRECT) == -1){ /* parent->child */
clicon_err(OE_UNIX, errno, "pipe");
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++;
if ((child = fork()) < 0) {
clicon_err(OE_UNIX, errno, "fork");
goto done;
}
if (child == 0) { /* Child */
/* Unblock all signals except TSTP */
clicon_signal_unblock(0);
signal(SIGTSTP, SIG_IGN);
close(outfd[0]); /* Close unused read ends */
outfd[0] = -1;
close(0);
/* Divert stdout and stderr to pipes */
dup2(outfd[1], STDOUT_FILENO);
if (doerr)
dup2(outfd[1], STDERR_FILENO);
execvp(argv[0], argv);
perror("execvp"); /* Shouldnt reach here */
exit(-1);
if (dup2(p2c[0], STDIN_FILENO) < 0){
// if (dup(p2c[0]) < 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){
perror("dup2");
return -1;
}
close(c2p[1]);
close(c2p[0]);
if (execvp(argv[0], argv) < 0){
perror("execvp");
return -1;
}
exit(-1); /* Shouldnt reach here */
}
/* Parent */
/* Close unused write ends */
close(outfd[1]);
outfd[1] = -1;
/* Read from pipe */
while ((n = read(outfd[0], buf, sizeof(buf)-1)) != 0) {
if (n < 0) {
if (errno == EINTR)
continue;
break;
}
buf[n] = '\0';
/* Pass read data to callback function is defined */
if (outcb)
outcb(buf);
}
/* Wait for child to finish */
if(waitpid(child, &status, 0) == child)
retval = WEXITSTATUS(status);
close(p2c[0]);
close(c2p[1]);
*pid = child;
*fdout = p2c[1];
*fdin = c2p[0];
retval = 0;
done:
/* Clean up all pipes */
if (outfd[0] != -1)
close(outfd[0]);
if (outfd[1] != -1)
close(outfd[1]);
if (sig){ /* Restore sigmask and fn */
sigprocmask(SIG_SETMASK, &oset, NULL);
set_signal(SIGINT, oldhandler, NULL);
@ -210,6 +192,25 @@ clixon_proc_run(char **argv,
return retval;
}
int
clixon_proc_socket_close(pid_t pid,
int fdin,
int fdout)
{
int retval = -1;
int status;
if (fdin != -1)
close(fdin);
if (fdout != -1)
close(fdout); /* Usually kills */
kill(pid, SIGTERM);
// usleep(100000); /* Wait for child to finish */
if(waitpid(pid, &status, 0) == pid)
retval = WEXITSTATUS(status);
return retval;
}
/*! Fork and exec a sub-process, let it run and return pid
*
* @param[in] argv NULL-terminated Argument vector
@ -217,7 +218,6 @@ clixon_proc_run(char **argv,
* @param[out] pid
* @retval 0 OK
* @retval -1 Error.
* @see clixon_proc_daemon
* @note SIGCHLD is set to IGN here. Maybe it should be done in main?
*/
int
@ -305,79 +305,6 @@ clixon_proc_background(char **argv,
return retval;
}
/*! Double fork and exec a sub-process as a daemon, let it run and return pid
*
* @param[in] argv NULL-terminated Argument vector
* @param[out] pid
* @retval 0 OK
* @retval -1 Error.
* @see clixon_proc_background
*/
int
clixon_proc_daemon(char **argv,
pid_t *pid0)
{
int retval = -1;
int status;
pid_t child;
pid_t pid;
int i;
struct rlimit rlim = {0, };
FILE *fpid = NULL;
if (argv == NULL){
clicon_err(OE_UNIX, EINVAL, "argv is NULL");
goto done;
}
if ((fpid = tmpfile()) == NULL){
clicon_err(OE_UNIX, errno, "tmpfile");
goto done;
}
if ((child = fork()) < 0) {
clicon_err(OE_UNIX, errno, "fork");
goto done;
}
if (child == 0) { /* Child */
clicon_signal_unblock(0);
if ((pid = fork()) < 0) {
clicon_err(OE_UNIX, errno, "fork");
return -1;
}
if (pid == 0) { /* Grandchild, create new session */
setsid();
if (chdir("/") < 0){
clicon_err(OE_UNIX, errno, "chdirq");
exit(1);
}
/* Close open descriptors */
if ( ! getrlimit(RLIMIT_NOFILE, &rlim))
for (i = 0; i < rlim.rlim_cur; i++)
close(i);
if (execv(argv[0], argv) < 0) {
clicon_err(OE_UNIX, errno, "execv");
exit(1);
}
/* Not reached */
}
if (fprintf(fpid, "%ld\n", (long) pid) < 1){
clicon_err(OE_DAEMON, errno, "fprintf Could not write pid");
goto done;
}
fclose(fpid);
// waitpid(pid, &status2, 0);
exit(pid);
}
if (waitpid(child, &status, 0) > 0){
pidfile_get_fd(fpid, pid0);
retval = 0;
}
fclose(fpid);
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*--------------------------------------------------------------------------------*
* Process management: start/stop registered processes for internal use
*/

View file

@ -67,9 +67,7 @@
/* clicon */
#include "clixon_err.h"
#include "clixon_queue.h"
#ifdef CLIXON_PROTO_PLAIN
#include "clixon_event.h"
#endif
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_log.h"
@ -323,7 +321,7 @@ msg_dump(struct clicon_msg *msg)
return retval;
}
/*! Send a CLICON netconf message
/*! Send a CLICON netconf message using internal IPC message
* @param[in] s socket (unix or inet) to communicate with backend
* @param[out] msg CLICON msg data reply structure. Free with free()
*/
@ -337,20 +335,6 @@ clicon_msg_send(int s,
__FUNCTION__, ntohl(msg->op_len));
if (clicon_debug_get() > 2)
msg_dump(msg);
#ifdef CLIXON_PROTO_PLAIN
{
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "%s]]>]]>", (char*)&msg->op_body);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_CFG, errno, "atomicio");
clicon_log(LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno));
goto done;
}
}
#else
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohl(msg->op_len)) < 0){
clicon_err(OE_CFG, errno, "atomicio");
@ -358,123 +342,12 @@ clicon_msg_send(int s,
strerror(errno), ntohs(msg->op_len), msg->op_body);
goto done;
}
#endif /* CLIXON_PROTO_PLAIN */
retval = 0;
done:
return retval;
}
#ifdef CLIXON_PROTO_PLAIN
/*! Receive a message using plain ascii
* @see netconf_input_cb()
*/
static int
clicon_msg_rcv1(int s,
cbuf **cb1,
int *eof)
{
int retval = -1;
unsigned char buf[BUFSIZ];
int i;
int len;
cbuf *cb=NULL;
int xml_state = 0;
int poll;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
return retval;
}
memset(buf, 0, sizeof(buf));
while (1){
if ((len = read(s, buf, sizeof(buf))) < 0){
if (errno == ECONNRESET)
len = 0; /* emulate EOF */
else{
clicon_log(LOG_ERR, "%s: read: %s", __FUNCTION__, strerror(errno));
goto done;
}
} /* read */
if (len == 0){ /* EOF */
// cc_closed++;
close(s);
goto ok;
}
for (i=0; i<len; i++){
if (buf[i] == 0)
continue; /* Skip NULL chars (eg from terminals) */
cprintf(cb, "%c", buf[i]);
if (detect_endtag("]]>]]>",
buf[i],
&xml_state)) {
/* OK, we have an xml string from a client */
/* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
*cb1 = cb;
clicon_debug(2, "%s", cbuf_get(cb));
cb = NULL;
goto ok;
}
}
/* poll==1 if more, poll==0 if none */
if ((poll = clixon_event_poll(s)) < 0)
goto done;
if (poll == 0)
break; /* No data to read */
} /* while */
ok:
retval = 0;
done:
clicon_debug(1, "%s done", __FUNCTION__);
if (cb)
cbuf_free(cb);
// if (cc_closed)
// retval = -1;
return retval;
}
/*! Receive a message using plain ascii
* @note message is copied once too many
* @note session-id is made up
*/
static int
clicon_msg_rcv_plain(int s,
struct clicon_msg **msg0,
int *eof)
{
int retval = -1;
cbuf *cb = NULL;
size_t sz;
static int ii = 0;
struct clicon_msg *msg = NULL;
if (clicon_msg_rcv1(s, &cb, eof) < 0)
goto done;
if (cb == NULL){
clicon_err(OE_CFG, EFAULT, "unrecognized input");
*eof = 1;
goto ok;
}
sz = sizeof(struct clicon_msg) + cbuf_len(cb) + 1;
if ((msg = (struct clicon_msg *)malloc(sz)) == NULL){
clicon_err(OE_CFG, errno, "malloc");
goto done;
}
memset(msg, 0, sz);
msg->op_len = htonl(sz);
msg->op_id = htonl(ii++); /* XXX seesion-ids are randomized */
strcpy((char*)&msg->op_body, cbuf_get(cb)); /* XXX message data copied */
cbuf_free(cb);
*msg0 = msg;
ok:
retval = 0;
done:
return retval;
}
#endif /* CLIXON_PROTO_PLAIN */
/*! Receive a CLICON message
/*! Receive a CLICON message using IPC message struct
*
* XXX: timeout? and signals?
* There is rudimentary code for turning on signals and handling them
@ -501,12 +374,6 @@ clicon_msg_rcv(int s,
sigfn_t oldhandler;
uint32_t mlen;
#ifdef CLIXON_PROTO_PLAIN /* for testing, eg fuzzing */
if (clicon_msg_rcv_plain(s, msg, eof) < 0)
goto done;
return 0;
#endif
*eof = 0;
if (0)
set_signal(SIGINT, atomicio_sig_handler, &oldhandler);
@ -549,6 +416,90 @@ clicon_msg_rcv(int s,
return retval;
}
/*! Receive a message using plain ascii
* @param[in] s socket (unix or inet) to communicate with backend
* @param[out] cb1 cligen buf struct containing the incoming message
* @param[out] eof Set if eof encountered
* @see netconf_input_cb()
*/
int
clicon_msg_rcv1(int s,
cbuf *cb,
int *eof)
{
int retval = -1;
unsigned char buf[BUFSIZ];
int i;
int len;
int xml_state = 0;
int poll;
clicon_debug(1, "%s", __FUNCTION__);
memset(buf, 0, sizeof(buf));
while (1){
if ((len = read(s, buf, sizeof(buf))) < 0){
if (errno == ECONNRESET)
len = 0; /* emulate EOF */
else{
clicon_log(LOG_ERR, "%s: read: %s errno:%d", __FUNCTION__, strerror(errno), errno);
goto done;
}
} /* read */
if (len == 0){ /* EOF */
// cc_closed++;
close(s);
goto ok;
}
for (i=0; i<len; i++){
if (buf[i] == 0)
continue; /* Skip NULL chars (eg from terminals) */
cprintf(cb, "%c", buf[i]);
if (detect_endtag("]]>]]>",
buf[i],
&xml_state)) {
/* OK, we have an xml string from a client */
/* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
goto ok;
}
}
/* poll==1 if more, poll==0 if none */
if ((poll = clixon_event_poll(s)) < 0)
goto done;
if (poll == 0)
break; /* No data to read */
} /* while */
ok:
retval = 0;
done:
clicon_debug(1, "%s done", __FUNCTION__);
// if (cc_closed)
// retval = -1;
return retval;
}
/*! Send a CLICON netconf message plain text
* @param[in] s socket (unix or inet) to communicate with backend
* @param[out] msg CLICON msg data reply structure. Free with free()
*/
int
clicon_msg_send1(int s,
cbuf *cb)
{
int retval = -1;
cprintf(cb, "]]>]]>");
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_CFG, errno, "atomicio");
clicon_log(LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno));
goto done;
}
retval = 0;
done:
return retval;
}
/*! Connect to server, send a clicon_msg message and wait for result using unix socket
*
* @param[in] h Clicon handle
@ -593,8 +544,7 @@ clicon_rpc_connect_unix(clicon_handle h,
/*! Connect to server, send a clicon_msg message and wait for result using an inet socket
*
* @param[in] h Clicon handle
* @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[in] h Clicon handle (not used)
* @param[in] dst IPv4 address
* @param[in] port TCP port
* @param[out] retdata Returned data as string netconf xml tree.
@ -647,14 +597,16 @@ 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] s Socket to communicate with backend
* @param[in] fdin Input file descriptor
* @param[in] fdout Output file descriptor (for socket same as fdin)
* @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 s,
clicon_rpc(int fdin,
int fdout,
struct clicon_msg *msg,
char **ret)
{
@ -662,15 +614,14 @@ clicon_rpc(int s,
struct clicon_msg *reply = NULL;
int eof;
char *data = NULL;
cxobj *cx = NULL;
if (clicon_msg_send(s, msg) < 0)
if (clicon_msg_send(fdout, msg) < 0)
goto done;
if (clicon_msg_rcv(s, &reply, &eof) < 0)
if (clicon_msg_rcv(fdin, &reply, &eof) < 0)
goto done;
if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "Unexpected close of CLICON_SOCK. Clixon backend daemon may have crashed.");
close(s);
close(fdin); /* assume socket */
errno = ESHUTDOWN;
goto done;
}
@ -682,13 +633,52 @@ clicon_rpc(int s,
}
retval = 0;
done:
if (cx)
xml_free(cx);
if (reply)
free(reply);
return retval;
}
/*! Send a netconf message and recevive result.
*
* TBD: timeout, interrupt?
* retval may be -1 and
* 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] msgin CLICON msg data structure. It has fixed header and variable body.
* @param[out] msgret Returned data as netconf xml tree.
* @retval 0 OK
* @retval -1 Error
* see clicon_rpc using clicon_msg
*/
int
clicon_rpc1(int fdin,
int fdout,
cbuf *msg,
cbuf *msgret)
{
int retval = -1;
int eof;
clicon_debug(1, "%s", __FUNCTION__);
if (clicon_msg_send1(fdout, msg) < 0)
goto done;
if (clicon_msg_rcv1(fdin, 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 */
errno = ESHUTDOWN;
goto done;
}
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Send a clicon_msg message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client

View file

@ -154,7 +154,7 @@ clicon_rpc_msg(clicon_handle h,
/* 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, msg, &retdata) < 0)
if (clicon_rpc(s, s, msg, &retdata) < 0)
goto done;
clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata);

View file

@ -176,7 +176,7 @@ main(int argc,
else
if (clicon_rpc_connect_inet(h, sockpath, 4535, &s) < 0)
goto done;
if (clicon_rpc(s, msg, &retdata) < 0)
if (clicon_rpc(s, s, msg, &retdata) < 0)
goto done;
close(s);
fprintf(stdout, "%s\n", retdata);