/*
*
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
CLIXON is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
CLIXON is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CLIXON; see the file LICENSE. If not, see
.
*
* Protocol to communicate between clients (eg clicon_cli, clicon_netconf)
* and server (clicon_backend)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* cligen */
#include
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_sig.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
static int _atomicio_sig = 0;
struct map_type2str{
enum clicon_msg_type mt_type;
char *mt_str; /* string as in 4.2.4 in RFC 6020 */
};
/* Mapping between yang keyword string <--> clicon constants */
static const struct map_type2str msgmap[] = {
{CLICON_MSG_COMMIT, "commit"},
{CLICON_MSG_VALIDATE, "validate"},
{CLICON_MSG_CHANGE, "change"},
{CLICON_MSG_SAVE, "save"},
{CLICON_MSG_LOAD, "load"},
{CLICON_MSG_KILL, "kill"},
{CLICON_MSG_DEBUG, "debug"},
{CLICON_MSG_CALL, "call"},
{CLICON_MSG_SUBSCRIPTION, "subscription"},
{CLICON_MSG_OK, "ok"},
{CLICON_MSG_NOTIFY, "notify"},
{CLICON_MSG_ERR, "err"},
{-1, NULL},
};
static char *
msg_type2str(enum clicon_msg_type type)
{
const struct map_type2str *mt;
for (mt = &msgmap[0]; mt->mt_str; mt++)
if (mt->mt_type == type)
return mt->mt_str;
return NULL;
}
/*! Open local connection using unix domain sockets
*/
int
clicon_connect_unix(char *sockpath)
{
struct sockaddr_un addr;
int retval = -1;
int s;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_CFG, errno, "socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path)-1);
clicon_debug(2, "%s: connecting to %s", __FUNCTION__, addr.sun_path);
if (connect(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){
if (errno == EACCES)
clicon_err(OE_CFG, errno, "connecting unix socket: %s.\n"
"Client should be member of group $CLICON_SOCK_GROUP: ",
sockpath);
else
clicon_err(OE_CFG, errno, "connecting unix socket: %s", sockpath);
close(s);
goto done;
}
retval = s;
done:
return retval;
}
static void
atomicio_sig_handler(int arg)
{
_atomicio_sig++;
}
/*! Ensure all of data on socket comes through. fn is either read or write
*/
static ssize_t
atomicio(ssize_t (*fn) (int, void *, size_t), int fd, void *_s, size_t n)
{
char *s = _s;
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)
continue;
}
else
if (errno == EAGAIN)
continue;
case 0: /* fall thru */
return (res);
default:
pos += res;
}
}
return (pos);
}
static int
msg_dump(struct clicon_msg *msg)
{
int i;
char buf[9*8];
char buf2[9*8];
memset(buf2, 0, sizeof(buf2));
snprintf(buf2, sizeof(buf2), "%s:", __FUNCTION__);
for (i=0; iop_len); i++){
snprintf(buf, sizeof(buf), "%s%02x", buf2, ((char*)msg)[i]&0xff);
if ((i+1)%32==0){
clicon_debug(2, buf);
snprintf(buf, sizeof(buf), "%s:", __FUNCTION__);
}
else
if ((i+1)%4==0)
snprintf(buf, sizeof(buf), "%s ", buf2);
strncpy(buf2, buf, sizeof(buf2));
}
if (i%32)
clicon_debug(2, buf);
return 0;
}
int
clicon_msg_send(int s,
struct clicon_msg *msg)
{
int retval = -1;
clicon_debug(2, "%s: send msg seq=%d len=%d",
__FUNCTION__, ntohs(msg->op_type), ntohs(msg->op_len));
if (debug > 2)
msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohs(msg->op_len)) < 0){
clicon_err(OE_CFG, errno, "%s", __FUNCTION__);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Receive a CLICON message on a UNIX domain socket
*
* 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 UNIX domain socket to communicate with backend
* @param[out] msg CLICON msg data reply structure. allocated using CLICON chunks,
* freed by caller with unchunk*(...,label)
* @param[out] eof Set if eof encountered
* @param[in] label Label used in chunk allocation and deallocation.
* Note: caller must ensure that s is closed if eof is set after call.
*/
int
clicon_msg_rcv(int s,
struct clicon_msg **msg,
int *eof,
const char *label)
{
int retval = -1;
struct clicon_msg hdr;
int hlen;
int len2;
sigfn_t oldhandler;
uint16_t mlen;
*eof = 0;
if (0)
set_signal(SIGINT, atomicio_sig_handler, &oldhandler);
if ((hlen = atomicio(read, s, &hdr, sizeof(hdr))) < 0){
clicon_err(OE_CFG, errno, "%s", __FUNCTION__);
goto done;
}
if (hlen == 0){
retval = 0;
*eof = 1;
goto done;
}
if (hlen != sizeof(hdr)){
clicon_err(OE_CFG, errno, "%s: header too short (%d)", __FUNCTION__, hlen);
goto done;
}
mlen = ntohs(hdr.op_len);
clicon_debug(2, "%s: rcv msg seq=%d, len=%d",
__FUNCTION__, ntohs(hdr.op_type), mlen);
if ((*msg = (struct clicon_msg *)chunk(mlen, label)) == NULL){
clicon_err(OE_CFG, errno, "%s: chunk", __FUNCTION__);
goto done;
}
memcpy(*msg, &hdr, hlen);
if ((len2 = read(s, (*msg)->op_body, mlen - sizeof(hdr))) < 0){
clicon_err(OE_CFG, errno, "%s: read", __FUNCTION__);
goto done;
}
if (len2 != mlen - sizeof(hdr)){
clicon_err(OE_CFG, errno, "%s: body too short", __FUNCTION__);
goto done;
}
if (debug > 1)
msg_dump(*msg);
retval = 0;
done:
if (0)
set_signal(SIGINT, oldhandler, NULL);
return retval;
}
/*! Connect to server, send an clicon_msg message and wait for result.
* Compared to clicon_rpc, this is a one-shot rpc: open, send, get reply and close.
* NOTE: this is dependent on unix domain
*/
int
clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath,
char **data,
uint16_t *datalen,
int *sock0,
const char *label)
{
int retval = -1;
int s = -1;
struct stat sb;
clicon_debug(1, "Send %s msg on %s",
msg_type2str(ntohs(msg->op_type)), sockpath);
/* special error handling to get understandable messages (otherwise ENOENT) */
if (stat(sockpath, &sb) < 0){
clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath);
goto done;
}
if (!S_ISSOCK(sb.st_mode)){
clicon_err(OE_PROTO, EIO, "%s: Not unix socket", sockpath);
goto done;
}
if ((s = clicon_connect_unix(sockpath)) < 0)
goto done;
if (clicon_rpc(s, msg, data, datalen, label) < 0)
goto done;
if (sock0 != NULL)
*sock0 = s;
retval = 0;
done:
if (sock0 == NULL && s >= 0)
close(s);
return retval;
}
/*! Connect to server, send an clicon_msg message and wait for result using an inet socket
* Compared to clicon_rpc, this is a one-shot rpc: open, send, get reply and close.
*/
int
clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst,
uint16_t port,
char **data,
uint16_t *datalen,
int *sock0,
const char *label)
{
int retval = -1;
int s = -1;
struct sockaddr_in addr;
clicon_debug(1, "Send %s msg to %s:%hu",
msg_type2str(ntohs(msg->op_type)), dst, port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(addr.sin_family, dst, &addr.sin_addr) != 1)
goto done; /* Could check getaddrinfo */
/* special error handling to get understandable messages (otherwise ENOENT) */
if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_CFG, errno, "socket");
return -1;
}
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){
clicon_err(OE_CFG, errno, "connecting socket inet4");
close(s);
goto done;
}
if (clicon_rpc(s, msg, data, datalen, label) < 0)
goto done;
if (sock0 != NULL)
*sock0 = s;
retval = 0;
done:
if (sock0 == NULL && s >= 0)
close(s);
return retval;
}
/*! Send a clicon_msg message and wait for 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] s Socket to communicate with backend
* @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[out] data Returned data as byte-strin exclusing header.
* Deallocate w unchunk...(..., label)
* @param[out] datalen Length of returned data
* @param[in] label Label used in chunk allocation.
*/
int
clicon_rpc(int s,
struct clicon_msg *msg,
char **data,
uint16_t *datalen,
const char *label)
{
int retval = -1;
struct clicon_msg *reply;
int eof;
uint32_t err;
uint32_t suberr;
char *reason;
enum clicon_msg_type type;
if (clicon_msg_send(s, msg) < 0)
goto done;
if (clicon_msg_rcv(s, &reply, &eof, label) < 0)
goto done;
if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__);
close(s);
errno = ESHUTDOWN;
goto done;
}
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_OK:
if (data != NULL) {
*data = reply->op_body;
*datalen = ntohs(reply->op_len) - sizeof(*reply);
}
break;
case CLICON_MSG_ERR:
if (clicon_msg_err_decode(reply, &err, &suberr, &reason, label) < 0)
goto done;
clicon_err(err, suberr, "%s", reason);
goto done;
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %d",
__FUNCTION__, type);
goto done;
break;
}
retval = 0;
done:
return retval;
}
int
send_msg_reply(int s,
uint16_t type,
char *data,
uint16_t datalen)
{
int retval = -1;
struct clicon_msg *reply;
uint16_t len;
len = sizeof(*reply) + datalen;
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
goto done;
memset(reply, 0, len);
reply->op_type = htons(type);
reply->op_len = htons(len);
if (datalen > 0)
memcpy(reply->op_body, data, datalen);
if (clicon_msg_send(s, reply) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
int
send_msg_ok(int s)
{
return send_msg_reply(s, CLICON_MSG_OK, NULL, 0);
}
int
send_msg_notify(int s,
int level,
char *event)
{
int retval = -1;
struct clicon_msg *msg;
if ((msg=clicon_msg_notify_encode(level, event, __FUNCTION__)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
int
send_msg_err(int s, int err, int suberr, char *format, ...)
{
va_list args;
char *reason;
int len;
int retval = -1;
struct clicon_msg *msg;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((reason = (char *)chunk(len, __FUNCTION__)) == NULL)
return -1;
memset(reason, 0, len);
va_start(args, format);
vsnprintf(reason, len, format, args);
va_end(args);
if ((msg=clicon_msg_err_encode(clicon_errno, clicon_suberrno,
reason, __FUNCTION__)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}