85cd488bef
Signed-off-by: Dean Troyer <dtroyer@gmail.com>
462 lines
14 KiB
C
462 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.
|
|
*/
|
|
|
|
/*
|
|
This is a backchannel messaging agent that will run on the host in order to
|
|
pass messages between the host and the guest.
|
|
|
|
When a new instance appears (detected by a new unix socket of the specified
|
|
format being added to the watched directory) we open a unix stream socket
|
|
and connect to the instance, storing the mapping from instance to socket.
|
|
|
|
When an instance dies we will close the socket and remove the mapping.
|
|
|
|
We will monitor the connections to the instances as well as a unix datagram
|
|
socket used to communicate with other apps on the host. Messages coming
|
|
from an instance will be forwarded to the appropriate app, and vice versa.
|
|
|
|
If we try to send a message to a guest socket that has nothing listening
|
|
within the guest, by default the message will get queued up without giving
|
|
us any indication that there is no listener. These messages can get bundled
|
|
together when they get delivered to the guest.
|
|
*/
|
|
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <linux/un.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <sys/time.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <limits.h>
|
|
#include <syslog.h>
|
|
#include <execinfo.h>
|
|
#include <json-c/json.h>
|
|
|
|
#include "misc.h"
|
|
#include "host_guest_msg_type.h"
|
|
#include "host_instance_mgmt.h"
|
|
|
|
int app_fd;
|
|
|
|
// One tokener for each instance connection serve as reassembly buffer
|
|
struct json_tokener* tok[MAX_FDS_HOST];
|
|
|
|
#define SERVER_SOCKET_FORMAT \
|
|
"/var/lib/libvirt/qemu/cgcs.messaging.%s.sock"
|
|
|
|
// Message has arrived from the guest.
|
|
// This assumes the message has been validated
|
|
void process_msg(json_object *jobj_msg, int fd)
|
|
{
|
|
int rc;
|
|
struct sockaddr_un cliaddr;
|
|
char *name;
|
|
int addrlen;
|
|
|
|
name = instance_name_by_fd(fd);
|
|
if (!name) {
|
|
PRINT_ERR("whoops, can't get instance name from fd, dropping\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");
|
|
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 data. data is a json object that is nested inside the msg
|
|
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);
|
|
json_object_object_add(jobj_outmsg, SOURCE_INSTANCE, json_object_new_string(name));
|
|
|
|
const char *outmsg = json_object_to_json_string_ext(jobj_outmsg, JSON_C_TO_STRING_PLAIN);
|
|
ssize_t outlen = strlen(outmsg);
|
|
|
|
// 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.
|
|
rc = sendto(app_fd, outmsg, outlen, 0, (struct sockaddr *) &cliaddr,
|
|
addrlen);
|
|
if (rc == -1) {
|
|
PRINT_ERR("unable to send msg to client %.*s: %m\n", UNIX_ADDR_LEN,
|
|
cliaddr.sun_path+1);
|
|
} else if (rc != outlen) {
|
|
PRINT_ERR("sendto didn't send the whole message to client\n");
|
|
}
|
|
json_object_put(jobj_outmsg);
|
|
}
|
|
|
|
// Read and process all messages from the guest. If the guest dies, tear
|
|
// down the connection.
|
|
void scan_guest_fd(struct pollfd *pfd)
|
|
{
|
|
char buf[10000];
|
|
ssize_t rc;
|
|
|
|
if (pfd->revents & POLLHUP) {
|
|
// The only known cause of this is the death of the qemu process.
|
|
// Tear everything down.
|
|
disconnect_guest(pfd->fd);
|
|
} else if (pfd->revents & POLLIN) {
|
|
// Read a message from the guest socket.
|
|
// We assume that all the data arrives in one packet.
|
|
// To handle arbitrary messages sizes we should receive into a buffer
|
|
// with knowledge of message length, etc. Can extend later if needed.
|
|
rc = read(pfd->fd, buf, sizeof(buf));
|
|
if (rc == 0) {
|
|
PRINT_INFO("got read of 0 bytes on guest fd\n");
|
|
return;
|
|
} else if (rc < 0) {
|
|
if (errno == EAGAIN)
|
|
// Odd, should have been a message
|
|
return;
|
|
else {
|
|
PRINT_ERR("read from guest: %m");
|
|
return;
|
|
}
|
|
}
|
|
handle_virtio_serial_msg(buf, rc, pfd->fd, tok[pfd->fd]);
|
|
}
|
|
}
|
|
|
|
|
|
// validate header and send message to specified guest
|
|
void handle_app_msg(const char *msg, struct sockaddr_un *cliaddr,
|
|
socklen_t cliaddrlen)
|
|
{
|
|
int rc;
|
|
char *app_addr;
|
|
int sock;
|
|
|
|
//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;
|
|
int version;
|
|
if (!json_object_object_get_ex(jobj_msg, VERSION, &jobj_version)) {
|
|
PRINT_ERR("failed to parse version\n");
|
|
goto done;
|
|
}
|
|
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 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_address\n");
|
|
goto done;
|
|
}
|
|
|
|
// parse dest instance
|
|
struct json_object *jobj_dest_instance;
|
|
if (!json_object_object_get_ex(jobj_msg, DEST_INSTANCE, &jobj_dest_instance)) {
|
|
PRINT_ERR("failed to parse dest_instance\n");
|
|
goto done;
|
|
}
|
|
const char *dest_instance = json_object_get_string(jobj_dest_instance);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// look up the guest socket based on the instance name
|
|
sock = fd_find_by_instance_name((char *)dest_instance);
|
|
if (sock == -1) {
|
|
PRINT_INFO("unable to find instance connection socket for %.20s\n",
|
|
dest_instance);
|
|
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);
|
|
|
|
// send to guest
|
|
ssize_t outlen = strlen(outmsg);
|
|
rc = send(sock, outmsg, outlen, 0);
|
|
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\n");
|
|
}
|
|
|
|
// use '\n' to delimit JSON string
|
|
rc = send(sock, "\n", 1, 0);
|
|
if (rc == -1) {
|
|
PRINT_ERR("unable to send \\n \n");
|
|
}
|
|
|
|
json_object_put(jobj_outmsg);
|
|
|
|
done:
|
|
json_object_put(jobj_msg);
|
|
}
|
|
|
|
// Read and process a message from the application socket
|
|
void scan_app_fd()
|
|
{
|
|
char buf[10000];
|
|
struct sockaddr_un cliaddr;
|
|
ssize_t rc;
|
|
|
|
// Process a message from the app socket
|
|
socklen_t addrlen = sizeof(struct sockaddr_un);
|
|
rc = recvfrom(app_fd, buf, sizeof(buf), 0,
|
|
(struct sockaddr *) &cliaddr, &addrlen);
|
|
if (rc < 0) {
|
|
if (errno == EAGAIN)
|
|
// Odd, should have been a message
|
|
return;
|
|
else {
|
|
PRINT_ERR("error in recvfrom from app: %m");
|
|
return;
|
|
}
|
|
}
|
|
handle_app_msg(buf, &cliaddr, addrlen);
|
|
}
|
|
|
|
//dump stack trace on segfault
|
|
static void segv_handler(int signum)
|
|
{
|
|
int count;
|
|
void *syms[100];
|
|
int fd = open("/var/log/host_agent_backtrace.log", O_RDWR|O_APPEND|O_CREAT, S_IRWXU);
|
|
if (fd == -1) {
|
|
PRINT_ERR("Unable to open host 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);
|
|
}
|
|
|
|
void free_tok()
|
|
{
|
|
int i;
|
|
for (i=0; i<MAX_FDS_HOST; i++) {
|
|
free(tok[i]);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct sockaddr_un svaddr;
|
|
int rc;
|
|
int flags;
|
|
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(SIGSEGV, segv_handler);
|
|
|
|
// 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 to agent addr: %m");
|
|
exit(-1);
|
|
}
|
|
|
|
pollfd_add(app_fd, POLLIN);
|
|
|
|
// This will set up monitoring for new/deleted instances
|
|
// and will set up connections for existing instances.
|
|
if (server_scan_init() < 0)
|
|
return -1;
|
|
|
|
int i;
|
|
for (i=0; i<MAX_FDS_HOST; i++) {
|
|
tok[i] = json_tokener_new();
|
|
}
|
|
|
|
while(1) {
|
|
int i;
|
|
|
|
// The timeout is for processing delayed connection retries.
|
|
rc = poll(pollfds, nfds, next_connection_retry_interval());
|
|
|
|
if (rc == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
PRINT_ERR("problem with poll: %m");
|
|
free_tok();
|
|
exit(-1);
|
|
}
|
|
|
|
// With poll() we can't tell if we timed out without actually checking
|
|
// the time, so run this unconditionally.
|
|
process_connection_retries();
|
|
|
|
for(i=0;i<nfds;i++) {
|
|
if ((pollfds[i].fd == app_fd) && (pollfds[i].revents & POLLIN))
|
|
scan_app_fd();
|
|
else if ((pollfds[i].fd == inotify_fd) && (pollfds[i].revents & POLLIN))
|
|
handle_inotify_event();
|
|
else
|
|
scan_guest_fd(pollfds+i);
|
|
}
|
|
}
|
|
|
|
free_tok();
|
|
return 0;
|
|
}
|