From 5fa82dcea722e3077afd24def9bd04781924f0cf Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 28 Feb 2024 13:20:18 +0100 Subject: [PATCH] Swapped to use chunked framing in internal protocol --- lib/clixon/clixon_netconf_input.h | 1 + lib/clixon/clixon_proto.h | 39 +- lib/src/clixon_client.c | 8 +- lib/src/clixon_netconf_input.c | 41 +- lib/src/clixon_proto.c | 1067 ++++++++++------------------- 5 files changed, 414 insertions(+), 742 deletions(-) diff --git a/lib/clixon/clixon_netconf_input.h b/lib/clixon/clixon_netconf_input.h index 91e889b7..2bb3e29d 100644 --- a/lib/clixon/clixon_netconf_input.h +++ b/lib/clixon/clixon_netconf_input.h @@ -48,6 +48,7 @@ extern "C" { #endif +int detect_endtag(char *tag, char ch, int *state); ssize_t netconf_input_read2(int s, unsigned char *buf, ssize_t buflen, int *eof); int netconf_input_msg2(unsigned char **bufp, size_t *lenp, cbuf *cbmsg, netconf_framing_type framing, int *frame_state, size_t *frame_size, diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 6248c22e..e7744b00 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -44,7 +44,9 @@ * Types */ -/* Protocol message header */ +/*! Protocol message header (histoorical) + * Current use is a shim layer for sending packets + */ struct clicon_msg { uint32_t op_len; /* length of whole message: body+header, network byte order. */ uint32_t op_id; /* session-id. network byte order. 1..max(u32), can be zero in client hello */ @@ -56,45 +58,26 @@ struct clicon_msg { */ char *format_int2str(enum format_enum showas); enum format_enum format_str2int(char *str); +int clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len); struct clicon_msg *clicon_msg_encode(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 2, 3))); -int clicon_msg_decode(struct clicon_msg *msg, yang_stmt *yspec, uint32_t *id, cxobj **xml, cxobj **xerr); - int clicon_connect_unix(clixon_handle h, char *sockpath); - - int clicon_rpc_connect_unix(clixon_handle h, char *sockpath, int *sock0); - int clicon_rpc_connect_inet(clixon_handle h, char *dst, uint16_t port, int *sock0); +/* NETCONF 1.0 */ +int clixon_msg_rcv10(int s, const char *descr, cbuf *cb, int *eof); +int clixon_msg_send10(int s, const char *descr, cbuf *cb); +int clixon_rpc10(int sock, const char *descr, cbuf *msgin, cbuf *msgret, int *eof); +/* NETCONF 1.1 */ +int clixon_msg_rcv11(int s, const char *descr, cbuf **cb, int *eof); int clicon_rpc(int sock, const char *descr, struct clicon_msg *msg, char **xret, int *eof); - -int clicon_rpc1(int sock, const char *descr, cbuf *msgin, cbuf *msgret, int *eof); - -int clicon_msg_send(int s, const char *descr, struct clicon_msg *msg); - -int clicon_msg_send1(int s, const char *descr, cbuf *cb); - -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL -int clixon_msg_send2(int s, const char *descr, cbuf *cb); -int clixon_msg_rcv2(int s, const char *descr, cbuf **cb, int *eof); -#endif - -int clicon_msg_rcv(int s, const char *descr, int intr, struct clicon_msg **msg, int *eof); - -int clicon_msg_rcv1(int s, const char *descr, cbuf *cb, int *eof); - +int send_msg_reply(int s, const char *descr, char *data, uint32_t datalen); int send_msg_notify_xml(clixon_handle h, int s, const char *descr, cxobj *xev); -int send_msg_reply(int s, const char *descr, char *data, uint32_t datalen); - -int detect_endtag(char *tag, char ch, int *state); - -int clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len); - #endif /* _CLIXON_PROTO_H_ */ diff --git a/lib/src/clixon_client.c b/lib/src/clixon_client.c index 194513ee..800d2795 100644 --- a/lib/src/clixon_client.c +++ b/lib/src/clixon_client.c @@ -188,7 +188,7 @@ clixon_client_lock(clixon_handle h, NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR, lock?"":"un", db, lock?"":"un"); - if (clicon_rpc1(sock, descr, msg, msgret, &eof) < 0) + if (clixon_rpc10(sock, descr, msg, msgret, &eof) < 0) goto done; if (eof){ close(sock); @@ -242,7 +242,7 @@ clixon_client_hello(int sock, cprintf(msg, ""); cprintf(msg, ""); cprintf(msg, "]]>]]>"); - if (clicon_msg_send1(sock, descr, msg) < 0) + if (clixon_msg_send10(sock, descr, msg) < 0) goto done; retval = 0; done: @@ -529,9 +529,9 @@ clixon_client_get_xdata(clixon_handle h, cprintf(msg, ""); if (netconf_output_encap(0, msg) < 0) // XXX configurable session goto done; - if (clicon_msg_send1(sock, descr, msg) < 0) + if (clixon_msg_send10(sock, descr, msg) < 0) goto done; - if (clicon_msg_rcv1(sock, descr, msgret, &eof) < 0) + if (clixon_msg_rcv10(sock, descr, msgret, &eof) < 0) goto done; if (eof){ close(sock); diff --git a/lib/src/clixon_netconf_input.c b/lib/src/clixon_netconf_input.c index 528ba914..86af6241 100644 --- a/lib/src/clixon_netconf_input.c +++ b/lib/src/clixon_netconf_input.c @@ -76,6 +76,43 @@ #include "clixon_proto.h" #include "clixon_netconf_input.h" +/*! Look for a text pattern in an input string, one char at a time + * + * @param[in] tag What to look for + * @param[in] ch New input character + * @param[in,out] state A state integer holding how far we have parsed. + * @retval 1 Yes, we have detected end tag! + * @retval 0 No, we havent detected end tag + * @code + * int state = 0; + * char ch; + * while (1) { + * // read ch + * if (detect_endtag("mypattern", ch, &state)) { + * // mypattern is matched + * } + * } + * @endcode + */ +int +detect_endtag(char *tag, + char ch, + int *state) +{ + int retval = 0; + + if (tag[*state] == ch){ + (*state)++; + if (*state == strlen(tag)){ + *state = 0; + retval = 1; + } + } + else + *state = 0; + return retval; +} + /*! Read from socket and append to cbuf * * @param[in] s Socket where input arrives. Read from this. @@ -194,9 +231,10 @@ netconf_input_msg2(unsigned char **bufp, return retval; } -/*! Process incoming frame, ie a char message framed by ]]>]]> +/*! Parse incoming frame (independent of framing) * * Parse string to xml, check only one netconf message within a frame + * A relatively high-level function. * @param[in] cb Packet buffer * @param[in] yb Yang binding: Y_RPC for server-side, Y_NONE for client-side (for now) * @param[in] yspec Yang spec @@ -268,4 +306,3 @@ netconf_input_frame2(cbuf *cb, retval = 0; goto done; } - diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 547a4d75..3e785c4b 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -135,6 +135,53 @@ format_str2int(char *str) return fv?fv->fv_int:-1; } +/*! Given family, addr str, port, return sockaddr and length + * + * @param[in] addrtype Address family: inet:ipv4-address or inet:ipv6-address + * @param[in] addrstr IP address as string + * @param[in] port TCP port host byte order + * @param[out] sa sockaddr, should be allocated + * @param[out] salen length of sockaddr data + * @code + * struct sockaddr_in6 sin6 = {0,}; // because its larger than sin and sa + * struct sockaddr *sa = &sin6; + * size_t sa_len; + * if (clixon_inet2sin(inet:ipv4-address, "0.0.0.0", 80, sa, &sa_len) < 0) + * err; + * @endcode + * Probably misplaced, need a clixon_network file? + */ +int +clixon_inet2sin(const char *addrtype, + const char *addrstr, + uint16_t port, + struct sockaddr *sa, + size_t *sa_len) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (strcmp(addrtype, "inet:ipv6-address") == 0) { + sin6 = (struct sockaddr_in6 *)sa; + *sa_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = htons(port); + sin6->sin6_family = AF_INET6; + inet_pton(AF_INET6, addrstr, &sin6->sin6_addr); + } + else if (strcmp(addrtype, "inet:ipv4-address") == 0) { + sin = (struct sockaddr_in *)sa; + *sa_len = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + sin->sin_addr.s_addr = inet_addr(addrstr); + } + else{ + clixon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype); + return -1; + } + return 0; +} + /*! Encode a clicon netconf message using variable argument lists * * @param[in] id Session id of client @@ -176,47 +223,6 @@ clicon_msg_encode(uint32_t id, return msg; } -/*! Decode a clicon netconf message - * - * @param[in] msg Clixon msg - * @param[in] yspec Yang specification, (can be NULL) - * @param[out] id Session id - * @param[out] xml XML parse tree - * @param[out] xerr Reason for failure (yang assignment not made) if retval =0 - * @retval 1 Parse OK and all yang assignment made - * @retval 0 Parse OK but yang assigment not made (or only partial) - * @retval -1 Error with clixon_err called. Includes parse error - */ -int -clicon_msg_decode(struct clicon_msg *msg, - yang_stmt *yspec, - uint32_t *id, - cxobj **xml, - cxobj **xerr) -{ - int retval = -1; - char *xmlstr; - int ret; - - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - /* hdr */ - if (id) - *id = ntohl(msg->op_id); - /* body */ - xmlstr = msg->op_body; - // XXX clixon_debug(CLIXON_DBG_MSG, "Recv: %s", xmlstr); - if ((ret = clixon_xml_parse_string(xmlstr, yspec?YB_RPC:YB_NONE, yspec, xml, xerr)) < 0) - goto done; - if (ret == 0) - goto fail; - retval = 1; - done: - return retval; - fail: - retval = 0; - goto done; -} - /*! Open local connection using unix domain sockets * * @param[in] h Clixon handle @@ -256,485 +262,6 @@ clicon_connect_unix(clixon_handle h, return retval; } -#ifndef NETCONF_INPUT_UNIFIED_INTERNAL -static void -atomicio_sig_handler(int arg) -{ - _atomicio_sig++; -} -#endif - -/*! Ensure all of data on socket comes through. fn is either read or write - * - * @param[in] fn I/O function, ie read/write - * @param[in] fd File descriptor, eg socket - * @param[in] s0 Buffer to read to or write from - * @param[in] n Number of bytes to read/write, loop until done - */ -static ssize_t -atomicio(ssize_t (*fn) (int, void *, size_t), - int fd, - void *s0, - size_t n) -{ - char *s = s0; - ssize_t res, pos = 0; - - while (n > pos) { - _atomicio_sig = 0; - res = (fn)(fd, s + pos, n - pos); - switch (res) { - case -1: - if (errno == EINTR){ - if (_atomicio_sig == 0) - continue; - } - else if (errno == EAGAIN) - continue; - else if (errno == ECONNRESET)/* Connection reset by peer */ - res = 0; - else if (errno == EPIPE) /* Client shutdown */ - res = 0; - else if (errno == EBADF) /* client shutdown - freebsd */ - res = 0; - case 0: /* fall thru */ - return (res); - default: - pos += res; - } - } - return (pos); -} - -/*! Log message as hex on debug. - * - * @param[in] dbglevel Debug level - * @param[in] msg Byte stream - * @param[in] len Length of byte stream - * @param[in] file Calling file name - * @retval 0 OK - * @retval -1 Error - */ -static int -msg_hex(int dbglevel, - const char *msg, - size_t len, - char const *file) -{ - int retval = -1; - cbuf *cb = NULL; - int i; - - if (!clixon_debug_isset(dbglevel)) /* compare debug level with global variable */ - goto ok; - if ((cb = cbuf_new()) == NULL){ - clixon_err(OE_CFG, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "%s:", file); - for (i=0; iop_len)); - if (descr) - clixon_debug(CLIXON_DBG_MSG, "Send [%s]: %s", descr, msg->op_body); - else{ - clixon_debug(CLIXON_DBG_MSG, "Send: %s", msg->op_body); - } - msg_hex(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, (char*)msg, ntohl(msg->op_len), __FUNCTION__); - if (atomicio((ssize_t (*)(int, void *, size_t))write, - s, msg, ntohl(msg->op_len)) < 0){ - e = errno; - clixon_err(OE_CFG, e, "atomicio"); - clixon_log(NULL, LOG_WARNING, "%s: write: %s len:%u msg:%s", __FUNCTION__, - strerror(e), ntohs(msg->op_len), msg->op_body); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Receive a Clixon message using IPC message struct - * - * XXX: timeout? and signals? - * There is rudimentary code for turning on signals and handling them - * so that they can be interrupted by ^C. But the problem is that this - * is a library routine and such things should be set up in the cli - * application for example: a daemon calling this function will want another - * behaviour. - * Now, ^C will interrupt the whole process, and this may not be what you want. - * - * @param[in] s Socket (unix or inet) to communicate with backend - * @param[in] descr Description of peer for logging - * @param[in] intr If set, make a ^C cause an error (OBSOLETE?) - * @param[out] msg Clixon msg data reply structure. Free with free() - * @param[out] eof Set if eof encountered - * @retval 0 OK - * @retval -1 Error - * @note: Caller must ensure that s is closed if eof is set after call. - * @note: intr parameter used in eg CLI where receive should be interruptable - * @see clicon_msg_rcv1 using plain NETCONF - */ -int -clicon_msg_rcv(int s, - const char *descr, - int intr, - struct clicon_msg **msg, - int *eof) -{ - int retval = -1; -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL - struct clicon_msg hdr; - size_t mlen; - cbuf *cb = NULL; - - /* Step 1: emulate a clicon_msg API */ - if (clixon_msg_rcv2(s, descr, &cb, eof) < 0) - goto done; - mlen = cbuf_len(cb) + sizeof(hdr); - if ((*msg = (struct clicon_msg *)malloc(mlen+1)) == NULL){ - clicon_err(OE_PROTO, errno, "malloc"); - goto done; - } - memset(*msg, 0, mlen+1); - (*msg)->op_len = htonl(mlen); - (*msg)->op_id = 0; // XXX attribute? - memcpy(&(*msg)->op_body, cbuf_get(cb), cbuf_len(cb)); - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -#else - struct clicon_msg hdr; - int hlen; - ssize_t len2; - uint32_t mlen; - sigset_t oldsigset; - struct sigaction oldsigaction[32] = {{{0,},},}; - - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - *eof = 0; - if (intr){ - if (clixon_signal_save(&oldsigset, oldsigaction) < 0) - goto done; - set_signal(SIGINT, SIG_IGN, NULL); - clicon_signal_unblock(SIGINT); - set_signal_flags(SIGINT, 0, atomicio_sig_handler, NULL); - } - if ((hlen = atomicio(read, s, &hdr, sizeof(hdr))) < 0){ - if (intr && _atomicio_sig) - ; - else - clixon_err(OE_CFG, errno, "atomicio"); - goto done; - } - msg_hex(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, (char*)&hdr, hlen, __FUNCTION__); - if (hlen == 0){ - *eof = 1; - goto ok; - } - if (hlen != sizeof(hdr)){ - clixon_err(OE_PROTO, errno, "header too short (%d)", hlen); - goto done; - } - mlen = ntohl(hdr.op_len); - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "op-len:%u op-id:%u", - mlen, ntohl(hdr.op_id)); - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "rcv msg len=%d", - mlen); - if (mlen <= sizeof(hdr)){ - clixon_err(OE_PROTO, 0, "op_len:%u too short", mlen); - *eof = 1; - goto ok; - } - if ((*msg = (struct clicon_msg *)malloc(mlen+1)) == NULL){ - clixon_err(OE_PROTO, errno, "malloc"); - goto done; - } - memcpy(*msg, &hdr, hlen); - if ((len2 = atomicio(read, s, (*msg)->op_body, mlen - sizeof(hdr))) < 0){ - clixon_err(OE_PROTO, errno, "read"); - goto done; - } - if (len2) - msg_hex(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, (*msg)->op_body, len2, __FUNCTION__); - if (len2 != mlen - sizeof(hdr)){ - clixon_err(OE_PROTO, 0, "body too short"); - *eof = 1; - goto ok; - } - if (((char*)*msg)[mlen-1] != '\0'){ - clixon_err(OE_PROTO, 0, "body not NULL terminated"); - *eof = 1; - goto ok; - } - if (descr) - clixon_debug(CLIXON_DBG_MSG, "Recv [%s]: %s", descr, (*msg)->op_body); - else - clixon_debug(CLIXON_DBG_MSG, "Recv: %s", (*msg)->op_body); - ok: - retval = 0; - done: - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "retval:%d", retval); - if (intr){ - if (clixon_signal_restore(&oldsigset, oldsigaction) < 0) - goto done; - } - return retval; -#endif /* NETCONF_INPUT_UNIFIED_INTERNAL */ -} - -/*! Receive a message using plain NETCONF - * - * @param[in] s socket (unix or inet) to communicate with backend - * @param[in] descr Description of peer for logging - * @param[out] cb cligen buf struct containing the incoming message - * @param[out] eof Set if eof encountered - * @retval 0 OK - * @retval -1 Error - * @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, - const char *descr, - cbuf *cb, - int *eof) -{ - int retval = -1; - unsigned char buf[BUFSIZ]; - int i; - int len; - int xml_state = 0; - int poll; - - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - *eof = 0; - memset(buf, 0, sizeof(buf)); - while (1){ - if ((len = read(s, buf, sizeof(buf))) < 0){ - if (errno == ECONNRESET) - len = 0; /* emulate EOF */ - else{ - clixon_log(NULL, LOG_ERR, "%s: read: %s errno:%d", __FUNCTION__, strerror(errno), errno); - goto done; - } - } /* read */ - if (len == 0){ /* EOF */ - *eof = 1; - close(s); - goto ok; - } - for (i=0; i]]>", - 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: - if (descr) - clixon_debug(CLIXON_DBG_MSG, "Recv [%s]: %s", descr, cbuf_get(cb)); - else - clixon_debug(CLIXON_DBG_MSG, "Recv: %s", cbuf_get(cb)); - retval = 0; - done: - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "done"); - return retval; -} - -/*! Send a Clixon netconf message plain NETCONF - * - * @param[in] s socket (unix or inet) to communicate with backend - * @param[in] descr Description of peer for logging - * @param[in] cb data buffer including NETCONF (and EOM) - * @retval 0 OK - * @retval -1 Error - * @see clicon_msg_send using internal IPC header - * @see clicon_msg_send2 version 2 - chunked - */ -int -clicon_msg_send1(int s, - const char *descr, - cbuf *cb) -{ - int retval = -1; - - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - if (descr) - clixon_debug(CLIXON_DBG_MSG, "Send [%s]: %s", descr, cbuf_get(cb)); - else - clixon_debug(CLIXON_DBG_MSG, "Send: %s", cbuf_get(cb)); - if (atomicio((ssize_t (*)(int, void *, size_t))write, - s, cbuf_get(cb), cbuf_len(cb)) < 0){ - clixon_err(OE_CFG, errno, "atomicio"); - clixon_log(NULL, LOG_WARNING, "%s: write: %s", __FUNCTION__, strerror(errno)); - goto done; - } - retval = 0; - done: - return retval; -} - -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL -/*! Send a message using unified NETCONF w chunked framing - * - * @param[in] s socket (unix or inet) to communicate with backend - * @param[in] descr Description of peer for logging - * @param[out] msg CLICON msg data reply structure. Free with free() - * @see clicon_msg_send using internal IPC header - */ -int -clixon_msg_send2(int s, - const char *descr, - cbuf *cb) -{ - int retval = -1; - - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "send msg len=%lu", cbuf_len(cb)); - if (descr) - clixon_debug(CLIXON_DBG_MSG, "Send [%s]: %s", descr, cbuf_get(cb)); - else - clixon_debug(CLIXON_DBG_MSG, "Send: %s", cbuf_get(cb)); - if (netconf_output_encap(NETCONF_SSH_CHUNKED, cb) < 0) - goto done; - if (atomicio((ssize_t (*)(int, void *, size_t))write, - 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; - } - retval = 0; - done: - return retval; -} - -/*! Receive a message using unified NETCONF w chunked framing - * - * @param[in] s socket (unix or inet) to communicate with backend - * @param[out] cb cligen buf struct containing the incoming message - * @param[out] eof Set if eof encountered - * @retval 0 OK (check eof) - * @retval -1 Error - * @see netconf_input_cb() - * @see clicon_msg_rcv using IPC message struct - * @note only NETCONF version 1.0 EOM framing - */ -int -clixon_msg_rcv2(int s, - const char *descr, - cbuf **cb, - int *eof) -{ - int retval = -1; - unsigned char buf[BUFSIZ]; - ssize_t buflen = sizeof(buf); - int frame_state = 0; - size_t frame_size = 0; - unsigned char *p = buf; - size_t plen; - cbuf *cbmsg=NULL; - ssize_t len; - int eom = 0; - cxobj *xtop = NULL; - cxobj *xerr = NULL; - - if ((cbmsg = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - eom = 0; - while (*eof == 0 && eom == 0) { - /* Read input data from socket and append to cbbuf */ - if ((len = netconf_input_read2(s, buf, buflen, eof)) < 0) - goto done; - p = buf; - plen = len; - while (!(*eof) && plen > 0){ - if (netconf_input_msg2(&p, &plen, - cbmsg, - NETCONF_SSH_CHUNKED, - &frame_state, - &frame_size, - &eom) < 0){ - /* Errors from input are only framing errors, non-fatal, return eof */ - *eof = 1; - cbuf_reset(cbmsg); - break; - } - if (eom == 0){ - continue; - } - clixon_debug(CLIXON_DBG_MSG, "Recv ext: %s", cbuf_get(cbmsg)); - } - } - clixon_debug(CLIXON_DBG_MSG, "Recv: %s", cbuf_get(cbmsg)); - if (cb){ - *cb = cbmsg; - cbmsg = NULL; - } - retval = 0; - done: - clixon_debug(CLIXON_DBG_MSG|CLIXON_DBG_DETAIL, "%s done", __FUNCTION__); - if (cbmsg) - cbuf_free(cbmsg); - if (xtop) - xml_free(xtop); - if (xerr) - xml_free(xerr); - return retval; -} -#endif /* NETCONF_INPUT_UNIFIED_INTERNAL */ - /*! Connect to server using unix socket * * @param[in] h Clixon handle @@ -824,80 +351,167 @@ clicon_rpc_connect_inet(clixon_handle h, return retval; } -/*! Send a clicon_msg message and wait for result. +/*! Ensure all of data on socket comes through. fn is either read or write * - * TBD: timeout, interrupt? - * retval may be -1 and - * errno set to ENOTCONN/ESHUTDOWN which means that socket is now closed probably - * due to remote peer disconnecting. The caller may have to do something,... - * - * @param[in] sock Socket / file descriptor - * @param[in] descr Description of peer for logging - * @param[in] msg Clixon msg data structure. It has fixed header and variable body. - * @param[out] xret Returned data as netconf xml tree. - * @param[out] eof Set if eof encountered - * @retval 0 OK (check eof) - * @retval -1 Error - * @see clicon_rpc1 using plain NETCONF XML + * @param[in] fn I/O function, ie read/write + * @param[in] fd File descriptor, eg socket + * @param[in] s0 Buffer to read to or write from + * @param[in] n Number of bytes to read/write, loop until done */ -int -clicon_rpc(int sock, - const char *descr, - struct clicon_msg *msg, - char **ret, - int *eof) +static ssize_t +atomicio(ssize_t (*fn) (int, void *, size_t), + int fd, + void *s0, + size_t n) { - int retval = -1; - struct clicon_msg *reply = NULL; -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL - cbuf *cbsend = cbuf_new(); - cbuf *cbrcv = NULL; + char *s = s0; + ssize_t res, pos = 0; - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - cprintf(cbsend, "%s", msg->op_body); - if (clixon_msg_send2(sock, descr, cbsend) < 0) - goto done; - if (cbsend) - cbuf_free(cbsend); - if (clixon_msg_rcv2(sock, descr, &cbrcv, eof) < 0) - goto done; - if (*eof) - goto ok; - if (cbrcv){ - if ((*ret = strdup(cbuf_get(cbrcv))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; + while (n > pos) { + _atomicio_sig = 0; + res = (fn)(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR){ + if (_atomicio_sig == 0) + continue; + } + else if (errno == EAGAIN) + continue; + else if (errno == ECONNRESET)/* Connection reset by peer */ + res = 0; + else if (errno == EPIPE) /* Client shutdown */ + res = 0; + else if (errno == EBADF) /* client shutdown - freebsd */ + res = 0; + case 0: /* fall thru */ + return (res); + default: + pos += res; } - cbuf_free(cbrcv); } -#else /* NETCONF_INPUT_UNIFIED_INTERNAL */ - char *data = NULL; + return (pos); +} - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - if (clicon_msg_send(sock, descr, msg) < 0) - goto done; - if (clicon_msg_rcv(sock, descr, 0, &reply, eof) < 0) - goto done; - if (*eof) - goto ok; +static int +clixon_msg_send(int s, + const char *descr, + cbuf *cb) +{ + int retval = -1; - data = reply->op_body; /* assume string */ - if (ret && data) - if ((*ret = strdup(data)) == NULL){ - clixon_err(OE_UNIX, errno, "strdup"); - goto done; - } -#endif /* NETCONF_INPUT_UNIFIED_INTERNAL */ - ok: + clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "send msg len=%lu", cbuf_len(cb)); + if (descr) + clixon_debug(CLIXON_DBG_MSG, "Send [%s]: %s", descr, cbuf_get(cb)); + else + clixon_debug(CLIXON_DBG_MSG, "Send: %s", cbuf_get(cb)); + if (atomicio((ssize_t (*)(int, void *, size_t))write, + 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; + } retval = 0; done: - clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "retval:%d", retval); - if (reply) - free(reply); return retval; } -/*! Send a netconf message and recieve result using plain NETCONF +/*================= NETCONF 1.0 EOM framing ================*/ + +/*! Receive a message using NETCONF 1.0 end-of-message encoding + * + * @param[in] s socket (unix or inet) to communicate with backend + * @param[in] descr Description of peer for logging + * @param[out] cb cligen buf struct containing the incoming message + * @param[out] eof Set if eof encountered + * @retval 0 OK + * @retval -1 Error + * @see clixon_msg_rcv11 Chunked framing + * @see netconf_input_msg2 + */ +int +clixon_msg_rcv10(int s, + const char *descr, + cbuf *cb, + int *eof) +{ + int retval = -1; + unsigned char buf[BUFSIZ]; + int i; + int len; + int xml_state = 0; + int poll; + + clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); + *eof = 0; + memset(buf, 0, sizeof(buf)); + while (1){ +#if 1 + if ((len = netconf_input_read2(s, buf, sizeof(buf), eof)) < 0) + goto done; +#else + if ((len = read(s, buf, sizeof(buf))) < 0){ + if (errno == ECONNRESET) + len = 0; /* emulate EOF */ + else{ + clixon_log(NULL, LOG_ERR, "%s: read: %s errno:%d", __FUNCTION__, strerror(errno), errno); + goto done; + } + } /* read */ + if (len == 0){ /* EOF */ + *eof = 1; + close(s); + goto ok; + } +#endif + for (i=0; i]]>", + 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: + if (descr) + clixon_debug(CLIXON_DBG_MSG, "Recv [%s]: %s", descr, cbuf_get(cb)); + else + clixon_debug(CLIXON_DBG_MSG, "Recv: %s", cbuf_get(cb)); + retval = 0; + done: + clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "done"); + return retval; +} + +/*! Send a message using NETCONF 1.0 end-of-message encoding + * + * @param[in] s socket (unix or inet) to communicate with backend + * @param[in] descr Description of peer for logging + * @param[in] cb data buffer including NETCONF (and EOM) + * @retval 0 OK + * @retval -1 Error + * @see clixon_msg_send11 version 1.1 - chunked + */ +int +clixon_msg_send10(int s, + const char *descr, + cbuf *cb) +{ + return clixon_msg_send(s, descr, cb); +} + +/*! Send a netconf message and recieve result using NETCONF 1.0 EOM encoding * * This is mainly used by the client API. * @param[in] sock Socket / file descriptor @@ -910,22 +524,22 @@ clicon_rpc(int sock, * @see clicon_rpc using clicon_msg protocol header */ int -clicon_rpc1(int sock, - const char *descr, - cbuf *msg, - cbuf *msgret, - int *eof) +clixon_rpc10(int sock, + const char *descr, + cbuf *msg, + cbuf *msgret, + int *eof) { int retval = -1; clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); - if (netconf_framing_preamble(NETCONF_SSH_CHUNKED, msg) < 0) + if (netconf_framing_preamble(NETCONF_SSH_EOM, msg) < 0) goto done; - if (netconf_framing_postamble(NETCONF_SSH_CHUNKED, msg) < 0) + if (netconf_framing_postamble(NETCONF_SSH_EOM, msg) < 0) goto done; - if (clicon_msg_send1(sock, descr, msg) < 0) + if (clixon_msg_send10(sock, descr, msg) < 0) goto done; - if (clicon_msg_rcv1(sock, descr, msgret, eof) < 0) + if (clixon_msg_rcv10(sock, descr, msgret, eof) < 0) goto done; retval = 0; done: @@ -933,6 +547,159 @@ clicon_rpc1(int sock, return retval; } +/*================= NETCONF 1.1 Chunked framing ================*/ + +/*! Send a message using NETCONF 1.1 w chunked framing + * + * @param[in] s socket (unix or inet) to communicate with backend + * @param[in] descr Description of peer for logging + * @param[out] msg CLICON msg data reply structure. Free with free() + * @see clixon_msg_send10 1.0 EOM + */ +static int +clixon_msg_send11(int s, + const char *descr, + cbuf *cb) +{ + int retval = -1; + + if (netconf_output_encap(NETCONF_SSH_CHUNKED, cb) < 0) + goto done; + if (clixon_msg_send(s, descr, cb) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Receive a message using unified NETCONF w chunked framing + * + * @param[in] s socket (unix or inet) to communicate with backend + * @param[out] cb cligen buf struct containing the incoming message + * @param[out] eof Set if eof encountered + * @retval 0 OK (check eof) + * @retval -1 Error + * @see netconf_input_cb() + * @note only NETCONF version 1.0 EOM framing + */ +int +clixon_msg_rcv11(int s, + const char *descr, + cbuf **cb, + int *eof) +{ + int retval = -1; + unsigned char buf[BUFSIZ]; + ssize_t buflen = sizeof(buf); + int frame_state = 0; + size_t frame_size = 0; + unsigned char *p = buf; + size_t plen; + cbuf *cbmsg=NULL; + ssize_t len; + int eom = 0; + cxobj *xtop = NULL; + cxobj *xerr = NULL; + + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + eom = 0; + while (*eof == 0 && eom == 0) { + /* Read input data from socket and append to cbbuf */ + if ((len = netconf_input_read2(s, buf, buflen, eof)) < 0) + goto done; + p = buf; + plen = len; + while (!(*eof) && plen > 0){ + if (netconf_input_msg2(&p, &plen, + cbmsg, + NETCONF_SSH_CHUNKED, + &frame_state, + &frame_size, + &eom) < 0){ + /* Errors from input are only framing errors, non-fatal, return eof */ + *eof = 1; + cbuf_reset(cbmsg); + break; + } + if (eom == 0){ + continue; + } + clixon_debug(CLIXON_DBG_MSG, "Recv ext: %s", cbuf_get(cbmsg)); + } + } + clixon_debug(CLIXON_DBG_MSG, "Recv: %s", cbuf_get(cbmsg)); + if (cb){ + *cb = cbmsg; + cbmsg = NULL; + } + retval = 0; + done: + clixon_debug(CLIXON_DBG_MSG|CLIXON_DBG_DETAIL, "%s done", __FUNCTION__); + if (cbmsg) + cbuf_free(cbmsg); + if (xtop) + xml_free(xtop); + if (xerr) + xml_free(xerr); + return retval; +} + +/*! Send a NETCONF message and wait for result. + * + * TBD: timeout, interrupt? + * retval may be -1 and + * errno set to ENOTCONN/ESHUTDOWN which means that socket is now closed probably + * due to remote peer disconnecting. The caller may have to do something,... + * @param[in] sock Socket / file descriptor + * @param[in] descr Description of peer for logging + * @param[in] msg Clixon msg data structure. It has fixed header and variable body. + * @param[out] xret Returned data as netconf xml tree. + * @param[out] eof Set if eof encountered + * @retval 0 OK (check eof) + * @retval -1 Error + * @see clixon_rpc10 using NETCONF 1.0 EOM + */ +int +clicon_rpc(int sock, + const char *descr, + struct clicon_msg *msg, + char **ret, + int *eof) +{ + int retval = -1; + struct clicon_msg *reply = NULL; + cbuf *cbsend = cbuf_new(); + cbuf *cbrcv = NULL; + + clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, ""); + cprintf(cbsend, "%s", msg->op_body); + if (clixon_msg_send11(sock, descr, cbsend) < 0) + goto done; + if (cbsend) + cbuf_free(cbsend); + if (clixon_msg_rcv11(sock, descr, &cbrcv, eof) < 0) + goto done; + if (*eof) + goto ok; + if (cbrcv){ + if ((*ret = strdup(cbuf_get(cbrcv))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + cbuf_free(cbrcv); + } + ok: + retval = 0; + done: + clixon_debug(CLIXON_DBG_MSG | CLIXON_DBG_DETAIL, "retval:%d", retval); + if (reply) + free(reply); + return retval; +} + /*! Send a clicon_msg message as reply to a clicon rpc request * * @param[in] s Socket to communicate with client @@ -949,7 +716,6 @@ send_msg_reply(int s, uint32_t datalen) { int retval = -1; -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL cbuf *cb = NULL; if ((cb = cbuf_new()) == NULL){ @@ -960,30 +726,12 @@ send_msg_reply(int s, clicon_err(OE_UNIX, errno, "cbuf_append_buf"); goto done; } - if (clixon_msg_send2(s, descr, cb) < 0) + if (clixon_msg_send11(s, descr, cb) < 0) goto done; retval = 0; done: if (cb) cbuf_free(cb); -#else /* NETCONF_INPUT_UNIFIED_INTERNAL */ - struct clicon_msg *reply = NULL; - uint32_t len; - - len = sizeof(*reply) + datalen; - if ((reply = (struct clicon_msg *)malloc(len)) == NULL) - goto done; - memset(reply, 0, len); - reply->op_len = htonl(len); - if (datalen > 0) - memcpy(reply->op_body, data, datalen); - if (clicon_msg_send(s, descr, reply) < 0) - goto done; - retval = 0; - done: - if (reply) - free(reply); -#endif /* NETCONF_INPUT_UNIFIED_INTERNAL */ return retval; } @@ -1002,7 +750,6 @@ send_msg_notify(int s, char *msg) { int retval = -1; -#ifdef NETCONF_INPUT_UNIFIED_INTERNAL cbuf *cb = NULL; if ((cb = cbuf_new()) == NULL){ @@ -1010,24 +757,12 @@ send_msg_notify(int s, goto done; } cprintf(cb, "%s", msg); - if (clixon_msg_send2(s, descr, cb) < 0) + if (clixon_msg_send11(s, descr, cb) < 0) goto done; retval = 0; done: if (cb) cbuf_free(cb); -#else /* NETCONF_INPUT_UNIFIED_INTERNAL */ - struct clicon_msg *imsg = NULL; - - if ((imsg = clicon_msg_encode(0, "%s", msg)) == NULL) - goto done; - if (clicon_msg_send(s, descr, imsg) < 0) - goto done; - retval = 0; - done: - if (imsg) - free(imsg); -#endif /* NETCONF_INPUT_UNIFIED_INTERNAL */ return retval; } @@ -1065,87 +800,3 @@ send_msg_notify_xml(clixon_handle h, cbuf_free(cb); return retval; } - -/*! Look for a text pattern in an input string, one char at a time - * - * @param[in] tag What to look for - * @param[in] ch New input character - * @param[in,out] state A state integer holding how far we have parsed. - * @retval 1 Yes, we have detected end tag! - * @retval 0 No, we havent detected end tag - * @code - * int state = 0; - * char ch; - * while (1) { - * // read ch - * if (detect_endtag("mypattern", ch, &state)) { - * // mypattern is matched - * } - * } - * @endcode - */ -int -detect_endtag(char *tag, - char ch, - int *state) -{ - int retval = 0; - - if (tag[*state] == ch){ - (*state)++; - if (*state == strlen(tag)){ - *state = 0; - retval = 1; - } - } - else - *state = 0; - return retval; -} - -/*! Given family, addr str, port, return sockaddr and length - * - * @param[in] addrtype Address family: inet:ipv4-address or inet:ipv6-address - * @param[in] addrstr IP address as string - * @param[in] port TCP port host byte order - * @param[out] sa sockaddr, should be allocated - * @param[out] salen length of sockaddr data - * @code - * struct sockaddr_in6 sin6 = {0,}; // because its larger than sin and sa - * struct sockaddr *sa = &sin6; - * size_t sa_len; - * if (clixon_inet2sin(inet:ipv4-address, "0.0.0.0", 80, sa, &sa_len) < 0) - * err; - * @endcode - * Probably misplaced, need a clixon_network file? - */ -int -clixon_inet2sin(const char *addrtype, - const char *addrstr, - uint16_t port, - struct sockaddr *sa, - size_t *sa_len) -{ - struct sockaddr_in6 *sin6; - struct sockaddr_in *sin; - - if (strcmp(addrtype, "inet:ipv6-address") == 0) { - sin6 = (struct sockaddr_in6 *)sa; - *sa_len = sizeof(struct sockaddr_in6); - sin6->sin6_port = htons(port); - sin6->sin6_family = AF_INET6; - inet_pton(AF_INET6, addrstr, &sin6->sin6_addr); - } - else if (strcmp(addrtype, "inet:ipv4-address") == 0) { - sin = (struct sockaddr_in *)sa; - *sa_len = sizeof(struct sockaddr_in); - sin->sin_family = AF_INET; - sin->sin_port = htons(port); - sin->sin_addr.s_addr = inet_addr(addrstr); - } - else{ - clixon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype); - return -1; - } - return 0; -}