nfv/guest-comm/host-guest-comm-2.0/guest_agent.c

504 lines
14 KiB
C

/*
* Copyright (c) 2013-2016, Wind River Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3) Neither the name of Wind River Systems nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*/
#define _GNU_SOURCE /* for memmem() */
#include <stddef.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <syslog.h>
#include <limits.h>
#include <signal.h>
#include <execinfo.h>
#include <json-c/json.h>
#include "host_guest_msg_type.h"
/*
Notes on virtio-serial in guest:
1) The fact that POLLHUP is always set in revents when host connection is down
means that the only way to get event-driven notification of connection is to
register for SIGIO. However, then we get a SIGIO every time the device becomes
readable. The solution is to selectively block SIGIO as long as we think the link
is up, then unblock it so we get notified when the link comes back.
2) If the host disconnects we will still process any buffered messages from it.
3) If read() returns 0 or write() returns -1 with errno of EAGAIN that means
the host is disconnected.
*/
// Tokener serves as reassembly buffer for host connection.
struct json_tokener* tok;
int host_fd;
int app_fd;
volatile sig_atomic_t check_host_connection=1;
volatile sig_atomic_t initial_connection=1;
// Currently we only use 2 fds, one for server group messaging and one for the
// connection to the host.
#define MAX_FDS_GUEST 10
// Message has arrived from the host.
// This assumes the message has been validated
void process_msg(json_object *jobj_msg, int fd)
{
int rc;
struct sockaddr_un cliaddr;
int addrlen;
// parse version
struct json_object *jobj_version;
if (!json_object_object_get_ex(jobj_msg, VERSION, &jobj_version)) {
PRINT_ERR("failed to parse version\n");
return;
}
int version = json_object_get_int(jobj_version);
if (version != CUR_VERSION) {
PRINT_ERR("received version %d, expected %d\n", version, CUR_VERSION);
return;
}
// parse source addr
struct json_object *jobj_source_addr;
if (!json_object_object_get_ex(jobj_msg, SOURCE_ADDR, &jobj_source_addr)) {
PRINT_ERR("failed to parse source_addr\n");
return;
}
// parse dest addr
struct json_object *jobj_dest_addr;
if (!json_object_object_get_ex(jobj_msg, DEST_ADDR, &jobj_dest_addr)) {
PRINT_ERR("failed to parse dest_addr\n");
return;
}
const char *dest_addr = json_object_get_string(jobj_dest_addr);
// parse msg data
struct json_object *jobj_data;
if (!json_object_object_get_ex(jobj_msg, DATA, &jobj_data)) {
PRINT_ERR("failed to parse data\n");
return;
}
//create outgoing message
struct json_object *jobj_outmsg = json_object_new_object();
if (jobj_outmsg == NULL) {
PRINT_ERR("failed to allocate json object for jobj_outmsg\n");
return;
}
json_object_object_add(jobj_outmsg, DATA, jobj_data);
json_object_object_add(jobj_outmsg, VERSION, json_object_new_int(CUR_VERSION));
json_object_object_add(jobj_outmsg, SOURCE_ADDR, jobj_source_addr);
const char *outmsg = json_object_to_json_string_ext(jobj_outmsg, JSON_C_TO_STRING_PLAIN);
// Set up destination address
memset(&cliaddr, 0, sizeof(struct sockaddr_un));
cliaddr.sun_family = AF_UNIX;
cliaddr.sun_path[0] = '\0';
strncpy(cliaddr.sun_path+1, dest_addr, strlen(dest_addr));
addrlen = sizeof(sa_family_t) + strlen(dest_addr) + 1;
// Send the message to the client.
// This will get transparently restarted if interrupted by signal.
ssize_t outlen = strlen(outmsg);
rc = sendto(app_fd, outmsg, outlen, 0, (struct sockaddr *) &cliaddr,
addrlen);
if (rc == -1) {
PRINT_ERR("unable to send msg to %.*s: %m\n", UNIX_ADDR_LEN, cliaddr.sun_path+1);
} else if (rc != outlen) {
PRINT_ERR("sendto didn't send the whole message\n");
}
json_object_put(jobj_outmsg);
}
void handle_app_msg(const char *msg, struct sockaddr_un *cliaddr,
socklen_t cliaddrlen)
{
int rc;
char *app_addr;
//parse incoming msg
struct json_object *jobj_msg = json_tokener_parse(msg);
if (jobj_msg == NULL) {
PRINT_ERR("failed to parse msg\n");
return;
}
// parse version
struct json_object *jobj_version;
if (!json_object_object_get_ex(jobj_msg, VERSION, &jobj_version)) {
PRINT_ERR("failed to parse version\n");
goto done;
}
int version = json_object_get_int(jobj_version);
if (version != CUR_VERSION) {
PRINT_ERR("message from app version %d, expected %d, dropping\n",
version, CUR_VERSION);
goto done;
}
// parse dest instance
struct json_object *jobj_dest_addr;
if (!json_object_object_get_ex(jobj_msg, DEST_ADDR, &jobj_dest_addr)) {
PRINT_ERR("failed to parse dest_address\n");
goto done;
}
// parse data
struct json_object *jobj_data;
if (!json_object_object_get_ex(jobj_msg, DATA, &jobj_data)) {
PRINT_ERR("failed to parse data\n");
goto done;
}
if (cliaddr->sun_path[0] == '\0') {
app_addr = cliaddr->sun_path+1;
// get length without family or leading null from abstract namespace
cliaddrlen = cliaddrlen - sizeof(sa_family_t) - 1;
app_addr[cliaddrlen] = '\0';
} else {
PRINT_INFO("client address not in abstract namespace, dropping\n");
goto done;
}
struct json_object *jobj_outmsg = json_object_new_object();
if (jobj_outmsg == NULL) {
PRINT_ERR("failed to allocate json object for outmsg\n");
goto done;
}
json_object_object_add(jobj_outmsg, DATA, jobj_data);
json_object_object_add(jobj_outmsg, VERSION, jobj_version);
json_object_object_add(jobj_outmsg, DEST_ADDR, jobj_dest_addr);
json_object_object_add(jobj_outmsg, SOURCE_ADDR, json_object_new_string(app_addr));
const char *outmsg = json_object_to_json_string_ext(jobj_outmsg, JSON_C_TO_STRING_PLAIN);
// use '\n' to delimit JSON string: mark the beginning
rc = write(host_fd, "\n", 1);
if (rc == -1) {
PRINT_ERR("unable to send \\n \n");
}
// send to host
ssize_t outlen = strlen(outmsg);
rc = write(host_fd, outmsg, outlen);
if (rc == -1) {
PRINT_ERR("unable to send msg from %.*s: %m\n", UNIX_ADDR_LEN, app_addr);
} else if (rc != outlen) {
PRINT_ERR("write didn't write the whole message to host\n");
}
// use '\n' to delimit JSON string: mark the ending
rc = write(host_fd, "\n", 1);
if (rc == -1) {
PRINT_ERR("unable to send \\n \n");
}
json_object_put(jobj_outmsg);
done:
json_object_put(jobj_msg);
}
void unmask_sigio(void)
{
sigset_t mask;
sigemptyset (&mask);
sigaddset (&mask, SIGIO);
sigprocmask(SIG_UNBLOCK, &mask, 0);
}
void mask_sigio(void)
{
sigset_t mask;
sigemptyset (&mask);
sigaddset (&mask, SIGIO);
sigprocmask(SIG_BLOCK, &mask, 0);
}
void handle_host_conn_down()
{
check_host_connection = 0;
unmask_sigio();
}
void scan_host_fd(struct pollfd *pfd)
{
char buf[10000];
ssize_t rc;
if (pfd->revents & POLLIN) {
// Read all messages from the host device
while(1) {
rc = read(pfd->fd, buf, sizeof(buf));
if (rc == 0) {
// Connection to host has gone down
handle_host_conn_down();
return;
} else if (rc < 0) {
if (errno == EAGAIN)
// We've read all the messages
return;
else {
PRINT_ERR("read from host: %m");
return;
}
}
handle_virtio_serial_msg(buf, rc, pfd->fd, tok);
}
}
}
void scan_app_fd(struct pollfd *pfd)
{
char buf[10000];
struct sockaddr_un cliaddr;
ssize_t rc;
// Read all messages from the app socket
if (pfd->revents & POLLIN) {
while(1) {
socklen_t addrlen = sizeof(struct sockaddr_un);
rc = recvfrom(pfd->fd, buf, sizeof(buf), 0,
(struct sockaddr *) &cliaddr, &addrlen);
if (rc < 0) {
if (errno == EAGAIN)
// We've read all the messages
return;
else {
PRINT_ERR("recvfrom from app: %m");
return;
}
}
handle_app_msg(buf, &cliaddr, addrlen);
}
}
}
//we get a SIGIO if the host connection connects/disconnects
static void sigio_handler(int signal)
{
struct pollfd pfd;
pfd.fd = host_fd;
pfd.events = POLLIN;
poll(&pfd, 1, 0);
// if host is not connected, just exit
if (pfd.revents & POLLHUP)
return;
// host is connected so check it in main loop
check_host_connection = 1;
initial_connection = 1;
}
//dump stack trace on segfault
static void segv_handler(int signum)
{
int count;
void *syms[100];
int fd = open("/var/log/guest_agent_backtrace.log", O_RDWR|O_APPEND|O_CREAT, S_IRWXU);
if (fd == -1) {
PRINT_ERR("Unable to open guest agent backtrace file: %m");
goto out;
}
write(fd, "\n", 1);
count = backtrace(syms, 100);
if (count == 0) {
char *log = "Got zero items in backtrace.\n";
write(fd, log, strlen(log));
goto out;
}
backtrace_symbols_fd(syms, count, fd);
out:
fflush(NULL);
exit(-1);
}
int main(int argc, char **argv)
{
int flags;
int rc;
struct sockaddr_un svaddr;
int addrlen;
PRINT_INFO("%s starting up\n", *argv);
// optional arg for log level. Higher number means more logs
if (argc > 1) {
char *endptr, *str;
long val;
str = argv[1];
errno = 0;
val = strtol(str, &endptr, 0);
if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
|| (errno != 0 && val == 0)) {
PRINT_ERR("error parsing log level arg: strtol: %m");
exit(-1);
}
if (endptr == str) {
PRINT_ERR("No digits were found\n");
exit(EXIT_FAILURE);
}
if (val > LOG_DEBUG)
val = LOG_DEBUG;
setlogmask(LOG_UPTO(val));
} else
setlogmask(LOG_UPTO(LOG_WARNING));
signal(SIGIO, sigio_handler);
signal(SIGSEGV, segv_handler);
// set up fd for talking to host
host_fd = open("/dev/virtio-ports/cgcs.messaging", O_RDWR|O_NONBLOCK);
if (host_fd == -1) {
PRINT_ERR("problem with open: %m");
exit(-1);
}
flags = fcntl(host_fd, F_GETFL);
rc = fcntl(host_fd, F_SETFL, flags | O_ASYNC);
if (rc == -1) {
PRINT_ERR("problem setting host_fd async: %m");
exit(-1);
}
rc = fcntl(host_fd, F_SETOWN, getpid());
if (rc == -1) {
PRINT_ERR("problem owning host_fd: %m");
exit(-1);
}
// set up socket for talking to apps
app_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (app_fd == -1) {
PRINT_ERR("problem with socket: %m");
exit(-1);
}
flags = fcntl(app_fd, F_GETFL, 0);
fcntl(app_fd, F_SETFL, flags | O_NONBLOCK);
memset(&svaddr, 0, sizeof(struct sockaddr_un));
svaddr.sun_family = AF_UNIX;
svaddr.sun_path[0] = '\0';
strncpy(svaddr.sun_path+1, AGENT_ADDR, sizeof(svaddr.sun_path) - 2);
addrlen = sizeof(sa_family_t) + strlen(AGENT_ADDR) + 1;
if (bind(app_fd, (struct sockaddr *) &svaddr, addrlen) == -1) {
PRINT_ERR("problem with bind: %m");
exit(-1);
}
tok = json_tokener_new();
while(1) {
struct pollfd pollfds[MAX_FDS_GUEST];
int i;
int nfds = 0;
if (check_host_connection) {
// we think the host connection is up
if (initial_connection) {
//mask SIGIO if we haven't already
mask_sigio();
initial_connection=0;
}
pollfds[nfds].fd = host_fd;
pollfds[nfds].events = POLLIN;
nfds++;
}
pollfds[nfds].fd = app_fd;
pollfds[nfds].events = POLLIN;
nfds++;
if (nfds > 0) {
rc = poll(pollfds, nfds, -1);
if (rc == -1) {
if (errno == EINTR)
continue;
PRINT_ERR("problem with poll: %m");
free(tok);
exit(-1);
}
for(i=0;i<nfds;i++) {
if (pollfds[i].fd == host_fd)
scan_host_fd(pollfds+i);
if (pollfds[i].fd == app_fd)
scan_app_fd(pollfds+i);
}
} else
// no connected fds, just wait for SIGIO
pause();
}
free(tok);
return 0;
}