diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
commit | 86ed03f8adee56c050c73018537371c230a664a6 (patch) | |
tree | eae3d04cdf1c49848e5a671327ab38297f4acb0d /agents/virt/server | |
parent | Initial commit. (diff) | |
download | fence-agents-86ed03f8adee56c050c73018537371c230a664a6.tar.xz fence-agents-86ed03f8adee56c050c73018537371c230a664a6.zip |
Adding upstream version 4.12.1.upstream/4.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | agents/virt/server/Makefile.am | 79 | ||||
-rw-r--r-- | agents/virt/server/config.c | 698 | ||||
-rw-r--r-- | agents/virt/server/cpg-virt.c | 643 | ||||
-rw-r--r-- | agents/virt/server/cpg.c | 411 | ||||
-rw-r--r-- | agents/virt/server/cpg.h | 29 | ||||
-rw-r--r-- | agents/virt/server/daemon_init.c | 215 | ||||
-rw-r--r-- | agents/virt/server/history.c | 124 | ||||
-rw-r--r-- | agents/virt/server/libvirt.c | 359 | ||||
-rw-r--r-- | agents/virt/server/main.c | 281 | ||||
-rw-r--r-- | agents/virt/server/mcast.c | 622 | ||||
-rw-r--r-- | agents/virt/server/plugin.c | 417 | ||||
-rw-r--r-- | agents/virt/server/serial.c | 459 | ||||
-rw-r--r-- | agents/virt/server/serial.h | 20 | ||||
-rw-r--r-- | agents/virt/server/static_map.c | 237 | ||||
-rw-r--r-- | agents/virt/server/tcp.c | 575 | ||||
-rw-r--r-- | agents/virt/server/uuid-test.c | 66 | ||||
-rw-r--r-- | agents/virt/server/uuid-test.h | 14 | ||||
-rw-r--r-- | agents/virt/server/virt-serial.c | 444 | ||||
-rw-r--r-- | agents/virt/server/virt-sockets.c | 242 | ||||
-rw-r--r-- | agents/virt/server/virt.c | 630 | ||||
-rw-r--r-- | agents/virt/server/virt.h | 62 | ||||
-rw-r--r-- | agents/virt/server/vsock.c | 565 |
22 files changed, 7192 insertions, 0 deletions
diff --git a/agents/virt/server/Makefile.am b/agents/virt/server/Makefile.am new file mode 100644 index 0000000..fbca617 --- /dev/null +++ b/agents/virt/server/Makefile.am @@ -0,0 +1,79 @@ +############################################################################### +############################################################################### +## +## Copyright (C) 2009-2021 Red Hat, Inc. +## +## This copyrighted material is made available to anyone wishing to use, +## modify, copy, or redistribute it subject to the terms and conditions +## of the GNU General Public License v.2. +## +############################################################################### +############################################################################### + +MAINTAINERCLEANFILES = Makefile.in + +noinst_HEADERS = cpg.h serial.h uuid-test.h virt.h + +sbin_PROGRAMS = fence_virtd + +# +# daemon +# +fence_virtd_SOURCES = main.c plugin.c config.c static_map.c uuid-test.c \ + daemon_init.c + +fence_virtd_CFLAGS = $(VIRT_AM_CFLAGS) \ + $(nss_CFLAGS) $(xml2_CFLAGS) $(uuid_CFLAGS) $(PTHREAD_CFLAGS) \ + $(AM_CFLAGS) + +fence_virtd_LDADD = $(VIRT_CONFIG_LIBS) $(VIRT_COMMON_LIBS) \ + $(nss_LIBS) $(xml2_LIBS) $(uuid_LIBS) $(PTHREAD_LIBS) $(dl_LIBS) + +fence_virtd_LDFLAGS = $(VIRT_AM_LDFLAGS) $(VIRT_COMMON_LDFLAGS) + +virt_la_SOURCES = libvirt.c virt.c uuid-test.c +cpg_la_SOURCES = cpg-virt.c cpg.c virt.c uuid-test.c +multicast_la_SOURCES = mcast.c history.c +tcp_la_SOURCES = tcp.c history.c +vsock_la_SOURCES = vsock.c history.c +serial_la_SOURCES = virt-serial.c virt-sockets.c serial.c history.c + +fence_virtd_CFLAGS += -DMODULE_PATH=\"$(libdir)/fence-virt/\" + +fvlibdir = $(libdir)/fence-virt + +fvlib_LTLIBRARIES = + +MODULESCFLAGS = $(VIRT_AM_CFLAGS) $(AM_CFLAGS) +MODULESLDFLAGS = $(VIRT_AM_LDFLAGS) $(VIRT_COMMON_LIBS) $(VIRT_COMMON_LDFLAGS) -module -avoid-version -export-dynamic + +if modlibvirt +fvlib_LTLIBRARIES += virt.la +virt_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) $(virt_CFLAGS) +virt_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) $(virt_LIBS) +endif +if modcpg +fvlib_LTLIBRARIES += cpg.la +cpg_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) $(cpg_CFLAGS) $(virt_CFLAGS) +cpg_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) $(cpg_LIBS) $(virt_LIBS) +endif +if modmulticast +fvlib_LTLIBRARIES += multicast.la +multicast_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) +multicast_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) +endif +if modserial +fvlib_LTLIBRARIES += serial.la +serial_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) $(xml2_CFLAGS) $(virt_CFLAGS) +serial_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) $(xml2_LIBS) $(virt_LIBS) +endif +if modtcp +fvlib_LTLIBRARIES += tcp.la +tcp_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) +tcp_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) +endif +if modvsock +fvlib_LTLIBRARIES += vsock.la +vsock_la_CFLAGS = $(MODULESCFLAGS) $(nss_CFLAGS) +vsock_la_LDFLAGS = $(MODULESLDFLAGS) $(nss_LIBS) +endif diff --git a/agents/virt/server/config.c b/agents/virt/server/config.c new file mode 100644 index 0000000..fa9af97 --- /dev/null +++ b/agents/virt/server/config.c @@ -0,0 +1,698 @@ +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <errno.h> + +#include "simpleconfig.h" +#include "static_map.h" +#include "mcast.h" +#include "xvm.h" +#include "server_plugin.h" +#include "simple_auth.h" + + +static int +yesno(const char *prompt, int dfl) +{ + char result[10]; + + printf("%s [%c/%c]? ", prompt, dfl?'Y':'y', dfl?'n':'N'); + fflush(stdout); + + memset(result, 0, sizeof(result)); + if (fgets(result, 9, stdin) == NULL) + return dfl; + + if (result[0] == 'y' || result[0] == 'Y') + return 1; + if (result[0] == 'n' || result[0] == 'N') + return 0; + + return dfl; +} + + +static int +text_input(const char *prompt, const char *dfl, char *input, size_t len) +{ + const char *tmpdfl = dfl; + const char *nulldfl = ""; + + if (dfl == NULL) { + tmpdfl = nulldfl; + } + + printf("%s [%s]: ", prompt, tmpdfl); + fflush(stdout); + + memset(input, 0, len); + if (fgets(input, len, stdin) == NULL) { + strncpy(input, tmpdfl, len); + return 0; + } + if (input[strlen(input)-1] == '\n') + input[strlen(input)-1] = 0; + + if (strlen(input) == 0) { + strncpy(input, tmpdfl, len); + return 0; + } + + return 0; +} + + +static int +plugin_path_configure(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + int done = 0; + + if (sc_get(config, "fence_virtd/@module_path", val, + sizeof(val))) { +#ifdef MODULE_PATH + snprintf(val, sizeof(val), MODULE_PATH); +#else + printf("Failed to determine module search path.\n"); +#endif + } + + do { + text_input("Module search path", val, inp, sizeof(inp)); + + printf("\n"); + done = plugin_search(inp); + if (done > 0) { + plugin_dump(); + done = 1; + } else { + done = 0; + printf("No modules found in %s!\n", inp); + if (yesno("Use this value anyway", 0) == 1) + done = 1; + } + } while (!done); + + sc_set(config, "fence_virtd/@module_path", inp); + + return 0; +} + + +static int +backend_config_libvirt(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + + printf("\n"); + printf("The libvirt backend module is designed for single desktops or\n" + "servers. Do not use in environments where virtual machines\n" + "may be migrated between hosts.\n\n"); + + /* Default backend plugin */ + if (sc_get(config, "backends/libvirt/@uri", val, + sizeof(val))) { + strncpy(val, DEFAULT_HYPERVISOR_URI, sizeof(val)); + } + + text_input("Libvirt URI", val, inp, sizeof(inp)); + + sc_set(config, "backends/libvirt/@uri", inp); + + return 0; +} + + +static int +backend_config_cpg(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + int done = 0; + + printf("\n"); + printf("The CPG backend module is designed for use in clusters\n" + "running corosync and libvirt. It utilizes the CPG API to \n" + "route fencing requests, finally utilizing libvirt to perform\n" + "fencing actions.\n\n"); + + if (sc_get(config, "backends/cpg/@uri", val, + sizeof(val))) { + strncpy(val, DEFAULT_HYPERVISOR_URI, sizeof(val)); + } + + text_input("Libvirt URI", val, inp, sizeof(inp)); + + sc_set(config, "backends/cpg/@uri", inp); + + printf("\n"); + printf("The name mode is how the cpg plugin stores and \n" + "references virtual machines. Since virtual machine names\n" + "are not guaranteed to be unique cluster-wide, use of UUIDs\n" + "is strongly recommended. However, for compatibility with \n" + "fence_xvmd, the use of 'name' mode is also supported.\n\n"); + + if (sc_get(config, "backends/cpg/@name_mode", val, + sizeof(val))) { + strncpy(val, "uuid", sizeof(val)); + } + + do { + text_input("VM naming/tracking mode (name or uuid)", + val, inp, sizeof(inp)); + if (!strcasecmp(inp, "uuid")) { + done = 1; + } else if (!strcasecmp(inp, "name")) { + done = 0; + printf("This can be dangerous if you do not take care to" + "ensure that\n" + "virtual machine names are unique " + "cluster-wide.\n"); + if (yesno("Use name mode anyway", 1) == 1) + done = 1; + } + } while (!done); + + sc_set(config, "backends/cpg/@name_mode", inp); + + return 0; +} + + +static int +listener_config_multicast(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + const char *family = "ipv4"; + struct in_addr sin; + struct in6_addr sin6; + int done = 0; + + printf("\n"); + printf("The multicast listener module is designed for use environments\n" + "where the guests and hosts may communicate over a network using\n" + "multicast.\n\n"); + + + /* MULTICAST IP ADDRESS/FAMILY */ + printf("The multicast address is the address that a client will use to\n" + "send fencing requests to fence_virtd.\n\n"); + + if (sc_get(config, "listeners/multicast/@address", + val, sizeof(val)-1)) { + strncpy(val, IPV4_MCAST_DEFAULT, sizeof(val)); + } + + done = 0; + do { + text_input("Multicast IP Address", val, inp, sizeof(inp)); + + if (inet_pton(AF_INET, inp, &sin) == 1) { + printf("\nUsing ipv4 as family.\n\n"); + family = "ipv4"; + done = 1; + } else if (inet_pton(AF_INET6, inp, &sin6) == 1) { + printf("\nUsing ipv6 as family.\n\n"); + family = "ipv6"; + done = 1; + } else + printf("'%s' is not a valid IP address!\n", inp); + } while (!done); + + sc_set(config, "listeners/multicast/@family", family); + sc_set(config, "listeners/multicast/@address", inp); + + /* MULTICAST IP PORT */ + if (sc_get(config, "listeners/multicast/@port", + val, sizeof(val)-1)) { + snprintf(val, sizeof(val), "%d", DEFAULT_MCAST_PORT); + } + + done = 0; + do { + char *p; + int ret; + + text_input("Multicast IP Port", val, inp, sizeof(inp)); + ret = strtol(inp, &p, 0); + if (*p != '\0' || ret <= 0 || ret >= 65536) { + printf("Port value '%s' is out of range\n", val); + continue; + } else + done = 1; + } while (!done); + + sc_set(config, "listeners/multicast/@port", inp); + + /* MULTICAST INTERFACE */ + printf("\nSetting a preferred interface causes fence_virtd to listen only\n" + "on that interface. Normally, it listens on all interfaces.\n" + "In environments where the virtual machines are using the host\n" + "machine as a gateway, this *must* be set (typically to virbr0).\n" + "Set to 'none' for no interface.\n\n" + ); + + if (sc_get(config, "listeners/multicast/@interface", + val, sizeof(val)-1)) { + strncpy(val, "none", sizeof(val)); + } + + done = 0; + do { + text_input("Interface", val, inp, sizeof(inp)); + + if (!strcasecmp(inp, "none")) { + break; + } + + if (strlen(inp) > 0) { + int ret; + + ret = if_nametoindex(inp); + if (ret < 0) { + printf("Invalid interface: %s\n", inp); + if (yesno("Use anyway", 1) == 1) + done = 1; + } else + done = 1; + } else + printf("No interface given\n"); + } while (!done); + + if (!strcasecmp(inp, "none")) { + sc_set(config, "listeners/multicast/@interface", NULL); + } else { + sc_set(config, "listeners/multicast/@interface", inp); + } + + + /* KEY FILE */ + printf("\nThe key file is the shared key information which is used to\n" + "authenticate fencing requests. The contents of this file must\n" + "be distributed to each physical host and virtual machine within\n" + "a cluster.\n\n"); + + if (sc_get(config, "listeners/multicast/@key_file", + val, sizeof(val)-1)) { + strncpy(val, DEFAULT_KEY_FILE, sizeof(val)); + } + + done = 0; + do { + text_input("Key File", val, inp, sizeof(inp)); + + if (!strcasecmp(inp, "none")) { + break; + } + + if (strlen(inp) > 0) { + if (inp[0] != '/') { + printf("Invalid key file: %s\n", inp); + if (yesno("Use anyway", 1) == 1) + done = 1; + } else + done = 1; + } else + printf("No key file given\n"); + } while (!done); + + if (!strcasecmp(inp, "none")) { + sc_set(config, "listeners/multicast/@key_file", NULL); + } else { + sc_set(config, "listeners/multicast/@key_file", inp); + } + + return 0; +} + +static int +listener_config_tcp(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + const char *family = "ipv4"; + struct in_addr sin; + struct in6_addr sin6; + int done = 0; + + printf("\n"); + printf("The TCP listener module is designed for use in environments\n" + "where the guests and hosts communicate over viosproxy.\n\n"); + + /* IP ADDRESS/FAMILY */ + printf("The IP address is the address that a client will use to\n" + "send fencing requests to fence_virtd.\n\n"); + + if (sc_get(config, "listeners/tcp/@address", + val, sizeof(val)-1)) { + strncpy(val, IPV4_MCAST_DEFAULT, sizeof(val)); + } + + done = 0; + do { + text_input("TCP Listen IP Address", val, inp, sizeof(inp)); + + if (inet_pton(AF_INET, inp, &sin) == 1) { + printf("\nUsing ipv4 as family.\n\n"); + family = "ipv4"; + done = 1; + } else if (inet_pton(AF_INET6, inp, &sin6) == 1) { + printf("\nUsing ipv6 as family.\n\n"); + family = "ipv6"; + done = 1; + } else { + printf("'%s' is not a valid IP address!\n", inp); + continue; + } + } while (!done); + + sc_set(config, "listeners/tcp/@family", family); + sc_set(config, "listeners/tcp/@address", inp); + + /* MULTICAST IP PORT */ + if (sc_get(config, "listeners/tcp/@port", + val, sizeof(val)-1)) { + snprintf(val, sizeof(val), "%d", DEFAULT_MCAST_PORT); + } + + done = 0; + do { + char *p; + int ret; + + text_input("TCP Listen Port", val, inp, sizeof(inp)); + + ret = strtol(inp, &p, 0); + if (*p != '\0' || ret <= 0 || ret >= 65536) { + printf("Port value '%s' is out of range\n", val); + continue; + } + done = 1; + } while (!done); + sc_set(config, "listeners/tcp/@port", inp); + + /* KEY FILE */ + printf("\nThe key file is the shared key information which is used to\n" + "authenticate fencing requests. The contents of this file must\n" + "be distributed to each physical host and virtual machine within\n" + "a cluster.\n\n"); + + if (sc_get(config, "listeners/tcp/@key_file", + val, sizeof(val)-1)) { + strncpy(val, DEFAULT_KEY_FILE, sizeof(val)); + } + + done = 0; + do { + text_input("Key File", val, inp, sizeof(inp)); + + if (!strcasecmp(inp, "none")) { + break; + } + + if (strlen(inp) > 0) { + if (inp[0] != '/') { + printf("Invalid key file: %s\n", inp); + if (yesno("Use anyway", 1) == 1) + done = 1; + } else + done = 1; + } else + printf("No key file given\n"); + } while (!done); + + if (!strcasecmp(inp, "none")) { + sc_set(config, "listeners/tcp/@key_file", NULL); + } else { + sc_set(config, "listeners/tcp/@key_file", inp); + } + + return 0; +} + +static int +listener_config_serial(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + int done; + + printf("\n"); + printf("The serial plugin allows fence_virtd to communicate with\n" + "guests using serial or guest-forwarding VMChannel instead\n" + "of using TCP/IP networking.\n\n"); + printf("Special configuration of virtual machines is required. See\n" + "fence_virt.conf(5) for more details.\n\n"); + + if (sc_get(config, "listeners/serial/@uri", + val, sizeof(val)-1)) { + strncpy(val, DEFAULT_HYPERVISOR_URI, sizeof(val)); + } + + text_input("Libvirt URI", val, inp, sizeof(inp)); + + printf("\nSetting a socket path prevents fence_virtd from taking\n" + "hold of all Unix domain sockets created when the guest\n" + "is started. A value like /var/run/cluster/fence might\n" + "be a good value. Don't forget to create the directory!\n\n"); + + if (sc_get(config, "listeners/serial/@path", + val, sizeof(val)-1)) { + strncpy(val, "none", sizeof(val)); + } + + text_input("Socket directory", val, inp, sizeof(inp)); + if (!strcasecmp(inp, "none")) { + sc_set(config, "listeners/serial/@path", NULL); + } else { + sc_set(config, "listeners/serial/@path", inp); + } + + printf("\nThe serial plugin allows two types of guest to host\n" + "configurations. One is via a serial port; the other is\n" + "utilizing the newer VMChannel.\n\n"); + + if (sc_get(config, "listeners/serial/@mode", + val, sizeof(val)-1)) { + strncpy(val, "serial", sizeof(val)); + } + + if (!strcasecmp(inp, "none")) { + sc_set(config, "listeners/serial/@path", NULL); + } else { + sc_set(config, "listeners/serial/@path", inp); + } + + done = 0; + do { + text_input("Mode (serial or vmchannel)", val, inp, + sizeof(inp)); + + if (strcasecmp(inp, "serial") && strcasecmp(inp, "vmchannel")) { + printf("Invalid mode: %s\n", inp); + if (yesno("Use anyway", 1) == 1) + done = 1; + } else + done = 1; + } while (!done); + + sc_set(config, "listeners/serial/@mode", inp); + return 0; +} + + +static int +backend_configure(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + int done; + + printf("\n"); + printf("Backend modules are responsible for routing requests to\n" + "the appropriate hypervisor or management layer.\n\n"); + + /* Default backend plugin */ + if (sc_get(config, "fence_virtd/@backend", val, + sizeof(val))) { + strncpy(val, "libvirt", sizeof(val)); + } + + done = 0; + do { + text_input("Backend module", val, inp, sizeof(inp)); + if (plugin_find_backend(inp) == NULL) { + printf("No backend module named %s found!\n", inp); + if (yesno("Use this value anyway", 0) == 1) + done = 1; + } else + done = 1; + } while (!done); + + sc_set(config, "fence_virtd/@backend", inp); + + if (!strcmp(inp, "libvirt")) { + backend_config_libvirt(config); + } else if (!strcmp(inp, "cpg")) { + backend_config_cpg(config); + } + + return 0; +} + + +static int +listener_configure(config_object_t *config) +{ + char val[4096]; + char inp[4096]; + int done; + + printf("\n"); + printf("Listener modules are responsible for accepting requests\n" + "from fencing clients.\n\n"); + + /* Default backend plugin */ + if (sc_get(config, "fence_virtd/@listener", val, + sizeof(val))) { + strncpy(val, "multicast", sizeof(val)); + } + + done = 0; + do { + text_input("Listener module", val, inp, sizeof(inp)); + if (plugin_find_listener(inp) == NULL) { + printf("No listener module named %s found!\n", inp); + if (yesno("Use this value anyway", 0) == 1) + done = 1; + } else + done = 1; + } while (!done); + + sc_set(config, "fence_virtd/@listener", inp); + if (!strcmp(inp, "multicast")) + listener_config_multicast(config); + else if (!strcmp(inp, "tcp")) + listener_config_tcp(config); + else if (!strcmp(inp, "serial")) + listener_config_serial(config); + else + printf("Unable to configure unknown listner module '%s'\n", inp); + + return 0; +} + + +int +check_file_permissions(const char *fname) +{ + struct stat st; + mode_t file_perms = 0600; + int ret; + + ret = stat(fname, &st); + if (ret != 0) { + printf("stat failed on file '%s': %s\n", + fname, strerror(errno)); + return 1; + } + + if ((st.st_mode & 0777) != file_perms) { + printf("Insecure permissions on file " + "'%s': changing from 0%o to 0%o.\n", fname, + (unsigned int)(st.st_mode & 0777), + (unsigned int)file_perms); + if (chmod(fname, file_perms) != 0) { + printf("Unable to change permissions for file '%s'", + fname); + return 1; + } + } + + return 0; +} + +int +do_configure(config_object_t *config, const char *config_file) +{ + FILE *fp = NULL; + char message[80]; + char tmp_filename[4096]; + int tmp_fd = -1; + mode_t old_umask; + + if (sc_parse(config, config_file) != 0) { + printf("Parsing of %s failed.\n", config_file); + if (yesno("Start from scratch", 0) == 0) { + return 1; + } + } + + plugin_path_configure(config); + listener_configure(config); + backend_configure(config); + + printf("\nConfiguration complete.\n\n"); + + printf("=== Begin Configuration ===\n"); + sc_dump(config, stdout); + printf("=== End Configuration ===\n"); + + snprintf(message, sizeof(message), "Replace %s with the above", + config_file); + if (yesno(message, 0) == 0) { + return 1; + } + + snprintf(tmp_filename, sizeof(tmp_filename), + "%s.XXXXXX", config_file); + + old_umask = umask(077); + tmp_fd = mkstemp(tmp_filename); + umask(old_umask); + + if (tmp_fd < 0) { + perror("fopen"); + printf("Failed to write configuration file!\n"); + return 1; + } + + fp = fdopen(tmp_fd, "w+"); + if (fp == NULL) + goto out_fail; + + sc_dump(config, fp); + + if (rename(tmp_filename, config_file) < 0) { + perror("rename"); + goto out_fail; + } + + fclose(fp); + close(tmp_fd); + + return 0; + +out_fail: + if (fp) + fclose(fp); + if (tmp_fd >= 0) + close(tmp_fd); + if (strlen(tmp_filename)) + unlink(tmp_filename); + printf("Failed to write configuration file!\n"); + return 1; +} diff --git a/agents/virt/server/cpg-virt.c b/agents/virt/server/cpg-virt.c new file mode 100644 index 0000000..304519c --- /dev/null +++ b/agents/virt/server/cpg-virt.c @@ -0,0 +1,643 @@ +/* + Copyright Red Hat, Inc. 2017 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ +/* + * Author: Ryan McCabe <rmccabe@redhat.com> + */ +#include "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <time.h> +#include <string.h> +#include <syslog.h> +#include <errno.h> +#include <unistd.h> +#include <pthread.h> +#include <corosync/cpg.h> + +#include "debug.h" +#include "virt.h" +#include "xvm.h" +#include "cpg.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" + +#define NAME "cpg" +#define CPG_VERSION "0.1" + +#define MAGIC 0x38e93fc2 + +struct cpg_info { + int magic; + config_object_t *config; + int vp_count; + virConnectPtr *vp; +}; + +#define VALIDATE(arg) \ +do {\ + if (!arg || ((struct cpg_info *) arg)->magic != MAGIC) { \ + errno = EINVAL;\ + return -1; \ + } \ +} while(0) + +static struct cpg_info *cpg_virt_handle = NULL; +static int use_uuid = 0; +pthread_mutex_t local_vm_list_lock = PTHREAD_MUTEX_INITIALIZER; +static virt_list_t *local_vm_list = NULL; + +pthread_mutex_t remote_vm_list_lock = PTHREAD_MUTEX_INITIALIZER; +static virt_list_t *remote_vm_list = NULL; + +static void cpg_virt_init_libvirt(struct cpg_info *info); + +static int +virt_list_update(struct cpg_info *info, virt_list_t **vl, int my_id) +{ + virt_list_t *list = NULL; + + if (*vl) + vl_free(*vl); + + list = vl_get(info->vp, info->vp_count, my_id); + if (!list && (errno == EPIPE || errno == EINVAL)) { + do { + cpg_virt_init_libvirt(info); + } while (info->vp_count == 0); + list = vl_get(info->vp, info->vp_count, my_id); + } + + *vl = list; + if (!list) + return -1; + + return 0; +} + + +static void +store_domains(virt_list_t *vl) +{ + int i; + + if (!vl) + return; + + for (i = 0 ; i < vl->vm_count ; i++) { + int ret; + + if (!strcmp(DOMAIN0NAME, vl->vm_states[i].v_name)) + continue; + + ret = cpg_send_vm_state(&vl->vm_states[i]); + if (ret < 0) { + printf("Error storing VM state for %s|%s\n", + vl->vm_states[i].v_name, vl->vm_states[i].v_uuid); + } + } +} + + +static void +update_local_vms(struct cpg_info *info) +{ + uint32_t my_id = 0; + + if (!info) + return; + + cpg_get_ids(&my_id, NULL); + virt_list_update(info, &local_vm_list, my_id); + store_domains(local_vm_list); +} + + +static int +do_off(struct cpg_info *info, const char *vm_name) +{ + dbg_printf(5, "%s %s\n", __FUNCTION__, vm_name); + return vm_off(info->vp, info->vp_count, vm_name); + +} + +static int +do_on(struct cpg_info *info, const char *vm_name) +{ + dbg_printf(5, "%s %s\n", __FUNCTION__, vm_name); + return vm_on(info->vp, info->vp_count, vm_name); + +} + +static int +do_reboot(struct cpg_info *info, const char *vm_name) +{ + dbg_printf(5, "%s %s\n", __FUNCTION__, vm_name); + return vm_reboot(info->vp, info->vp_count, vm_name); +} + +static void +cpg_join_cb(const struct cpg_address *join, size_t joinlen) { + struct cpg_info *info = cpg_virt_handle; + + pthread_mutex_lock(&local_vm_list_lock); + update_local_vms(info); + pthread_mutex_unlock(&local_vm_list_lock); +} + +static void +cpg_leave_cb(const struct cpg_address *left, size_t leftlen) { + struct cpg_info *info = cpg_virt_handle; + int i; + + pthread_mutex_lock(&remote_vm_list_lock); + for (i = 0 ; i < leftlen ; i++) { + dbg_printf(2, "Removing VMs owned by nodeid %u\n", left[i].nodeid); + vl_remove_by_owner(&remote_vm_list, left[i].nodeid); + } + pthread_mutex_unlock(&remote_vm_list_lock); + + pthread_mutex_lock(&local_vm_list_lock); + update_local_vms(info); + pthread_mutex_unlock(&local_vm_list_lock); +} + +static void +store_cb(void *data, size_t len, uint32_t nodeid, uint32_t seqno) +{ + uint32_t my_id; + virt_state_t *vs = (virt_state_t *) data; + struct cpg_info *info = cpg_virt_handle; + + cpg_get_ids(&my_id, NULL); + + if (nodeid == my_id) + return; + + pthread_mutex_lock(&local_vm_list_lock); + if (!local_vm_list) + update_local_vms(info); + pthread_mutex_unlock(&local_vm_list_lock); + + pthread_mutex_lock(&remote_vm_list_lock); + vl_update(&remote_vm_list, vs); + pthread_mutex_unlock(&remote_vm_list_lock); +} + +/* +** This function must a send reply from at least one node, otherwise +** the requesting fence_virtd will block forever in wait_cpt_reply. +*/ +static void +do_real_work(void *data, size_t len, uint32_t nodeid, uint32_t seqno) +{ + struct cpg_info *info = cpg_virt_handle; + struct cpg_fence_req *req = data; + struct cpg_fence_req reply; + int reply_code = -1; + virt_state_t *vs = NULL; + int cur_state; + uint32_t cur_owner = 0; + int local = 0; + uint32_t my_id, high_id; + + dbg_printf(2, "Request %d for VM %s\n", req->request, req->vm_name); + + if (cpg_get_ids(&my_id, &high_id) == -1) { + syslog(LOG_WARNING, "Unable to get CPG IDs"); + printf("Should never happen: Can't get CPG node ids - can't proceed\n"); + return; + } + + memcpy(&reply, req, sizeof(reply)); + + pthread_mutex_lock(&local_vm_list_lock); + update_local_vms(info); + if (strlen(req->vm_name)) { + if (use_uuid) + vs = vl_find_uuid(local_vm_list, req->vm_name); + else + vs = vl_find_name(local_vm_list, req->vm_name); + + if (vs) { + local = 1; + cur_owner = vs->v_state.s_owner; + cur_state = vs->v_state.s_state; + dbg_printf(2, "Found VM %s locally state %d\n", + req->vm_name, cur_state); + } + } + pthread_mutex_unlock(&local_vm_list_lock); + + if (vs == NULL) { + pthread_mutex_lock(&remote_vm_list_lock); + if (strlen(req->vm_name)) { + if (use_uuid) + vs = vl_find_uuid(remote_vm_list, req->vm_name); + else + vs = vl_find_name(remote_vm_list, req->vm_name); + + if (vs) { + cur_owner = vs->v_state.s_owner; + cur_state = vs->v_state.s_state; + dbg_printf(2, "Found VM %s remotely on %u state %d\n", + req->vm_name, cur_owner, cur_state); + } + } + pthread_mutex_unlock(&remote_vm_list_lock); + } + + if (!vs) { + /* + ** We know about all domains on all nodes in the CPG group. + ** If we didn't find it, and we're high ID, act on the request. + ** We can safely assume the VM is OFF because it wasn't found + ** on any current members of the CPG group. + */ + if (my_id == high_id) { + if (req->request == FENCE_STATUS) + reply_code = RESP_OFF; + else if (req->request == FENCE_OFF || req->request == FENCE_REBOOT) + reply_code = RESP_SUCCESS; + else + reply_code = 1; + + dbg_printf(2, "Acting on request %d for unknown domain %s -> %d\n", + req->request, req->vm_name, reply_code); + goto out; + } + + dbg_printf(2, "Not acting on request %d for unknown domain %s\n", + req->request, req->vm_name); + return; + } + + if (local) { + if (req->request == FENCE_STATUS) { + /* We already have the status */ + if (cur_state == VIR_DOMAIN_SHUTOFF) + reply_code = RESP_OFF; + else + reply_code = RESP_SUCCESS; + } else if (req->request == FENCE_OFF) { + reply_code = do_off(info, req->vm_name); + } else if (req->request == FENCE_ON) { + reply_code = do_on(info, req->vm_name); + } else if (req->request == FENCE_REBOOT) { + reply_code = do_reboot(info, req->vm_name); + } else { + dbg_printf(2, "Not explicitly handling request type %d for %s\n", + req->request, req->vm_name); + reply_code = 0; + } + goto out; + } + + /* + ** This is a request for a non-local domain that exists on a + ** current CPG group member, so that member will see the request + ** and act on it. We don't need to do anything. + */ + dbg_printf(2, "Nothing to do for non-local domain %s seq %d owner %u\n", + req->vm_name, seqno, cur_owner); + return; + +out: + dbg_printf(2, "[%s] sending reply code seq %d -> %d\n", + req->vm_name, seqno, reply_code); + + reply.response = reply_code; + if (cpg_send_reply(&reply, sizeof(reply), nodeid, seqno) < 0) { + dbg_printf(2, "cpg_send_reply failed for %s [%d %d]: %s\n", + req->vm_name, nodeid, seqno, strerror(errno)); + } +} + + +static int +do_request(const char *vm_name, int request, uint32_t seqno) +{ + struct cpg_fence_req freq, *frp; + size_t retlen; + uint32_t seq; + int ret; + + memset(&freq, 0, sizeof(freq)); + if (!vm_name) { + dbg_printf(1, "No VM name\n"); + return 1; + } + + if (strlen(vm_name) >= sizeof(freq.vm_name)) { + dbg_printf(1, "VM name %s too long\n", vm_name); + return 1; + } + + strcpy(freq.vm_name, vm_name); + + freq.request = request; + freq.seqno = seqno; + + if (cpg_send_req(&freq, sizeof(freq), &seq) != 0) { + dbg_printf(1, "Failed to send request %d for VM %s\n", + freq.request, vm_name); + return 1; + } + + dbg_printf(2, "Sent request %d for VM %s got seqno %d\n", + request, vm_name, seq); + + if (cpg_wait_reply((void *) &frp, &retlen, seq) != 0) { + dbg_printf(1, "Failed to receive reply seq %d for %s\n", seq, vm_name); + return 1; + } + + dbg_printf(2, "Received reply [%d] seq %d for %s\n", + frp->response, seq, vm_name); + + ret = frp->response; + free(frp); + + return ret; +} + + +static int +cpg_virt_null(const char *vm_name, void *priv) +{ + VALIDATE(priv); + printf("[cpg-virt] Null operation on %s\n", vm_name); + + return 1; +} + + +static int +cpg_virt_off(const char *vm_name, const char *src, uint32_t seqno, void *priv) +{ + VALIDATE(priv); + printf("[cpg-virt] OFF operation on %s seq %d\n", vm_name, seqno); + + return do_request(vm_name, FENCE_OFF, seqno); +} + + +static int +cpg_virt_on(const char *vm_name, const char *src, uint32_t seqno, void *priv) +{ + VALIDATE(priv); + printf("[cpg-virt] ON operation on %s seq %d\n", vm_name, seqno); + + return do_request(vm_name, FENCE_ON, seqno); +} + + +static int +cpg_virt_devstatus(void *priv) +{ + printf("[cpg-virt] Device status\n"); + VALIDATE(priv); + + return 0; +} + + +static int +cpg_virt_status(const char *vm_name, void *priv) +{ + VALIDATE(priv); + printf("[cpg-virt] STATUS operation on %s\n", vm_name); + + return do_request(vm_name, FENCE_STATUS, 0); +} + + +static int +cpg_virt_reboot(const char *vm_name, const char *src, + uint32_t seqno, void *priv) +{ + VALIDATE(priv); + printf("[cpg-virt] REBOOT operation on %s seq %d\n", vm_name, seqno); + + return do_request(vm_name, FENCE_REBOOT, 0); +} + + +static int +cpg_virt_hostlist(hostlist_callback callback, void *arg, void *priv) +{ + struct cpg_info *info = (struct cpg_info *) priv; + int i; + + VALIDATE(priv); + printf("[cpg-virt] HOSTLIST operation\n"); + + pthread_mutex_lock(&local_vm_list_lock); + update_local_vms(info); + for (i = 0 ; i < local_vm_list->vm_count ; i++) { + callback(local_vm_list->vm_states[i].v_name, + local_vm_list->vm_states[i].v_uuid, + local_vm_list->vm_states[i].v_state.s_state, arg); + } + pthread_mutex_unlock(&local_vm_list_lock); + + return 1; +} + +static void +cpg_virt_init_libvirt(struct cpg_info *info) { + config_object_t *config = info->config; + int i = 0; + + if (info->vp) { + dbg_printf(2, "Lost libvirtd connection. Reinitializing.\n"); + for (i = 0 ; i < info->vp_count ; i++) + virConnectClose(info->vp[i]); + free(info->vp); + info->vp = NULL; + } + info->vp_count = 0; + + do { + virConnectPtr vp; + virConnectPtr *vpl = NULL; + char conf_attr[256]; + char value[1024]; + char *uri; + + if (i != 0) { + snprintf(conf_attr, sizeof(conf_attr), + "backends/cpg/@uri%d", i); + } else + snprintf(conf_attr, sizeof(conf_attr), "backends/cpg/@uri"); + ++i; + + if (sc_get(config, conf_attr, value, sizeof(value)) != 0) + break; + + uri = value; + vp = virConnectOpen(uri); + if (!vp) { + dbg_printf(1, "[cpg-virt:INIT] Failed to connect to URI: %s\n", uri); + continue; + } + + vpl = realloc(info->vp, sizeof(*info->vp) * (info->vp_count + 1)); + if (!vpl) { + dbg_printf(1, "[cpg-virt:INIT] Out of memory allocating URI: %s\n", + uri); + virConnectClose(vp); + continue; + } + + info->vp = vpl; + info->vp[info->vp_count++] = vp; + + if (i > 1) + dbg_printf(1, "[cpg-virt:INIT] Added URI%d %s\n", i - 1, uri); + else + dbg_printf(1, "[cpg_virt:INIT] Added URI %s\n", uri); + } while (1); +} + +static int +cpg_virt_init(backend_context_t *c, config_object_t *config) +{ + char value[1024]; + struct cpg_info *info = NULL; + int ret; + + ret = cpg_start(PACKAGE_NAME, + do_real_work, store_cb, cpg_join_cb, cpg_leave_cb); + if (ret < 0) + return -1; + + info = calloc(1, sizeof(*info)); + if (!info) + return -1; + info->magic = MAGIC; + info->config = config; + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value)) == 0) + dset(atoi(value)); + + cpg_virt_init_libvirt(info); + + /* Naming scheme is no longer a top-level config option. + * However, we retain it here for configuration compatibility with + * versions 0.1.3 and previous. + */ + if (sc_get(config, "fence_virtd/@name_mode", + value, sizeof(value)-1) == 0) { + + dbg_printf(1, "Got %s for name_mode\n", value); + if (!strcasecmp(value, "uuid")) { + use_uuid = 1; + } else if (!strcasecmp(value, "name")) { + use_uuid = 0; + } else { + dbg_printf(1, "Unsupported name_mode: %s\n", value); + } + } + + if (sc_get(config, "backends/cpg/@name_mode", + value, sizeof(value)-1) == 0) + { + dbg_printf(1, "Got %s for name_mode\n", value); + if (!strcasecmp(value, "uuid")) { + use_uuid = 1; + } else if (!strcasecmp(value, "name")) { + use_uuid = 0; + } else { + dbg_printf(1, "Unsupported name_mode: %s\n", value); + } + } + + if (info->vp_count < 1) { + dbg_printf(1, "[cpg_virt:INIT] Could not connect to any hypervisors\n"); + cpg_stop(); + free(info); + return -1; + } + + pthread_mutex_lock(&local_vm_list_lock); + update_local_vms(info); + pthread_mutex_unlock(&local_vm_list_lock); + + *c = (void *) info; + cpg_virt_handle = info; + return 0; +} + + +static int +cpg_virt_shutdown(backend_context_t c) +{ + struct cpg_info *info = (struct cpg_info *)c; + int i = 0; + int ret = 0; + + VALIDATE(info); + info->magic = 0; + + cpg_stop(); + + for (i = 0 ; i < info->vp_count ; i++) { + if (virConnectClose(info->vp[i]) < 0) + ret = -errno; + } + + free(info->vp); + free(info); + + return ret; +} + + +static fence_callbacks_t cpg_callbacks = { + .null = cpg_virt_null, + .off = cpg_virt_off, + .on = cpg_virt_on, + .reboot = cpg_virt_reboot, + .status = cpg_virt_status, + .devstatus = cpg_virt_devstatus, + .hostlist = cpg_virt_hostlist +}; + +static backend_plugin_t cpg_virt_plugin = { + .name = NAME, + .version = CPG_VERSION, + .callbacks = &cpg_callbacks, + .init = cpg_virt_init, + .cleanup = cpg_virt_shutdown, +}; + +double +BACKEND_VER_SYM(void) +{ + return PLUGIN_VERSION_BACKEND; +} + +const backend_plugin_t * +BACKEND_INFO_SYM(void) +{ + return &cpg_virt_plugin; +} diff --git a/agents/virt/server/cpg.c b/agents/virt/server/cpg.c new file mode 100644 index 0000000..8f84cd8 --- /dev/null +++ b/agents/virt/server/cpg.c @@ -0,0 +1,411 @@ +#include "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <malloc.h> +#include <signal.h> +#include <unistd.h> +#include <sys/select.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <sys/uio.h> +#include <list.h> +#include <pthread.h> + +#include <corosync/cpg.h> + +#include "debug.h" +#include "virt.h" +#include "cpg.h" + +#define NODE_ID_NONE ((uint32_t) -1) + +struct msg_queue_node { + list_head(); + uint32_t seqno; +#define STATE_CLEAR 0 +#define STATE_MESSAGE 1 + uint32_t state; + void *msg; + size_t msglen; +}; + +struct wire_msg { +#define TYPE_REQUEST 0 +#define TYPE_REPLY 1 +#define TYPE_STORE_VM 2 + uint32_t type; + uint32_t seqno; + uint32_t target; + uint32_t pad; + char data[0]; +}; + +static uint32_t seqnum = 0; +static struct msg_queue_node *pending = NULL; +static cpg_handle_t cpg_handle; +static struct cpg_name gname; + +static pthread_mutex_t cpg_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cpg_cond = PTHREAD_COND_INITIALIZER; +static pthread_t cpg_thread = 0; + +static pthread_mutex_t cpg_ids_mutex = PTHREAD_MUTEX_INITIALIZER; +static uint32_t my_node_id = NODE_ID_NONE; +static uint32_t high_id_from_callback = NODE_ID_NONE; + +static request_callback_fn req_callback_fn; +static request_callback_fn store_callback_fn; +static confchange_callback_fn conf_leave_fn; +static confchange_callback_fn conf_join_fn; + + +int +cpg_get_ids(uint32_t *my_id, uint32_t *high_id) +{ + if (!my_id && !high_id) + return -1; + + pthread_mutex_lock(&cpg_ids_mutex); + if (my_id) + *my_id = my_node_id; + + if (high_id) + *high_id = high_id_from_callback; + pthread_mutex_unlock(&cpg_ids_mutex); + + return 0; +} + +static void +cpg_deliver_func(cpg_handle_t h, + const struct cpg_name *group_name, + uint32_t nodeid, + uint32_t pid, + void *msg, + size_t msglen) +{ + struct msg_queue_node *n; + struct wire_msg *m = msg; + int x, found; + + pthread_mutex_lock(&cpg_mutex); + if (m->type == TYPE_REPLY) { + /* Reply to a request we sent */ + found = 0; + + list_for(&pending, n, x) { + if (m->seqno != n->seqno) + continue; + if (m->target != my_node_id) + continue; + found = 1; + break; + } + + if (!found) + goto out_unlock; + + /* Copy our message in to a buffer */ + n->msglen = msglen - sizeof(*m); + if (!n->msglen) { + /* XXX do what? */ + } + n->msg = malloc(n->msglen); + if (!n->msg) { + goto out_unlock; + } + n->state = STATE_MESSAGE; + memcpy(n->msg, (char *)msg + sizeof(*m), n->msglen); + + list_remove(&pending, n); + list_insert(&pending, n); + + dbg_printf(2, "Seqnum %d replied; removing from list\n", n->seqno); + + pthread_cond_broadcast(&cpg_cond); + goto out_unlock; + } + pthread_mutex_unlock(&cpg_mutex); + + if (m->type == TYPE_REQUEST) { + req_callback_fn(&m->data, msglen - sizeof(*m), + nodeid, m->seqno); + } + if (m->type == TYPE_STORE_VM) { + store_callback_fn(&m->data, msglen - sizeof(*m), + nodeid, m->seqno); + } + + return; + +out_unlock: + pthread_mutex_unlock(&cpg_mutex); +} + + +static void +cpg_config_change(cpg_handle_t h, + const struct cpg_name *group_name, + const struct cpg_address *members, size_t memberlen, + const struct cpg_address *left, size_t leftlen, + const struct cpg_address *join, size_t joinlen) +{ + int x; + int high; + + pthread_mutex_lock(&cpg_ids_mutex); + high = my_node_id; + + for (x = 0; x < memberlen; x++) { + if (members[x].nodeid > high) + high = members[x].nodeid; + } + + high_id_from_callback = high; + pthread_mutex_unlock(&cpg_ids_mutex); + + if (joinlen > 0) + conf_join_fn(join, joinlen); + + if (leftlen > 0) + conf_leave_fn(left, leftlen); +} + + +static cpg_callbacks_t my_callbacks = { + .cpg_deliver_fn = cpg_deliver_func, + .cpg_confchg_fn = cpg_config_change +}; + + +int +cpg_send_req(void *data, size_t len, uint32_t *seqno) +{ + struct iovec iov; + struct msg_queue_node *n; + struct wire_msg *m; + size_t msgsz = sizeof(*m) + len; + int ret; + + n = malloc(sizeof(*n)); + if (!n) + return -1; + + m = malloc(msgsz); + if (!m) { + free(n); + return -1; + } + + /* only incremented on send */ + n->state = STATE_CLEAR; + n->msg = NULL; + n->msglen = 0; + + pthread_mutex_lock(&cpg_mutex); + list_insert(&pending, n); + n->seqno = ++seqnum; + m->seqno = seqnum; + *seqno = seqnum; + pthread_mutex_unlock(&cpg_mutex); + + m->type = TYPE_REQUEST; /* XXX swab? */ + m->target = NODE_ID_NONE; + memcpy(&m->data, data, len); + + iov.iov_base = m; + iov.iov_len = msgsz; + ret = cpg_mcast_joined(cpg_handle, CPG_TYPE_AGREED, &iov, 1); + + free(m); + if (ret == CS_OK) + return 0; + return -1; +} + + +int +cpg_send_vm_state(virt_state_t *vs) +{ + struct iovec iov; + struct msg_queue_node *n; + struct wire_msg *m; + size_t msgsz = sizeof(*m) + sizeof(*vs); + int ret; + + n = calloc(1, (sizeof(*n))); + if (!n) + return -1; + + m = calloc(1, msgsz); + if (!m) { + free(n); + return -1; + } + + n->state = STATE_MESSAGE; + n->msg = NULL; + n->msglen = 0; + + pthread_mutex_lock(&cpg_mutex); + list_insert(&pending, n); + pthread_mutex_unlock(&cpg_mutex); + + m->type = TYPE_STORE_VM; + m->target = NODE_ID_NONE; + + memcpy(&m->data, vs, sizeof(*vs)); + + iov.iov_base = m; + iov.iov_len = msgsz; + ret = cpg_mcast_joined(cpg_handle, CPG_TYPE_AGREED, &iov, 1); + + free(m); + if (ret == CS_OK) + return 0; + + return -1; +} + + +int +cpg_send_reply(void *data, size_t len, uint32_t nodeid, uint32_t seqno) +{ + struct iovec iov; + struct wire_msg *m; + size_t msgsz = sizeof(*m) + len; + int ret; + + m = malloc(msgsz); + if (!m) + return -1; + + /* only incremented on send */ + m->seqno = seqno; + m->type = TYPE_REPLY; /* XXX swab? */ + m->target = nodeid; + memcpy(&m->data, data, len); + + iov.iov_base = m; + iov.iov_len = msgsz; + ret = cpg_mcast_joined(cpg_handle, CPG_TYPE_AGREED, &iov, 1); + + free(m); + if (ret == CS_OK) + return 0; + + return -1; +} + + +int +cpg_wait_reply(void **data, size_t *len, uint32_t seqno) +{ + struct msg_queue_node *n; + int x, found = 0; + + while (!found) { + found = 0; + pthread_mutex_lock(&cpg_mutex); + pthread_cond_wait(&cpg_cond, &cpg_mutex); + + list_for(&pending, n, x) { + if (n->seqno != seqno) + continue; + if (n->state != STATE_MESSAGE) + continue; + found = 1; + goto out; + } + pthread_mutex_unlock(&cpg_mutex); + } + +out: + list_remove(&pending, n); + pthread_mutex_unlock(&cpg_mutex); + + *data = n->msg; + *len = n->msglen; + free(n); + + return 0; +} + + +static void * +cpg_dispatch_thread(void *arg) +{ + cpg_dispatch(cpg_handle, CS_DISPATCH_BLOCKING); + + return NULL; +} + + +int +cpg_start( const char *name, + request_callback_fn req_cb_fn, + request_callback_fn store_cb_fn, + confchange_callback_fn join_fn, + confchange_callback_fn leave_fn) +{ + cpg_handle_t h; + int ret; + + errno = EINVAL; + + if (!name) + return -1; + + ret = snprintf(gname.value, sizeof(gname.value), "%s", name); + if (ret <= 0) + return -1; + + if (ret >= sizeof(gname.value)) { + errno = ENAMETOOLONG; + return -1; + } + gname.length = ret; + + memset(&h, 0, sizeof(h)); + if (cpg_initialize(&h, &my_callbacks) != CS_OK) { + perror("cpg_initialize"); + return -1; + } + + if (cpg_join(h, &gname) != CS_OK) { + perror("cpg_join"); + return -1; + } + + cpg_local_get(h, &my_node_id); + dbg_printf(2, "My CPG nodeid is %d\n", my_node_id); + + pthread_mutex_lock(&cpg_mutex); + pthread_create(&cpg_thread, NULL, cpg_dispatch_thread, NULL); + + memcpy(&cpg_handle, &h, sizeof(h)); + + req_callback_fn = req_cb_fn; + store_callback_fn = store_cb_fn; + conf_join_fn = join_fn; + conf_leave_fn = leave_fn; + + pthread_mutex_unlock(&cpg_mutex); + + return 0; +} + + +int +cpg_stop(void) +{ + pthread_cancel(cpg_thread); + pthread_join(cpg_thread, NULL); + cpg_leave(cpg_handle, &gname); + cpg_finalize(cpg_handle); + + return 0; +} diff --git a/agents/virt/server/cpg.h b/agents/virt/server/cpg.h new file mode 100644 index 0000000..6873955 --- /dev/null +++ b/agents/virt/server/cpg.h @@ -0,0 +1,29 @@ +#ifndef __FENCE_VIRTD_CPG_H +#define __FENCE_VIRTD_CPG_H + +struct cpg_fence_req { + char vm_name[128]; + int request; + uint32_t seqno; + uint32_t response; +}; + +typedef void (*request_callback_fn)(void *data, size_t len, uint32_t nodeid, + uint32_t seqno); +typedef void (*confchange_callback_fn)(const struct cpg_address *m, size_t len); + +int cpg_start( const char *name, + request_callback_fn func, + request_callback_fn store_func, + confchange_callback_fn join, + confchange_callback_fn leave); + +int cpg_get_ids(uint32_t *me, uint32_t *high); +int cpg_stop(void); +int cpg_send_req(void *data, size_t len, uint32_t *seqno); +int cpg_wait_reply(void **data, size_t *len, uint32_t seqno); +int cpg_send_reply(void *data, size_t len, uint32_t nodeid, uint32_t seqno); +int cpg_send_vm_state(virt_state_t *vs); + + +#endif diff --git a/agents/virt/server/daemon_init.c b/agents/virt/server/daemon_init.c new file mode 100644 index 0000000..29b33ad --- /dev/null +++ b/agents/virt/server/daemon_init.c @@ -0,0 +1,215 @@ +/** @file + * daemon_init function, does sanity checks and calls daemon(). + * + * Author: Jeff Moyer <jmoyer@redhat.com> + */ +/* + * TODO: Clean this up so that only one function constructs the + * pidfile /var/run/loggerd.PID, and perhaps only one function + * forms the /proc/PID/ path. + * + * Also need to add file locking for the pid file. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/mman.h> +#include <sys/errno.h> +#include <libgen.h> +#include <signal.h> +#include <syslog.h> + + +/* + * This should ultimately go in a header file. + */ +void daemon_init(const char *prog, const char *pid_file, int nofork); +void daemon_cleanup(void); +int check_process_running(const char *cmd, const char *pid_file, pid_t * pid); + +/* + * Local prototypes. + */ +static void update_pidfile(const char *filename); +static int setup_sigmask(void); +static char pid_filename[PATH_MAX]; + +static int +check_pid_valid(pid_t pid, const char *prog) +{ + FILE *fp; + DIR *dir; + char filename[PATH_MAX]; + char dirpath[PATH_MAX]; + char proc_cmdline[64]; /* yank this from kernel somewhere */ + char *s = NULL; + + memset(filename, 0, PATH_MAX); + memset(dirpath, 0, PATH_MAX); + + snprintf(dirpath, sizeof (dirpath), "/proc/%d", pid); + if ((dir = opendir(dirpath)) == NULL) { + return 0; /* Pid has gone away. */ + } + closedir(dir); + + /* + * proc-pid directory exists. Now check to see if this + * PID corresponds to the daemon we want to start. + */ + snprintf(filename, sizeof (filename), "/proc/%d/cmdline", pid); + fp = fopen(filename, "r"); + if (fp == NULL) { + perror("check_pid_valid"); + return 0; /* Who cares.... Let's boogy on. */ + } + + if (!fgets(proc_cmdline, sizeof (proc_cmdline) - 1, fp)) { + /* + * Okay, we've seen processes keep a reference to a + * /proc/PID/stat file and not let go. Then when + * you try to read /proc/PID/cmline, you get either + * \000 or -1. In either case, we can safely assume + * the process has gone away. + */ + fclose(fp); + return 0; + } + fclose(fp); + + s = &(proc_cmdline[strlen(proc_cmdline)]); + if (*s == '\n') + *s = 0; + + /* + * Check to see if this is the same executable. + */ + if (strstr(proc_cmdline, prog) == NULL) { + return 0; + } else { + return 1; + } +} + + +int +check_process_running(const char *cmd, const char *filename, pid_t * pid) +{ + pid_t oldpid; + FILE *fp = NULL; + int ret; + + *pid = -1; + + /* + * Read the pid from the file. + */ + fp = fopen(filename, "r"); + if (fp == NULL) { /* error */ + return 0; + } + + ret = fscanf(fp, "%d\n", &oldpid); + fclose(fp); + + if ((ret == EOF) || (ret != 1)) + return 0; + + if (check_pid_valid(oldpid, cmd)) { + *pid = oldpid; + return 1; + } + return 0; +} + + +static void +update_pidfile(const char *filename) +{ + FILE *fp = NULL; + + strncpy(pid_filename, filename, PATH_MAX - 1); + + fp = fopen(pid_filename, "w"); + if (fp == NULL) { + syslog(LOG_ERR, "daemon_init: Unable to create pidfile %s: %s\n", + filename, strerror(errno)); + exit(1); + } + + fprintf(fp, "%d", getpid()); + fclose(fp); +} + + +static int +setup_sigmask(void) +{ + sigset_t set; + + sigfillset(&set); + + /* + * Dont't block signals which would cause us to dump core. + */ + sigdelset(&set, SIGQUIT); + sigdelset(&set, SIGILL); + sigdelset(&set, SIGTRAP); + sigdelset(&set, SIGABRT); + sigdelset(&set, SIGFPE); + sigdelset(&set, SIGSEGV); + sigdelset(&set, SIGBUS); + + /* + * Don't block SIGTERM or SIGCHLD + */ + sigdelset(&set, SIGTERM); + sigdelset(&set, SIGINT); + sigdelset(&set, SIGQUIT); + sigdelset(&set, SIGCHLD); + + return (sigprocmask(SIG_BLOCK, &set, NULL)); +} + + +void +daemon_init(const char *prog, const char *pid_file, int nofork) +{ + pid_t pid; + + if (check_process_running(prog, pid_file, &pid) && (pid != getpid())) { + syslog(LOG_ERR, + "daemon_init: Process \"%s\" already running.\n", + prog); + exit(1); + } + + if (setup_sigmask() < 0) { + syslog(LOG_ERR, "daemon_init: Unable to set signal mask.\n"); + exit(1); + } + + if (!nofork && daemon(0, 0)) { + syslog(LOG_ERR, "daemon_init: Unable to daemonize.\n"); + exit(1); + } + + update_pidfile(pid_file); +} + + +void +daemon_cleanup(void) +{ + if (strlen(pid_filename)) + unlink(pid_filename); +} diff --git a/agents/virt/server/history.c b/agents/virt/server/history.c new file mode 100644 index 0000000..bd3a68c --- /dev/null +++ b/agents/virt/server/history.c @@ -0,0 +1,124 @@ +#include "config.h" + +#include <stdio.h> +#include <malloc.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <list.h> +#include <time.h> + +#include "history.h" + +history_info_t * +history_init(history_compare_fn func, time_t expiration, size_t element_size) +{ + history_info_t *hist; + + errno = EINVAL; + if (!func || !expiration || !element_size) + return NULL; + + hist = malloc(sizeof(*hist)); + if (!hist) + return NULL; + memset(hist, 0, sizeof(*hist)); + + hist->timeout = expiration; + hist->element_size = element_size; + hist->compare_func = func; + + return hist; +} + + +/* + * Purge our history when the entries time out. + * + * Returns 1 if a matching history node was found, or 0 + * if not. + */ +int +history_check(history_info_t *hinfo, void *stuff) +{ + history_node *entry = NULL; + time_t now; + int x; + + if (!hinfo) + return 0; /* XXX */ + + if (!hinfo->hist) + return 0; + + now = time(NULL); + +loop_again: + list_for((&hinfo->hist), entry, x) { + if (entry->when < (now - hinfo->timeout)) { + list_remove((&hinfo->hist), entry); + free(entry->data); + free(entry); + goto loop_again; + } + + if (hinfo->compare_func(entry->data, stuff)) { + return 1; + } + } + return 0; +} + + +int +history_record(history_info_t *hinfo, void *data) +{ + history_node *entry = NULL; + + errno = EINVAL; + if (!data || !hinfo) + return -1; + + if (history_check(hinfo, data) == 1) { + errno = EEXIST; + return -1; + } + + entry = malloc(sizeof(*entry)); + if (!entry) { + return -1; + } + memset(entry, 0, sizeof(*entry)); + + entry->data = malloc(hinfo->element_size); + if (!entry->data) { + free(entry); + errno = ENOMEM; + return -1; + } + + memcpy(entry->data, data, hinfo->element_size); + entry->when = time(NULL); + list_insert((&hinfo->hist), entry); + return 0; +} + + +int +history_wipe(history_info_t *hinfo) +{ + history_node *entry = NULL; + + if (!hinfo) + return -1; + + while (hinfo->hist) { + entry = hinfo->hist; + list_remove((&hinfo->hist), entry); + free(entry->data); + free(entry); + } + + /* User must free(hinfo); */ + return 0; +} diff --git a/agents/virt/server/libvirt.c b/agents/virt/server/libvirt.c new file mode 100644 index 0000000..8f01045 --- /dev/null +++ b/agents/virt/server/libvirt.c @@ -0,0 +1,359 @@ +/* + Copyright Red Hat, Inc. 2006-2017 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ +/* + * Author: Lon Hohberger <lhh at redhat.com> + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/time.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> +#include <libvirt/virterror.h> +#include <nss.h> +#include <libgen.h> +#include <syslog.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "mcast.h" +#include "tcp.h" +#include "virt.h" +#include "debug.h" +#include "uuid-test.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" + +#define NAME "libvirt" +#define LIBVIRT_VERSION "0.3" + +#define MAGIC 0x1e19317a + +struct libvirt_info { + int magic; + config_object_t *config; + int vp_count; + virConnectPtr *vp; +}; + +#define VALIDATE(arg) \ +do {\ + if (!arg || ((struct libvirt_info *)arg)->magic != MAGIC) { \ + errno = EINVAL;\ + return -1; \ + } \ +} while(0) + + +static void +libvirt_init_libvirt_conf(struct libvirt_info *info) { + config_object_t *config = info->config; + int i = 0; + + if (info->vp) { + dbg_printf(2, "Lost libvirtd connection. Reinitializing.\n"); + for (i = 0 ; i < info->vp_count ; i++) + virConnectClose(info->vp[i]); + free(info->vp); + info->vp = NULL; + } + info->vp_count = 0; + + do { + virConnectPtr vp; + virConnectPtr *vpl = NULL; + char conf_attr[256]; + char value[1024]; + char *uri; + + if (i != 0) { + snprintf(conf_attr, sizeof(conf_attr), + "backends/libvirt/@uri%d", i); + } else + snprintf(conf_attr, sizeof(conf_attr), "backends/libvirt/@uri"); + ++i; + + if (sc_get(config, conf_attr, value, sizeof(value)) != 0) + break; + + uri = value; + vp = virConnectOpen(uri); + if (!vp) { + dbg_printf(1, "[libvirt:INIT] Failed to connect to URI: %s\n", uri); + continue; + } + + vpl = realloc(info->vp, sizeof(*info->vp) * (info->vp_count + 1)); + if (!vpl) { + dbg_printf(1, "[libvirt:INIT] Out of memory allocating URI: %s\n", + uri); + virConnectClose(vp); + continue; + } + + info->vp = vpl; + info->vp[info->vp_count++] = vp; + + if (i > 1) + dbg_printf(1, "[libvirt:INIT] Added URI%d %s\n", i - 1, uri); + else + dbg_printf(1, "[libvirt:INIT] Added URI %s\n", uri); + } while (1); +} + + +static int +libvirt_bad_connections(struct libvirt_info *info) { + int bad = 0; + int i; + + for (i = 0 ; i < info->vp_count ; i++) { + /* + ** Send a dummy command to trigger an error if libvirtd + ** died or restarted + */ + virConnectNumOfDomains(info->vp[i]); + if (!virConnectIsAlive(info->vp[i])) { + dbg_printf(1, "libvirt connection %d is dead\n", i); + bad++; + } + } + + if (info->vp_count < 1 || bad) + libvirt_init_libvirt_conf(info); + + return bad || info->vp_count < 1; +} + +static void +libvirt_validate_connections(struct libvirt_info *info) { + while (1) { + if (libvirt_bad_connections(info)) + sleep(1); + else + break; + } +} + +static int +libvirt_null(const char *vm_name, void *priv) +{ + dbg_printf(5, "ENTER %s %s\n", __FUNCTION__, vm_name); + printf("NULL operation: returning failure\n"); + return 1; +} + + +static int +libvirt_off(const char *vm_name, const char *src, uint32_t seqno, void *priv) +{ + struct libvirt_info *info = (struct libvirt_info *)priv; + + dbg_printf(5, "ENTER %s %s %u\n", __FUNCTION__, vm_name, seqno); + VALIDATE(info); + + libvirt_validate_connections(info); + return vm_off(info->vp, info->vp_count, vm_name); +} + + +static int +libvirt_on(const char *vm_name, const char *src, uint32_t seqno, void *priv) +{ + struct libvirt_info *info = (struct libvirt_info *)priv; + + dbg_printf(5, "ENTER %s %s %u\n", __FUNCTION__, vm_name, seqno); + VALIDATE(info); + + libvirt_validate_connections(info); + return vm_on(info->vp, info->vp_count, vm_name); +} + + +static int +libvirt_devstatus(void *priv) +{ + dbg_printf(5, "%s ---\n", __FUNCTION__); + + if (priv) + return 0; + return 1; +} + + +static int +libvirt_status(const char *vm_name, void *priv) +{ + struct libvirt_info *info = (struct libvirt_info *)priv; + + dbg_printf(5, "ENTER %s %s\n", __FUNCTION__, vm_name); + VALIDATE(info); + + libvirt_validate_connections(info); + return vm_status(info->vp, info->vp_count, vm_name); +} + + +static int +libvirt_reboot(const char *vm_name, const char *src, uint32_t seqno, void *priv) +{ + struct libvirt_info *info = (struct libvirt_info *)priv; + + dbg_printf(5, "ENTER %s %s %u\n", __FUNCTION__, vm_name, seqno); + VALIDATE(info); + + libvirt_validate_connections(info); + return vm_reboot(info->vp, info->vp_count, vm_name); +} + + +static int +libvirt_hostlist(hostlist_callback callback, void *arg, void *priv) +{ + struct libvirt_info *info = (struct libvirt_info *)priv; + virt_list_t *vl; + int x; + + dbg_printf(5, "ENTER %s\n", __FUNCTION__); + VALIDATE(info); + + libvirt_validate_connections(info); + + vl = vl_get(info->vp, info->vp_count, 1); + if (!vl) + return 0; + + for (x = 0; x < vl->vm_count; x++) { + callback(vl->vm_states[x].v_name, + vl->vm_states[x].v_uuid, + vl->vm_states[x].v_state.s_state, arg); + + dbg_printf(10, "[libvirt:HOSTLIST] Sent %s %s %d\n", + vl->vm_states[x].v_name, + vl->vm_states[x].v_uuid, + vl->vm_states[x].v_state.s_state); + } + + vl_free(vl); + return 0; +} + + +static int +libvirt_init(backend_context_t *c, config_object_t *config) +{ + char value[256]; + struct libvirt_info *info = NULL; + + dbg_printf(5, "ENTER [%s:%d %s]\n", __FILE__, __LINE__, __FUNCTION__); + + info = calloc(1, sizeof(*info)); + if (!info) + return -1; + info->magic = MAGIC; + info->config = config; + + libvirt_init_libvirt_conf(info); + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value)) == 0) + dset(atoi(value)); + + if (info->vp_count < 1) { + dbg_printf(1, "[libvirt:INIT] Could not connect to any hypervisors\n"); + if (info->vp) + free(info->vp); + free(info); + return -1; + } + + *c = (void *) info; + return 0; +} + + +static int +libvirt_shutdown(backend_context_t c) +{ + struct libvirt_info *info = (struct libvirt_info *)c; + int i; + int ret = 0; + + VALIDATE(info); + + for (i = 0 ; i < info->vp_count ; i++) { + if (virConnectClose(info->vp[i]) < 0) + ret = -errno; + } + + free(info->vp); + free(info); + return ret; +} + + +static fence_callbacks_t libvirt_callbacks = { + .null = libvirt_null, + .off = libvirt_off, + .on = libvirt_on, + .reboot = libvirt_reboot, + .status = libvirt_status, + .devstatus = libvirt_devstatus, + .hostlist = libvirt_hostlist +}; + +static backend_plugin_t libvirt_plugin = { + .name = NAME, + .version = LIBVIRT_VERSION, + .callbacks = &libvirt_callbacks, + .init = libvirt_init, + .cleanup = libvirt_shutdown, +}; + +double +BACKEND_VER_SYM(void) +{ + return PLUGIN_VERSION_BACKEND; +} + +const backend_plugin_t * +BACKEND_INFO_SYM(void) +{ + return &libvirt_plugin; +} diff --git a/agents/virt/server/main.c b/agents/virt/server/main.c new file mode 100644 index 0000000..fe57899 --- /dev/null +++ b/agents/virt/server/main.c @@ -0,0 +1,281 @@ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/param.h> +#include <libgen.h> +#include <stdint.h> +#include <syslog.h> + +/* Local includes */ +#include "simpleconfig.h" +#include "static_map.h" +#include "xvm.h" +#include "server_plugin.h" +#include "simple_auth.h" +#include "debug.h" + +/* configure.c */ +int daemon_init(const char *prog, const char *pid_file, int nofork); +int daemon_cleanup(void); + + +static void +usage(void) +{ + printf("Usage: fence_virtd [options]\n"); + printf(" -F Do not daemonize.\n"); + printf(" -f <file> Use <file> as configuration file.\n"); + printf(" -d <level> Set debugging level to <level>.\n"); + printf(" -c Configuration mode.\n"); + printf(" -l List plugins.\n"); + printf(" -w Wait for initialization.\n"); + printf(" -p <file> Use <file> to record the active process id.\n"); +} + + +static int run = 1; +static void +exit_handler(int sig) +{ + run = 0; +} + + +int +main(int argc, char **argv) +{ + char val[4096]; + char listener_name[80]; + char backend_name[80]; + const char *config_file = SYSCONFDIR "/fence_virt.conf"; + char *pid_file = NULL; + config_object_t *config = NULL; + map_object_t *map = NULL; + const listener_plugin_t *lp; + const backend_plugin_t *p; + listener_context_t listener_ctx = NULL; + backend_context_t backend_ctx = NULL; + int debug_set = 0, foreground = 0, wait_for_init = 0; + int opt, configure = 0; + + config = sc_init(); + map = map_init(); + + if (!config || !map) { + perror("malloc"); + return -1; + } + + while ((opt = getopt(argc, argv, "Ff:d:cwlhp:")) != EOF) { + switch(opt) { + case 'F': + printf("Background mode disabled\n"); + foreground = 1; + break; + case 'f': + printf("Using %s\n", optarg); + config_file = optarg; + break; + case 'p': + printf("Using %s\n", optarg); + pid_file = optarg; + break; + case 'd': + debug_set = atoi(optarg); + break; + case 'c': + configure = 1; + break; + case 'w': + wait_for_init = 1; + break; + case 'l': + plugin_dump(); + return 0; + case 'h': + case '?': + usage(); + return 0; + default: + return -1; + } + } + + if (configure) { + return do_configure(config, config_file); + } + + if (sc_parse(config, config_file) != 0) { + printf("Failed to parse %s\n", config_file); + return -1; + } + + if (debug_set) { + snprintf(val, sizeof(val), "%d", debug_set); + sc_set(config, "fence_virtd/@debug", val); + } else { + if (sc_get(config, "fence_virtd/@debug", val, sizeof(val))==0) + debug_set = atoi(val); + } + + dset(debug_set); + + if (!foreground) { + if (sc_get(config, "fence_virtd/@foreground", + val, sizeof(val)) == 0) + foreground = atoi(val); + } + + if (!wait_for_init) { + if (sc_get(config, "fence_virtd/@wait_for_init", + val, sizeof(val)) == 0) + wait_for_init = atoi(val); + if (!wait_for_init) { + /* XXX compat */ + if (sc_get(config, "fence_virtd/@wait_for_backend", + val, sizeof(val)) == 0) + wait_for_init = atoi(val); + } + } + + if (dget() > 3) + sc_dump(config, stdout); + + if (sc_get(config, "fence_virtd/@backend", backend_name, + sizeof(backend_name))) { + printf("Failed to determine backend.\n"); + printf("%s\n", val); + return -1; + } + + dbg_printf(1, "Backend plugin: %s\n", backend_name); + + if (sc_get(config, "fence_virtd/@listener", listener_name, + sizeof(listener_name))) { + printf("Failed to determine backend.\n"); + printf("%s\n", val); + return -1; + } + + dbg_printf(1, "Listener plugin: %s\n", listener_name); + + if (sc_get(config, "fence_virtd/@module_path", val, + sizeof(val))) { +#ifdef MODULE_PATH + snprintf(val, sizeof(val), MODULE_PATH); +#else + printf("Failed to determine module path.\n"); + return -1; +#endif + } + + dbg_printf(1, "Searching %s for plugins...\n", val); + + opt = plugin_search(val); + if (opt > 0) { + dbg_printf(1, "%d plugins found\n", opt); + } else { + printf("No plugins found\n"); + return 1; + } + + if (dget() > 3) + plugin_dump(); + + lp = plugin_find_listener(listener_name); + if (!lp) { + printf("Could not find listener \"%s\"\n", listener_name); + return 1; + } + + p = plugin_find_backend(backend_name); + if (!p) { + printf("Could not find backend \"%s\"\n", backend_name); + return 1; + } + + if (pid_file == NULL) { + pid_file = malloc(PATH_MAX); + memset(pid_file, 0, PATH_MAX); + snprintf(pid_file, PATH_MAX, "/var/run/%s.pid", basename(argv[0])); + } + + if (check_file_permissions(config_file) != 0) + return -1; + + sprintf(val, "listeners/%s/@key_file", listener_name); + if (sc_get(config, val, + val, sizeof(val)-1) == 0) { + dbg_printf(1, "Got %s for key_file\n", val); + } else { + snprintf(val, sizeof(val), "%s", DEFAULT_KEY_FILE); + } + + if (check_file_permissions(val) != 0) + return -1; + + openlog(basename(argv[0]), LOG_NDELAY | LOG_PID, LOG_DAEMON); + + daemon_init(basename(argv[0]), pid_file, foreground); + + signal(SIGINT, exit_handler); + signal(SIGTERM, exit_handler); + signal(SIGQUIT, exit_handler); + + syslog(LOG_NOTICE, "fence_virtd starting. Listener: %s Backend: %s", + listener_name, backend_name); + + while (p->init(&backend_ctx, config) < 0) { + if (!wait_for_init || !run) { + if (foreground) { + printf("Backend plugin %s failed to initialize\n", + backend_name); + } + syslog(LOG_ERR, + "Backend plugin %s failed to initialize\n", + backend_name); + return 1; + } + sleep(5); + } + + if (map_load(map, config) < 0) { + syslog(LOG_WARNING, "Failed to load static maps\n"); + } + + /* only client we have now is mcast (fence_xvm behavior) */ + while (lp->init(&listener_ctx, p->callbacks, config, map, + backend_ctx) != 0) { + if (!wait_for_init || !run) { + if (foreground) { + printf("Listener plugin %s failed to initialize\n", + listener_name); + } + syslog(LOG_ERR, + "Listener plugin %s failed to initialize\n", + listener_name); + return 1; + } + sleep(5); + } + + while (run && lp->dispatch(listener_ctx, NULL) >= 0); + + syslog(LOG_NOTICE, "fence_virtd shutting down"); + + map_release(map); + sc_release(config); + + lp->cleanup(listener_ctx); + p->cleanup(backend_ctx); + + plugin_unload(); + daemon_cleanup(); + + return 0; +} diff --git a/agents/virt/server/mcast.c b/agents/virt/server/mcast.c new file mode 100644 index 0000000..a95ab37 --- /dev/null +++ b/agents/virt/server/mcast.c @@ -0,0 +1,622 @@ +/* + Copyright Red Hat, Inc. 2006 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ +/* + * Author: Lon Hohberger <lhh at redhat.com> + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/time.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> +#include <nss.h> +#include <libgen.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "mcast.h" +#include "tcp.h" +#include "debug.h" +#include "fdops.h" +#include "list.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" +#include "history.h" + +#define NAME "multicast" +#define MCAST_VERSION "1.3" + +#define MCAST_MAGIC 0xabb911a3 + +#define VALIDATE(info) \ +do {\ + if (!info || info->magic != MCAST_MAGIC)\ + return -EINVAL;\ +} while(0) + +typedef struct _mcast_options { + char *addr; + char *key_file; + int ifindex; + int family; + unsigned int port; + unsigned int hash; + unsigned int auth; + unsigned int flags; +} mcast_options; + + +typedef struct _mcast_info { + uint64_t magic; + void *priv; + map_object_t *map; + history_info_t *history; + char key[MAX_KEY_LEN]; + mcast_options args; + const fence_callbacks_t *cb; + ssize_t key_len; + int mc_sock; + int need_kill; +} mcast_info; + + +struct mcast_hostlist_arg { + map_object_t *map; + const char *src; + int fd; +}; + + +/* + * See if we fenced this node recently (successfully) + * If so, ignore the request for a few seconds. + * + * We purge our history when the entries time out. + */ +static int +check_history(void *a, void *b) { + fence_req_t *old = a, *current = b; + + if (old->request == current->request && + old->seqno == current->seqno && + !strcasecmp((const char *)old->domain, + (const char *)current->domain)) { + return 1; + } + return 0; +} + + +static int +connect_tcp(fence_req_t *req, fence_auth_type_t auth, + void *key, size_t key_len) +{ + int fd = -1; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + char buf[128]; + + switch(req->family) { + case PF_INET: + memset(&sin, 0, sizeof(sin)); + memcpy(&sin.sin_addr, req->address, + sizeof(sin.sin_addr)); + sin.sin_family = PF_INET; + fd = ipv4_connect(&sin.sin_addr, req->port, + 5); + if (fd < 0) { + printf("Failed to call back\n"); + return -1; + } + break; + case PF_INET6: + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6.sin6_addr, req->address, + sizeof(sin6.sin6_addr)); + sin.sin_family = PF_INET6; + fd = ipv6_connect(&sin6.sin6_addr, req->port, + 5); + + memset(buf,0,sizeof(buf)); + inet_ntop(PF_INET6, &sin6.sin6_addr, buf, sizeof(buf)); + + if (fd < 0) { + printf("Failed to call back %s\n", buf); + return -1; + } + break; + default: + printf("Family = %d\n", req->family); + return -1; + } + + /* Noops if auth == AUTH_NONE */ + if (sock_response(fd, auth, key, key_len, 10) <= 0) { + printf("Failed to respond to challenge\n"); + close(fd); + return -1; + } + + if (sock_challenge(fd, auth, key, key_len, 10) <= 0) { + printf("Remote failed challenge\n"); + close(fd); + return -1; + } + return fd; +} + + +static int +mcast_hostlist(const char *vm_name, const char *vm_uuid, + int state, void *priv) +{ + struct mcast_hostlist_arg *arg = (struct mcast_hostlist_arg *)priv; + host_state_t hinfo; + struct timeval tv; + int ret; + + if (map_check2(arg->map, arg->src, vm_uuid, vm_name) == 0) { + /* if we don't have access to fence this VM, + * we should not see it in a hostlist either */ + return 0; + } + + strncpy((char *)hinfo.domain, vm_name, sizeof(hinfo.domain) - 1); + strncpy((char *)hinfo.uuid, vm_uuid, sizeof(hinfo.uuid) - 1); + hinfo.state = state; + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(arg->fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +mcast_hostlist_begin(int fd) +{ + struct timeval tv; + char val = (char)RESP_HOSTLIST; + + tv.tv_sec = 1; + tv.tv_usec = 0; + return _write_retry(fd, &val, 1, &tv); +} + + +static int +mcast_hostlist_end(int fd) +{ + host_state_t hinfo; + struct timeval tv; + int ret; + + printf("Sending terminator packet\n"); + + memset(&hinfo, 0, sizeof(hinfo)); + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +do_fence_request_tcp(fence_req_t *req, mcast_info *info) +{ + char ip_addr_src[1024]; + int fd = -1; + char response = 1; + struct mcast_hostlist_arg arg; + + fd = connect_tcp(req, info->args.auth, info->key, info->key_len); + if (fd < 0) { + dbg_printf(2, "Could not send reply to fence request: %s\n", + strerror(errno)); + goto out; + } + + inet_ntop(req->family, req->address, + ip_addr_src, sizeof(ip_addr_src)); + + dbg_printf(2, "Request %d seqno %d src %s target %s\n", + req->request, req->seqno, ip_addr_src, req->domain); + + switch(req->request) { + case FENCE_NULL: + response = info->cb->null((char *)req->domain, info->priv); + break; + case FENCE_ON: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->on((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_OFF: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->off((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_REBOOT: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->reboot((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_STATUS: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->status((char *)req->domain, info->priv); + break; + case FENCE_DEVSTATUS: + response = info->cb->devstatus(info->priv); + break; + case FENCE_HOSTLIST: + arg.map = info->map; + arg.src = ip_addr_src; + arg.fd = fd; + + mcast_hostlist_begin(arg.fd); + response = info->cb->hostlist(mcast_hostlist, &arg, + info->priv); + mcast_hostlist_end(arg.fd); + break; + } + + dbg_printf(3, "Sending response to caller...\n"); + if (_write_retry(fd, &response, 1, NULL) < 0) { + perror("write"); + } + + /* XVM shotguns multicast packets, so we want to avoid + * acting on the same request multiple times if the first + * attempt was successful. + */ + history_record(info->history, req); +out: + if (fd != -1) + close(fd); + + return 1; +} + + +static int +mcast_dispatch(listener_context_t c, struct timeval *timeout) +{ + mcast_info *info; + fence_req_t data; + fd_set rfds; + struct sockaddr_in sin; + int len; + int n; + socklen_t slen; + + info = (mcast_info *)c; + VALIDATE(info); + + FD_ZERO(&rfds); + FD_SET(info->mc_sock, &rfds); + + n = select((info->mc_sock)+1, &rfds, NULL, NULL, timeout); + if (n <= 0) { + if (errno == EINTR || errno == EAGAIN) + n = 0; + else + dbg_printf(2, "select: %s\n", strerror(errno)); + return n; + } + + slen = sizeof(sin); + len = recvfrom(info->mc_sock, &data, sizeof(data), 0, + (struct sockaddr *)&sin, &slen); + + if (len <= 0) { + perror("recvfrom"); + return len; + } + + swab_fence_req_t(&data); + + if (!verify_request(&data, info->args.hash, info->key, + info->key_len)) { + printf("Key mismatch; dropping packet\n"); + return 0; + } + + printf("Request %d seqno %d domain %s\n", data.request, data.seqno, + data.domain); + + if (history_check(info->history, &data) == 1) { + printf("We just did this request; dropping packet\n"); + return 0; + } + + switch(info->args.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + printf("Plain TCP request\n"); + do_fence_request_tcp(&data, info); + break; + default: + printf("XXX Unhandled authentication\n"); + } + + return 0; +} + + +static int +mcast_config(config_object_t *config, mcast_options *args) +{ + char value[1024]; + int errors = 0; + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value))==0) + dset(atoi(value)); + + if (sc_get(config, "listeners/multicast/@key_file", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for key_file\n", value); + args->key_file = strdup(value); + } else { + args->key_file = strdup(DEFAULT_KEY_FILE); + if (!args->key_file) { + dbg_printf(1, "Failed to allocate memory\n"); + return -1; + } + } + + args->hash = DEFAULT_HASH; + if (sc_get(config, "listeners/multicast/@hash", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for hash\n", value); + if (!strcasecmp(value, "none")) { + args->hash = HASH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->hash = HASH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->hash = HASH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->hash = HASH_SHA512; + } else { + dbg_printf(1, "Unsupported hash: %s\n", value); + ++errors; + } + } + + args->auth = DEFAULT_AUTH; + if (sc_get(config, "listeners/multicast/@auth", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for auth\n", value); + if (!strcasecmp(value, "none")) { + args->auth = AUTH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->auth = AUTH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->auth = AUTH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->auth = AUTH_SHA512; + } else { + dbg_printf(1, "Unsupported auth: %s\n", value); + ++errors; + } + } + + args->family = PF_INET; + if (sc_get(config, "listeners/multicast/@family", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for family\n", value); + if (!strcasecmp(value, "ipv4")) { + args->family = PF_INET; + } else if (!strcasecmp(value, "ipv6")) { + args->family = PF_INET6; + } else { + dbg_printf(1, "Unsupported family: %s\n", value); + ++errors; + } + } + + if (sc_get(config, "listeners/multicast/@address", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for address\n", value); + args->addr = strdup(value); + } else { + if (args->family == PF_INET) { + args->addr = strdup(IPV4_MCAST_DEFAULT); + } else { + args->addr = strdup(IPV6_MCAST_DEFAULT); + } + } + if (!args->addr) { + return -1; + } + + args->port = DEFAULT_MCAST_PORT; + if (sc_get(config, "listeners/multicast/@port", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for port\n", value); + args->port = atoi(value); + if (args->port <= 0) { + dbg_printf(1, "Invalid port: %s\n", value); + ++errors; + } + } + + args->ifindex = 0; + if (sc_get(config, "listeners/multicast/@interface", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for interface\n", value); + args->ifindex = if_nametoindex(value); + if (args->ifindex < 0) { + dbg_printf(1, "Invalid interface: %s\n", value); + ++errors; + } + } + + return errors; +} + + +static int +mcast_init(listener_context_t *c, const fence_callbacks_t *cb, + config_object_t *config, map_object_t *map, void *priv) +{ + mcast_info *info; + int mc_sock, ret; + + /* Initialize NSS; required to do hashing, as silly as that + sounds... */ + if (NSS_NoDB_Init(NULL) != SECSuccess) { + printf("Could not initialize NSS\n"); + return 1; + } + + info = malloc(sizeof(*info)); + if (!info) + return -1; + memset(info, 0, sizeof(*info)); + + info->priv = priv; + info->cb = cb; + info->map = map; + + ret = mcast_config(config, &info->args); + if (ret < 0) { + perror("mcast_config"); + free(info); + return -1; + } else if (ret > 0) { + printf("%d errors found during configuration\n",ret); + free(info); + return -1; + } + + if (info->args.auth != AUTH_NONE || info->args.hash != HASH_NONE) { + info->key_len = read_key_file(info->args.key_file, + info->key, sizeof(info->key)); + if (info->key_len < 0) { + printf("Could not read %s; operating without " + "authentication\n", info->args.key_file); + info->args.auth = AUTH_NONE; + info->args.hash = HASH_NONE; + info->key_len = 0; + } + } + + if (info->args.family == PF_INET) + mc_sock = ipv4_recv_sk(info->args.addr, + info->args.port, + info->args.ifindex); + else + mc_sock = ipv6_recv_sk(info->args.addr, + info->args.port, + info->args.ifindex); + if (mc_sock < 0) { + printf("Could not set up multicast listen socket\n"); + free(info); + return -1; + } + + info->magic = MCAST_MAGIC; + info->mc_sock = mc_sock; + info->history = history_init(check_history, 10, sizeof(fence_req_t)); + *c = (listener_context_t)info; + return 0; +} + + +static int +mcast_shutdown(listener_context_t c) +{ + mcast_info *info = (mcast_info *)c; + + VALIDATE(info); + info->magic = 0; + history_wipe(info->history); + free(info->history); + free(info->args.key_file); + free(info->args.addr); + close(info->mc_sock); + free(info); + + return 0; +} + + +static listener_plugin_t mcast_plugin = { + .name = NAME, + .version = MCAST_VERSION, + .init = mcast_init, + .dispatch = mcast_dispatch, + .cleanup = mcast_shutdown, +}; + +double +LISTENER_VER_SYM(void) +{ + return PLUGIN_VERSION_LISTENER; +} + +const listener_plugin_t * +LISTENER_INFO_SYM(void) +{ + return &mcast_plugin; +} diff --git a/agents/virt/server/plugin.c b/agents/virt/server/plugin.c new file mode 100644 index 0000000..d9a69fb --- /dev/null +++ b/agents/virt/server/plugin.c @@ -0,0 +1,417 @@ +/* + Copyright Red Hat, Inc. 2002-2004, 2009 + + The Magma Cluster API Library is free software; you can redistribute + it and/or modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either version + 2.1 of the License, or (at your option) any later version. + + The Magma Cluster API Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. + */ +/** @file + * Plugin loading routines + */ + +#include "config.h" + +#include <dlfcn.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdint.h> +#include <malloc.h> +#include <string.h> +#include <dirent.h> + +#include "list.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" +#include "debug.h" + +typedef struct _plugin_list { + list_head(); + const listener_plugin_t *listener; + const backend_plugin_t *backend; + void *handle; + plugin_type_t type; +} plugin_list_t; + +static plugin_list_t *server_plugins = NULL; + + +static int +plugin_reg_backend(void *handle, const backend_plugin_t *plugin) +{ + plugin_list_t *newplug; + + if (plugin_find_backend(plugin->name)) { + errno = EEXIST; + return -1; + } + + newplug = malloc(sizeof(*newplug)); + if (!newplug) + return -1; + memset(newplug, 0, sizeof(*newplug)); + newplug->backend = plugin; + newplug->type = PLUGIN_BACKEND; + newplug->handle = handle; + + list_insert(&server_plugins, newplug); + return 0; +} + + +static int +plugin_reg_listener(void *handle, const listener_plugin_t *plugin) +{ + plugin_list_t *newplug; + + if (plugin_find_listener(plugin->name)) { + errno = EEXIST; + return -1; + } + + newplug = malloc(sizeof(*newplug)); + if (!newplug) + return -1; + memset(newplug, 0, sizeof(*newplug)); + newplug->listener = plugin; + newplug->type = PLUGIN_LISTENER; + newplug->handle = handle; + + list_insert(&server_plugins, newplug); + return 0; +} + + +void +plugin_dump(void) +{ + plugin_list_t *p; + int x, y; + + y = 0; + list_for(&server_plugins, p, x) { + if (p->type == PLUGIN_BACKEND) { + if (!y) { + y = 1; + printf("Available backends:\n"); + } + printf(" %s %s\n", + p->backend->name, p->backend->version); + } + } + + y = 0; + list_for(&server_plugins, p, x) { + if (p->type == PLUGIN_LISTENER) { + if (!y) { + y = 1; + printf("Available listeners:\n"); + } + printf(" %s %s\n", + p->listener->name, p->listener->version); + } + } +} + + +const backend_plugin_t * +plugin_find_backend(const char *name) +{ + plugin_list_t *p; + int x; + + list_for(&server_plugins, p, x) { + if (p->type != PLUGIN_BACKEND) + continue; + if (!strcasecmp(name, p->backend->name)) + return p->backend; + } + + return NULL; +} + + +const listener_plugin_t * +plugin_find_listener(const char *name) +{ + plugin_list_t *p; + int x; + + list_for(&server_plugins, p, x) { + if (p->type != PLUGIN_LISTENER) + continue; + if (!strcasecmp(name, p->listener->name)) + return p->listener; + } + + return NULL; +} + + +static int +backend_plugin_load(void *handle, const char *libpath) +{ + const backend_plugin_t *plug = NULL; + double (*modversion)(void); + backend_plugin_t *(*modinfo)(void); + + modversion = dlsym(handle, BACKEND_VER_STR); + if (!modversion) { + dbg_printf(1, "Failed to map %s\n", BACKEND_VER_STR); + errno = EINVAL; + return -1; + } + + if (modversion() != PLUGIN_VERSION_BACKEND) { + dbg_printf(1, "API version mismatch in %s: \n" + " %f expected; %f received.\n", libpath, + PLUGIN_VERSION_BACKEND, modversion()); + errno = EINVAL; + return -1; + } + + modinfo = dlsym(handle, BACKEND_INFO_STR); + if (!modinfo) { + dbg_printf(1, "Failed to map %s\n", BACKEND_INFO_STR); + errno = EINVAL; + return -1; + } + + plug = modinfo(); + if (plugin_reg_backend(handle, plug) < 0) { + dbg_printf(1, "Failed to register %s %s\n", plug->name, + plug->version); + errno = EINVAL; + return -1; + } else { + dbg_printf(1, "Registered backend plugin %s %s\n", + plug->name, plug->version); + } + + return 0; +} + + +static int +listener_plugin_load(void *handle, const char *libpath) +{ + const listener_plugin_t *plug = NULL; + double (*modversion)(void); + listener_plugin_t *(*modinfo)(void); + + modversion = dlsym(handle, LISTENER_VER_STR); + if (!modversion) { + dbg_printf(1, "Failed to map %s\n", LISTENER_VER_STR); + errno = EINVAL; + return -1; + } + + if (modversion() != PLUGIN_VERSION_LISTENER) { + dbg_printf(1, "API version mismatch in %s: \n" + " %f expected; %f received.\n", libpath, + PLUGIN_VERSION_LISTENER, modversion()); + dlclose(handle); + errno = EINVAL; + return -1; + } + + modinfo = dlsym(handle, LISTENER_INFO_STR); + if (!modinfo) { + dbg_printf(1, "Failed to map %s\n", LISTENER_INFO_STR); + errno = EINVAL; + return -1; + } + + plug = modinfo(); + if (plugin_reg_listener(handle, plug) < 0) { + dbg_printf(1, "Failed to register %s %s\n", plug->name, + plug->version); + errno = EINVAL; + return -1; + } else { + dbg_printf(1, "Registered listener plugin %s %s\n", + plug->name, plug->version); + } + + return 0; +} + + +/** + * Load a cluster plugin .so file and map all the functions + * provided to entries in a backend_plugin_t structure. + * + * @param libpath Path to file. + * @return NULL on failure, or plugin-specific + * (const) backend_plugin_t * structure on + * success. + */ +int +plugin_load(const char *libpath) +{ + void *handle = NULL; + + errno = 0; + + if (!libpath) { + errno = EINVAL; + return -1; + } + + dbg_printf(3, "Loading plugin from %s\n", libpath); + handle = dlopen(libpath, RTLD_NOW); + if (!handle) { + dbg_printf(3, "Could not dlopen %s: %s\n", libpath, dlerror()); + errno = ELIBACC; + return -1; + } + + if (!backend_plugin_load(handle, libpath) || + !listener_plugin_load(handle, libpath)) + return 0; + + dbg_printf(3, "%s is not a valid plugin\n", libpath); + dlclose(handle); + errno = EINVAL; + return -1; +} + +void +plugin_unload(void) +{ + plugin_list_t *p; + int x; + + list_for(&server_plugins, p, x) { + dlclose(p->handle); + } +} + +/** + Free up a null-terminated array of strings + */ +static void +free_dirnames(char **dirnames) +{ + int x = 0; + + for (; dirnames[x]; x++) + free(dirnames[x]); + + free(dirnames); +} + + +static int +_compare(const void *a, const void *b) +{ + return strcmp((const char *)a, (const char *)b); +} + + +/** + Read all entries in a directory and return them in a NULL-terminated, + sorted array. + */ +static int +read_dirnames_sorted(const char *directory, char ***dirnames) +{ + DIR *dir; + struct dirent *entry; + char filename[1024]; + int count = 0, x = 0; + + dir = opendir(directory); + if (!dir) + return -1; + + /* Count the number of plugins */ + while ((entry = readdir(dir)) != NULL) + ++count; + + /* Malloc the entries */ + *dirnames = malloc(sizeof(char *) * (count+1)); + if (!*dirnames) { +#ifdef DEBUG + printf("%s: Failed to malloc %d bytes", + __FUNCTION__, (int)(sizeof(char *) * (count+1))); +#endif + closedir(dir); + errno = ENOMEM; + return -1; + } + memset(*dirnames, 0, sizeof(char *) * (count + 1)); + rewinddir(dir); + + /* Store the directory names. */ + while ((entry = readdir(dir)) != NULL) { + snprintf(filename, sizeof(filename), "%s/%s", directory, + entry->d_name); + + (*dirnames)[x] = strdup(filename); + if (!(*dirnames)[x]) { +#ifdef DEBUG + printf("Failed to duplicate %s\n", + filename); +#endif + free_dirnames(*dirnames); + closedir(dir); + errno = ENOMEM; + return -1; + } + ++x; + } + + closedir(dir); + + /* Sort the directory names. */ + qsort((*dirnames), count, sizeof(char *), _compare); + + return 0; +} + + +/** + */ +int +plugin_search(const char *pathname) +{ + int found = 0; + int fcount = 0; + char **filenames; + + dbg_printf(1, "Searching for plugins in %s\n", pathname); + if (read_dirnames_sorted(pathname, &filenames) != 0) { + return -1; + } + + for (fcount = 0; filenames[fcount]; fcount++) { + + if (plugin_load(filenames[fcount]) == 0) + ++found; + } + + free_dirnames(filenames); + if (!found) { + dbg_printf(1, "No usable plugins found.\n"); + errno = ELIBACC; + return -1; + } + + return found; +} diff --git a/agents/virt/server/serial.c b/agents/virt/server/serial.c new file mode 100644 index 0000000..bde8218 --- /dev/null +++ b/agents/virt/server/serial.c @@ -0,0 +1,459 @@ +/* + Copyright Red Hat, Inc. 2010 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ +/* + * Author: Lon Hohberger <lhh at redhat.com> + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/time.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> +#include <nss.h> +#include <libgen.h> + +/* Local includes */ +#include "debug.h" +#include "fdops.h" +#include "serial.h" +#include "list.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" +#include "history.h" +#include "xvm.h" + +#define NAME "serial" +#define SERIAL_VERSION "0.5" + +#define SERIAL_PLUG_MAGIC 0x1227a000 + +#define VALIDATE(info) \ +do {\ + if (!info || info->magic != SERIAL_PLUG_MAGIC)\ + return -EINVAL;\ +} while(0) + + +typedef struct _serial_info { + uint64_t magic; + const fence_callbacks_t *cb; + void *priv; + char *uri; + char *path; + history_info_t *history; + map_object_t *maps; + int mode; + int wake_fd; +} serial_info; + + +struct serial_hostlist_arg { + map_object_t *map; + const char *src; + int fd; +}; + + +/* + * See if we fenced this node recently (successfully) + * If so, ignore the request for a few seconds. + * + * We purge our history when the entries time out. + */ +static int +check_history(void *a, void *b) { + serial_req_t *old = a, *current = b; + + if (old->request == current->request && + old->seqno == current->seqno && + !strcasecmp((const char *)old->domain, + (const char *)current->domain)) { + return 1; + } + return 0; +} + + +static int +serial_hostlist(const char *vm_name, const char *vm_uuid, + int state, void *priv) +{ + struct serial_hostlist_arg *arg = (struct serial_hostlist_arg *)priv; + host_state_t hinfo; + struct timeval tv; + int ret; + + if (map_check2(arg->map, arg->src, vm_uuid, vm_name) == 0) { + /* if we don't have access to fence this VM, + * we should not see it in a hostlist either */ + return 0; + } + + strncpy((char *)hinfo.domain, vm_name, sizeof(hinfo.domain) - 1); + strncpy((char *)hinfo.uuid, vm_uuid, sizeof(hinfo.uuid) - 1); + hinfo.state = state; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + ret = _write_retry(arg->fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +serial_hostlist_begin(int fd) +{ + struct timeval tv; + serial_resp_t resp; + + resp.magic = SERIAL_MAGIC; + resp.response = RESP_HOSTLIST; + + tv.tv_sec = 1; + tv.tv_usec = 0; + return _write_retry(fd, &resp, sizeof(resp), &tv); +} + + +static int +serial_hostlist_end(int fd) +{ + host_state_t hinfo; + struct timeval tv; + int ret; + + //printf("Sending terminator packet\n"); + + memset(&hinfo, 0, sizeof(hinfo)); + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +do_fence_request(int fd, const char *src, serial_req_t *req, serial_info *info) +{ + char response = RESP_FAIL; + struct serial_hostlist_arg arg; + serial_resp_t resp; + + arg.fd = fd; + + switch(req->request) { + case FENCE_NULL: + response = info->cb->null((char *)req->domain, info->priv); + break; + case FENCE_ON: + if (map_check(info->maps, src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->on((char *)req->domain, src, + req->seqno, info->priv); + break; + case FENCE_OFF: + if (map_check(info->maps, src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->off((char *)req->domain, src, + req->seqno, info->priv); + break; + case FENCE_REBOOT: + if (map_check(info->maps, src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->reboot((char *)req->domain, src, + req->seqno, info->priv); + break; + case FENCE_STATUS: + if (map_check(info->maps, src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->status((char *)req->domain, info->priv); + break; + case FENCE_DEVSTATUS: + response = info->cb->devstatus(info->priv); + break; + case FENCE_HOSTLIST: + arg.map = info->maps; + arg.src = src; + arg.fd = fd; + + serial_hostlist_begin(arg.fd); + response = info->cb->hostlist(serial_hostlist, &arg, + info->priv); + serial_hostlist_end(arg.fd); + break; + } + + resp.magic = SERIAL_MAGIC; + resp.response = response; + swab_serial_resp_t(&resp); + + dbg_printf(3, "Sending response to caller...\n"); + if (_write_retry(fd, &resp, sizeof(resp), NULL) < 0) + perror("write"); + + /* XVM shotguns multicast packets, so we want to avoid + * acting on the same request multiple times if the first + * attempt was successful. + */ + history_record(info->history, req); + + return 1; +} + + +static int +serial_dispatch(listener_context_t c, struct timeval *timeout) +{ + char src_domain[MAX_DOMAINNAME_LENGTH]; + serial_info *info; + serial_req_t data; + fd_set rfds; + struct timeval tv; + int max; + int n, x, ret; + + info = (serial_info *)c; + VALIDATE(info); + + FD_ZERO(&rfds); + domain_sock_fdset(&rfds, &max); + FD_SET(info->wake_fd, &rfds); + if (info->wake_fd > max) + max = info->wake_fd; + + n = select(max+1, &rfds, NULL, NULL, timeout); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + n = 0; + else + dbg_printf(2, "select: %s\n", strerror(errno)); + return n; + } + + /* + * See if the goal was just to be woken up in order to refill our + * file descriptor set. For example, if multiple domains were + * created simultaneously, we would have to refill our fd_set + */ + if (FD_ISSET(info->wake_fd, &rfds)) { + tv.tv_sec = 0; + tv.tv_usec = 10000; + _read_retry(info->wake_fd, &c, 1, &tv); + return 0; + } + + /* + * If no requests, we're done + */ + if (n == 0) + return 0; + + /* find & read request */ + for (x = 0; x <= max; x++) { + if (FD_ISSET(x, &rfds)) { + tv.tv_sec = 1; + tv.tv_usec = 0; + + ret = _read_retry(x, &data, sizeof(data), &tv); + + if (ret != sizeof(data)) { + if (--n > 0) + continue; + else + return 0; + } else { + swab_serial_req_t(&data); + break; + } + } + } + + src_domain[0] = 0; + domain_sock_name(x, src_domain, sizeof(src_domain)); + + dbg_printf(2, "Sock %d Request %d seqno %d src %s target %s\n", x, + data.request, data.seqno, src_domain, data.domain); + + if (history_check(info->history, &data) == 1) { + dbg_printf(3, "We just did this request; dropping packet\n"); + return 0; + } + + do_fence_request(x, src_domain[0] == 0 ? NULL : src_domain, + &data, info); + + return 0; +} + + +static int +serial_config(config_object_t *config, serial_info *args) +{ + char value[1024]; + int errors = 0; + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value))==0) + dset(atoi(value)); + + if (sc_get(config, "listeners/serial/@uri", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for uri\n", value); + args->uri = strdup(value); + } + + if (sc_get(config, "listeners/serial/@path", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for path\n", value); + args->path = strdup(value); + } + + if (sc_get(config, "listeners/serial/@mode", + value, sizeof(value)-1) == 0) { + if (!strcasecmp(value, "vmchannel")) { + args->mode = 1; + } else if (!strcasecmp(value, "serial")) { + args->mode = 0; + } else { + args->mode = atoi(value); + if (args->mode < 0) + args->mode = 0; + } + + dbg_printf(1, "Got %s for mode\n", + args->mode?"VMChannel":"serial"); + + } + + return errors; +} + + +static int +serial_init(listener_context_t *c, const fence_callbacks_t *cb, + config_object_t *config, map_object_t *map, void *priv) +{ + serial_info *info; + int ret; + + info = malloc(sizeof(*info)); + if (!info) + return -1; + memset(info, 0, sizeof(*info)); + + info->priv = priv; + info->cb = cb; + + ret = serial_config(config, info); + if (ret < 0) { + perror("serial_config"); + return -1; + } else if (ret > 0) { + printf("%d errors found during configuration\n",ret); + return -1; + } + + info->maps = map; + + info->magic = SERIAL_PLUG_MAGIC; + info->history = history_init(check_history, 10, sizeof(fence_req_t)); + *c = (listener_context_t)info; + start_event_listener(info->uri, info->path, info->mode, &info->wake_fd); + sleep(1); + + return 0; +} + + +static int +serial_shutdown(listener_context_t c) +{ + serial_info *info = (serial_info *)c; + + dbg_printf(3, "Shutting down serial\n"); + + VALIDATE(info); + info->magic = 0; + stop_event_listener(); + domain_sock_cleanup(); + history_wipe(info->history); + free(info->history); + free(info->uri); + free(info->path); + free(info); + + return 0; +} + + +static listener_plugin_t serial_plugin = { + .name = NAME, + .version = SERIAL_VERSION, + .init = serial_init, + .dispatch = serial_dispatch, + .cleanup = serial_shutdown, +}; + +double +LISTENER_VER_SYM(void) +{ + return PLUGIN_VERSION_LISTENER; +} + +const listener_plugin_t * +LISTENER_INFO_SYM(void) +{ + return &serial_plugin; +} diff --git a/agents/virt/server/serial.h b/agents/virt/server/serial.h new file mode 100644 index 0000000..481400a --- /dev/null +++ b/agents/virt/server/serial.h @@ -0,0 +1,20 @@ +#ifndef __VIRT_SERIAL_H +#define __VIRT_SERIAL_H + +#include <sys/select.h> + +/* virt-sockets.c */ +int domain_sock_setup(const char *domain, const char *socket_path); +int domain_sock_close(const char *domain); +int domain_sock_fdset(fd_set *set, int *max); + +/* Find the domain name associated with a FD */ +int domain_sock_name(int fd, char *outbuf, size_t buflen); +int domain_sock_cleanup(void); + +/* virt-serial.c - event thread control functions */ +int start_event_listener(const char *uri, const char *path, int mode, int *wake_fd); +int stop_event_listener(void); + + +#endif diff --git a/agents/virt/server/static_map.c b/agents/virt/server/static_map.c new file mode 100644 index 0000000..7bdc400 --- /dev/null +++ b/agents/virt/server/static_map.c @@ -0,0 +1,237 @@ +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#include "simpleconfig.h" +#include "static_map.h" +#include "list.h" +#include "debug.h" +#include "serial.h" +#include "uuid-test.h" + +struct perm_entry { + list_head(); + char name[129]; +}; + +struct perm_group { + list_head(); + struct perm_entry *uuids; + struct perm_entry *ips; + char name[129]; +}; + + +static void +static_map_cleanup(void **info) +{ + struct perm_group *groups = (struct perm_group *)(*info); + struct perm_group *group; + struct perm_entry *entry; + + while (groups) { + group = groups; + list_remove(&groups, group); + while (group->uuids) { + entry = group->uuids; + list_remove(&group->uuids, entry); + free(entry); + } + while (group->ips) { + entry = group->ips; + list_remove(&group->ips, entry); + free(entry); + } + free(group); + } + + *info = NULL; +} + + +static int +static_map_check(void *info, const char *src, const char *tgt_uuid, const char *tgt_name) +{ + struct perm_group *groups = (struct perm_group *)info; + struct perm_group *group; + struct perm_entry *left, *tmp; + int x, y, uuid = 0; + + if (!info) + return 1; /* no maps == wide open */ + + dbg_printf(99, "[server:map_check] map request: src: %s uuid: %s name: %s\n", src, tgt_uuid, tgt_name); + + uuid = is_uuid(src); + + list_for(&groups, group, x) { + left = NULL; + + if (uuid) { + list_for(&group->uuids, tmp, y) { + if (!strcasecmp(tmp->name, src)) { + left = tmp; + break; + } + } + } else { + list_for(&group->ips, tmp, y) { + if (!strcasecmp(tmp->name, src)) { + left = tmp; + break; + } + } + } + + if (!left) + continue; + + list_for(&group->uuids, tmp, y) { + if (!strcasecmp(tmp->name, tgt_uuid)) { + return 1; + } + /* useful only for list */ + if (tgt_name) { + if (!strcasecmp(tmp->name, tgt_name)) { + return 1; + } + } + } + } + + return 0; +} + + +static int +static_map_load(void *config_ptr, void **perm_info) +{ + config_object_t *config = config_ptr; + int group_idx = 0; + int entry_idx = 0; + int found; + char value[128]; + char buf[256]; + char buf2[512]; + struct perm_group *group = NULL, *groups = NULL; + struct perm_entry *entry = NULL; + + if (!perm_info) + return -1; + + do { + snprintf(buf, sizeof(buf)-1, "groups/group[%d]", ++group_idx); + + if (sc_get(config, buf, value, sizeof(value)) != 0) { + snprintf(buf2, sizeof(buf2)-1, "%s/@uuid", buf); + if (sc_get(config, buf2, value, sizeof(value)) != 0) { + snprintf(buf2, sizeof(buf2)-1, "%s/@ip", buf); + if (sc_get(config, buf2, value, + sizeof(value)) != 0) { + break; + } + } + snprintf(buf2, sizeof(buf2)-1, "%s/@name", buf); + if (sc_get(config, buf2, value, sizeof(value)) != 0) { + snprintf(value, sizeof(value), "unnamed-%d", + group_idx); + } + } + + group = malloc(sizeof(*group)); + assert(group); + memset(group, 0, sizeof(*group)); + strncpy(group->name, value, sizeof(group->name)); + dbg_printf(3, "Group: %s\n", value); + + found = 0; + entry_idx = 0; + do { + snprintf(buf2, sizeof(buf2)-1, "%s/@uuid[%d]", + buf, ++entry_idx); + + if (sc_get(config, buf2, value, sizeof(value)) != 0) { + break; + } + + ++found; + entry = malloc(sizeof(*entry)); + assert(entry); + memset(entry, 0, sizeof(*entry)); + strncpy(entry->name, value, sizeof(entry->name)); + dbg_printf(3, " - UUID Entry: %s\n", value); + + list_insert(&group->uuids, entry); + + } while (1); + + entry_idx = 0; + do { + snprintf(buf2, sizeof(buf2)-1, "%s/@ip[%d]", + buf, ++entry_idx); + + if (sc_get(config, buf2, value, sizeof(value)) != 0) { + break; + } + + ++found; + entry = malloc(sizeof(*entry)); + assert(entry); + memset(entry, 0, sizeof(*entry)); + strncpy(entry->name, value, sizeof(entry->name)); + dbg_printf(3, " - IP Entry: %s\n", value); + + list_insert(&group->ips, entry); + + } while (1); + + + if (!found) + free(group); + else + list_insert(&groups, group); + + } while (1); + + *perm_info = groups; + + return 0; +} + + +static const map_object_t static_map_obj = { + .load = static_map_load, + .check = static_map_check, + .cleanup = static_map_cleanup, + .info = NULL +}; + + +void * +map_init(void) +{ + map_object_t *o; + + o = malloc(sizeof(*o)); + if (!o) + return NULL; + memset(o, 0, sizeof(*o)); + memcpy(o, &static_map_obj, sizeof(*o)); + + return (void *)o; +} + + +void +map_release(void *c) +{ + map_object_t *o = (map_object_t *)c; + + static_map_cleanup(&o->info); + free(c); +} diff --git a/agents/virt/server/tcp.c b/agents/virt/server/tcp.c new file mode 100644 index 0000000..c1fb60c --- /dev/null +++ b/agents/virt/server/tcp.c @@ -0,0 +1,575 @@ +/* + Copyright Red Hat, Inc. 2006-2012 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ + +#include "config.h" + +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <nss.h> +#include <sys/socket.h> +#include <netdb.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "mcast.h" +#include "tcp.h" +#include "tcp_listener.h" +#include "debug.h" +#include "fdops.h" +#include "list.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" +#include "history.h" + +#define NAME "tcp" +#define TCP_VERSION "0.2" + +#define TCP_MAGIC 0xc3dff7a9 + +#define VALIDATE(info) \ +do {\ + if (!info || info->magic != TCP_MAGIC)\ + return -EINVAL;\ +} while(0) + +typedef struct _tcp_options { + char *key_file; + char *addr; + int family; + unsigned int port; + unsigned int hash; + unsigned int auth; + unsigned int flags; +} tcp_options; + + +typedef struct _tcp_info { + uint64_t magic; + void *priv; + map_object_t *map; + history_info_t *history; + char key[MAX_KEY_LEN]; + tcp_options args; + const fence_callbacks_t *cb; + ssize_t key_len; + int listen_sock; +} tcp_info; + + +struct tcp_hostlist_arg { + map_object_t *map; + const char *src; + int fd; +}; + + +/* + * See if we fenced this node recently (successfully) + * If so, ignore the request for a few seconds. + * + * We purge our history when the entries time out. + */ +static int +check_history(void *a, void *b) { + fence_req_t *old = a, *current = b; + + if (old->request == current->request && + old->seqno == current->seqno && + !strcasecmp((const char *)old->domain, + (const char *)current->domain)) { + return 1; + } + return 0; +} + +static int +tcp_hostlist(const char *vm_name, const char *vm_uuid, + int state, void *priv) +{ + struct tcp_hostlist_arg *arg = (struct tcp_hostlist_arg *)priv; + host_state_t hinfo; + struct timeval tv; + int ret; + + if (map_check2(arg->map, arg->src, vm_uuid, vm_name) == 0) { + /* if we don't have access to fence this VM, + * we should not see it in a hostlist either */ + return 0; + } + + strncpy((char *)hinfo.domain, vm_name, sizeof(hinfo.domain) - 1); + strncpy((char *)hinfo.uuid, vm_uuid, sizeof(hinfo.uuid) - 1); + hinfo.state = state; + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(arg->fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +tcp_hostlist_begin(int fd) +{ + struct timeval tv; + char val = (char)RESP_HOSTLIST; + + tv.tv_sec = 1; + tv.tv_usec = 0; + return _write_retry(fd, &val, 1, &tv); +} + + +static int +tcp_hostlist_end(int fd) +{ + host_state_t hinfo; + struct timeval tv; + int ret; + + printf("Sending terminator packet\n"); + + memset(&hinfo, 0, sizeof(hinfo)); + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + +static socklen_t +sockaddr_len(const struct sockaddr_storage *ss) +{ + if (ss->ss_family == AF_INET) { + return sizeof(struct sockaddr_in); + } else { + return sizeof(struct sockaddr_in6); + } +} + +static int +do_fence_request_tcp(int fd, struct sockaddr_storage *ss, socklen_t sock_len, fence_req_t *req, tcp_info *info) +{ + char ip_addr_src[1024]; + char response = 1; + struct tcp_hostlist_arg arg; + int ret; + + /* Noops if auth == AUTH_NONE */ + if (sock_response(fd, info->args.auth, info->key, info->key_len, 10) <= 0) { + printf("Failed to respond to challenge\n"); + close(fd); + return -1; + } + + ret = sock_challenge(fd, info->args.auth, info->key, info->key_len, 10); + if (ret <= 0) { + printf("Remote failed challenge\n"); + close(fd); + return -1; + } + + + if (getnameinfo((struct sockaddr *)ss, sockaddr_len(ss), + ip_addr_src, sizeof(ip_addr_src), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV) < 0) { + printf("Unable to resolve!\n"); + close(fd); + return -1; + } + + dbg_printf(2, "Request %d seqno %d src %s target %s\n", + req->request, req->seqno, ip_addr_src, req->domain); + + switch(req->request) { + case FENCE_NULL: + response = info->cb->null((char *)req->domain, info->priv); + break; + case FENCE_ON: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->on((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_OFF: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->off((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_REBOOT: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->reboot((char *)req->domain, ip_addr_src, + req->seqno, info->priv); + break; + case FENCE_STATUS: + if (map_check(info->map, ip_addr_src, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->status((char *)req->domain, info->priv); + break; + case FENCE_DEVSTATUS: + response = info->cb->devstatus(info->priv); + break; + case FENCE_HOSTLIST: + arg.map = info->map; + arg.src = ip_addr_src; + arg.fd = fd; + + tcp_hostlist_begin(arg.fd); + response = info->cb->hostlist(tcp_hostlist, &arg, + info->priv); + tcp_hostlist_end(arg.fd); + break; + } + + dbg_printf(3, "Sending response to caller...\n"); + if (_write_retry(fd, &response, 1, NULL) < 0) { + perror("write"); + } + + history_record(info->history, req); + + if (fd != -1) + close(fd); + + return 1; +} + + +static int +tcp_dispatch(listener_context_t c, struct timeval *timeout) +{ + tcp_info *info; + fence_req_t data; + fd_set rfds; + int n; + int client_fd; + int ret; + struct timeval tv; + struct sockaddr_storage ss; + socklen_t sock_len = sizeof(ss); + + if (timeout != NULL) + memcpy(&tv, timeout, sizeof(tv)); + else { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + + info = (tcp_info *)c; + VALIDATE(info); + + FD_ZERO(&rfds); + FD_SET(info->listen_sock, &rfds); + + n = select(info->listen_sock + 1, &rfds, NULL, NULL, timeout); + if (n <= 0) { + if (errno == EINTR || errno == EAGAIN) + n = 0; + else + dbg_printf(2, "select: %s\n", strerror(errno)); + return n; + } + + client_fd = accept(info->listen_sock, (struct sockaddr *)&ss, &sock_len); + if (client_fd < 0) { + perror("accept"); + return -1; + } + + dbg_printf(3, "Accepted client...\n"); + + ret = _read_retry(client_fd, &data, sizeof(data), &tv); + if (ret != sizeof(data)) { + dbg_printf(3, "Invalid request (read %d bytes)\n", ret); + close(client_fd); + return 0; + } + + swab_fence_req_t(&data); + + if (!verify_request(&data, info->args.hash, info->key, + info->key_len)) { + printf("Key mismatch; dropping client\n"); + close(client_fd); + return 0; + } + + dbg_printf(3, "Request %d seqno %d domain %s\n", + data.request, data.seqno, data.domain); + + if (history_check(info->history, &data) == 1) { + printf("We just did this request; dropping client\n"); + close(client_fd); + return 0; + } + + switch(info->args.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + printf("Plain TCP request\n"); + do_fence_request_tcp(client_fd, &ss, sock_len, &data, info); + break; + default: + printf("XXX Unhandled authentication\n"); + } + + return 0; +} + + +static int +tcp_config(config_object_t *config, tcp_options *args) +{ + char value[1024]; + int errors = 0; + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value))==0) + dset(atoi(value)); + + if (sc_get(config, "listeners/tcp/@key_file", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for key_file\n", value); + args->key_file = strdup(value); + } else { + args->key_file = strdup(DEFAULT_KEY_FILE); + if (!args->key_file) { + dbg_printf(1, "Failed to allocate memory\n"); + return -1; + } + } + + args->hash = DEFAULT_HASH; + if (sc_get(config, "listeners/tcp/@hash", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for hash\n", value); + if (!strcasecmp(value, "none")) { + args->hash = HASH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->hash = HASH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->hash = HASH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->hash = HASH_SHA512; + } else { + dbg_printf(1, "Unsupported hash: %s\n", value); + ++errors; + } + } + + args->auth = DEFAULT_AUTH; + if (sc_get(config, "listeners/tcp/@auth", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for auth\n", value); + if (!strcasecmp(value, "none")) { + args->hash = AUTH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->hash = AUTH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->hash = AUTH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->hash = AUTH_SHA512; + } else { + dbg_printf(1, "Unsupported auth: %s\n", value); + ++errors; + } + } + + args->family = PF_INET; + if (sc_get(config, "listeners/tcp/@family", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for family\n", value); + if (!strcasecmp(value, "ipv4")) { + args->family = PF_INET; + } else if (!strcasecmp(value, "ipv6")) { + args->family = PF_INET6; + } else { + dbg_printf(1, "Unsupported family: %s\n", value); + ++errors; + } + } + + if (sc_get(config, "listeners/tcp/@address", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for address\n", value); + args->addr = strdup(value); + } else { + if (args->family == PF_INET) { + args->addr = strdup(IPV4_TCP_ADDR_DEFAULT); + } else { + args->addr = strdup(IPV6_TCP_ADDR_DEFAULT); + } + } + if (!args->addr) { + return -1; + } + + args->port = DEFAULT_MCAST_PORT; + if (sc_get(config, "listeners/tcp/@port", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for port\n", value); + args->port = atoi(value); + if (args->port <= 0) { + dbg_printf(1, "Invalid port: %s\n", value); + ++errors; + } + } + + return errors; +} + + +static int +tcp_init(listener_context_t *c, const fence_callbacks_t *cb, + config_object_t *config, map_object_t *map, void *priv) +{ + tcp_info *info; + int listen_sock, ret; + + /* Initialize NSS; required to do hashing, as silly as that + sounds... */ + if (NSS_NoDB_Init(NULL) != SECSuccess) { + printf("Could not initialize NSS\n"); + return 1; + } + + info = calloc(1, sizeof(*info)); + if (!info) + return -1; + + info->priv = priv; + info->cb = cb; + info->map = map; + + ret = tcp_config(config, &info->args); + if (ret < 0) + perror("tcp_config"); + else if (ret > 0) + printf("%d errors found during configuration\n",ret); + + if (ret != 0) { + if (info->args.key_file) + free(info->args.key_file); + if (info->args.addr) + free(info->args.addr); + free(info); + return -1; + } + + if (info->args.auth != AUTH_NONE || info->args.hash != HASH_NONE) { + info->key_len = read_key_file(info->args.key_file, + info->key, sizeof(info->key)); + if (info->key_len < 0) { + printf("Could not read %s; operating without " + "authentication\n", info->args.key_file); + info->args.auth = AUTH_NONE; + info->args.hash = HASH_NONE; + info->key_len = 0; + } + } + + if (info->args.family == PF_INET) { + listen_sock = ipv4_listen(info->args.addr, info->args.port, 10); + } else { + listen_sock = ipv6_listen(info->args.addr, info->args.port, 10); + } + + if (listen_sock < 0) { + printf("Could not set up listen socket\n"); + if (info->args.key_file) + free(info->args.key_file); + if (info->args.addr) + free(info->args.addr); + free(info); + return -1; + } + + info->magic = TCP_MAGIC; + info->listen_sock = listen_sock; + info->history = history_init(check_history, 10, sizeof(fence_req_t)); + *c = (listener_context_t)info; + return 0; +} + + +static int +tcp_shutdown(listener_context_t c) +{ + tcp_info *info = (tcp_info *)c; + + VALIDATE(info); + info->magic = 0; + history_wipe(info->history); + free(info->history); + free(info->args.key_file); + free(info->args.addr); + close(info->listen_sock); + free(info); + + return 0; +} + + +static listener_plugin_t tcp_plugin = { + .name = NAME, + .version = TCP_VERSION, + .init = tcp_init, + .dispatch = tcp_dispatch, + .cleanup = tcp_shutdown, +}; + +double +LISTENER_VER_SYM(void) +{ + return PLUGIN_VERSION_LISTENER; +} + +const listener_plugin_t * +LISTENER_INFO_SYM(void) +{ + return &tcp_plugin; +} diff --git a/agents/virt/server/uuid-test.c b/agents/virt/server/uuid-test.c new file mode 100644 index 0000000..3116ef9 --- /dev/null +++ b/agents/virt/server/uuid-test.c @@ -0,0 +1,66 @@ +#include "config.h" + +#include <uuid/uuid.h> +#include <errno.h> +#include <string.h> + +#include "uuid-test.h" + +int +is_uuid(const char *value) +{ + uuid_t id; + char test_value[37]; + + if (strlen(value) < 36) { + return 0; + } + + memset(id, 0, sizeof(uuid_t)); + + if (uuid_is_null(id) < 0) { + errno = EINVAL; + return -1; + } + + if (uuid_parse(value, id) < 0) { + return 0; + } + + memset(test_value, 0, sizeof(test_value)); + uuid_unparse(id, test_value); + + if (strcasecmp(value, test_value)) { + return 0; + } + + return 1; +} + +#ifdef STANDALONE +#include <stdio.h> + +int +main(int argc, char **argv) +{ + int ret; + + if (argc < 2) { + printf("Usage: uuidtest <value>\n"); + return 1; + } + + ret = is_uuid(argv[1]); + if (ret == 0) { + printf("%s is NOT a uuid\n", argv[1]); + } else if (ret == 1) { + printf("%s is a uuid\n", argv[1]); + } else { + printf("Error: %s\n", strerror(errno)); + return 1; + } + + return 0; +} + +#endif diff --git a/agents/virt/server/uuid-test.h b/agents/virt/server/uuid-test.h new file mode 100644 index 0000000..164fec7 --- /dev/null +++ b/agents/virt/server/uuid-test.h @@ -0,0 +1,14 @@ +#ifndef __UUID_TEST_H +#define __UUID_TEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +int is_uuid(const char *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/agents/virt/server/virt-serial.c b/agents/virt/server/virt-serial.c new file mode 100644 index 0000000..6b369bc --- /dev/null +++ b/agents/virt/server/virt-serial.c @@ -0,0 +1,444 @@ +// #include <config.h> + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/poll.h> +#include <libvirt/libvirt.h> + +#include <libxml/xmlreader.h> + +#include "simpleconfig.h" +#include "debug.h" + +#define DEBUG0(fmt) dbg_printf(5,"%s:%d :: " fmt "\n", \ + __func__, __LINE__) +#define DEBUG1(fmt, ...) dbg_printf(5, "%s:%d: " fmt "\n", \ + __func__, __LINE__, __VA_ARGS__) + +#include "serial.h" + +#define STREQ(a,b) (strcmp((a),(b)) == 0) + +static pthread_t event_tid = 0; +static int run = 0; + +/* Prototypes */ +const char *eventToString(int event); +int myDomainEventCallback1(virConnectPtr conn, virDomainPtr dom, + int event, int detail, void *opaque); + +void usage(const char *pname); + +struct domain_info { + virDomainPtr dom; + virDomainEventType event; +}; + +static int +is_in_directory(const char *dir, const char *pathspec) +{ + char *last_slash = NULL; + size_t dirlen, pathlen; + + if (!dir || !pathspec) + return 0; + + dirlen = strlen(dir); + pathlen = strlen(pathspec); + + /* + printf("dirlen = %d pathlen = %d\n", + dirlen, pathlen); + */ + + /* chop off trailing slashes */ + while (dirlen && dir[dirlen-1]=='/') + --dirlen; + + /* chop off leading slashes */ + while (dirlen && dir[0] == '/') { + ++dir; + --dirlen; + } + + /* chop off leading slashes */ + while (pathlen && pathspec[0] == '/') { + ++pathspec; + --pathlen; + } + + if (!dirlen || !pathlen) + return 0; + + if (pathlen <= dirlen) + return 0; + + last_slash = strrchr(pathspec, '/'); + + if (!last_slash) + return 0; + + while (*last_slash == '/' && last_slash > pathspec) + --last_slash; + + if (last_slash == pathspec) + return 0; + + pathlen = last_slash - pathspec + 1; + /*printf("real dirlen = %d real pathlen = %d\n", + dirlen, pathlen);*/ + if (pathlen != dirlen) + return 0; + + /* todo - intelligently skip multiple slashes mid-path */ + return !strncmp(dir, pathspec, dirlen); +} + + +static int +domainStarted(virDomainPtr mojaDomain, const char *path, int mode) +{ + char dom_uuid[42]; + char *xml; + xmlDocPtr doc; + xmlNodePtr cur, devices, child, serial; + xmlAttrPtr attr, attr_mode, attr_path; + + if (!mojaDomain) + return -1; + + virDomainGetUUIDString(mojaDomain, dom_uuid); + + xml = virDomainGetXMLDesc(mojaDomain, 0); + // printf("%s\n", xml); + // @todo: free mojaDomain + + // parseXML output + doc = xmlParseMemory(xml, strlen(xml)); + xmlFree(xml); + cur = xmlDocGetRootElement(doc); + + if (cur == NULL) { + fprintf(stderr, "Empty doc\n"); + xmlFreeDoc(doc); + return -1; + } + + if (xmlStrcmp(cur->name, (const xmlChar *) "domain")) { + fprintf(stderr, "no domain?\n"); + xmlFreeDoc(doc); + return -1; + } + + devices = cur->xmlChildrenNode; + for (devices = cur->xmlChildrenNode; devices != NULL; + devices = devices->next) { + if (xmlStrcmp(devices->name, (const xmlChar *) "devices")) { + continue; + } + + for (child = devices->xmlChildrenNode; child != NULL; + child = child->next) { + + if ((!mode && xmlStrcmp(child->name, (const xmlChar *) "serial")) || + (mode && xmlStrcmp(child->name, (const xmlChar *) "channel"))) { + continue; + } + + attr = xmlHasProp(child, (const xmlChar *)"type"); + if (attr == NULL) + continue; + + if (xmlStrcmp(attr->children->content, + (const xmlChar *) "unix")) { + continue; + } + + for (serial = child->xmlChildrenNode; serial != NULL; + serial = serial->next) { + if (xmlStrcmp(serial->name, + (const xmlChar *) "source")) { + continue; + } + + attr_mode = xmlHasProp(serial, (const xmlChar *)"mode"); + attr_path = xmlHasProp(serial, (const xmlChar *)"path"); + + if (!attr_path || !attr_mode) + continue; + + if (xmlStrcmp(attr_mode->children->content, + (const xmlChar *) "bind")) + continue; + + if (path && !is_in_directory(path, (const char *) + attr_path->children->content)) + continue; + + domain_sock_setup(dom_uuid, (const char *) + attr_path->children->content); + } + } + } + + xmlFreeDoc(doc); + return 0; +} + +static int +registerExisting(virConnectPtr vp, const char *path, int mode) +{ + int *d_ids = NULL; + int d_count, x; + virDomainPtr dom; + virDomainInfo d_info; + + errno = EINVAL; + if (!vp) + return -1; + + d_count = virConnectNumOfDomains(vp); + if (d_count <= 0) { + if (d_count == 0) { + /* Successful, but no domains running */ + errno = 0; + return 0; + } + goto out_fail; + } + + d_ids = malloc(sizeof (int) * d_count); + if (!d_ids) + goto out_fail; + + if (virConnectListDomains(vp, d_ids, d_count) < 0) + goto out_fail; + + /* Ok, we have the domain IDs - let's get their names and states */ + for (x = 0; x < d_count; x++) { + dom = virDomainLookupByID(vp, d_ids[x]); + if (!dom) { + /* XXX doom */ + goto out_fail; + } + + if (virDomainGetInfo(dom, &d_info) < 0) { + /* XXX no info for the domain?!! */ + virDomainFree(dom); + goto out_fail; + } + + if (d_info.state != VIR_DOMAIN_SHUTOFF && + d_info.state != VIR_DOMAIN_CRASHED) + domainStarted(dom, path, mode); + + virDomainFree(dom); + } + + out_fail: + free(d_ids); + return 0; +} + +static int +domainStopped(virDomainPtr mojaDomain) +{ + char dom_uuid[42]; + + if (!mojaDomain) + return -1; + + virDomainGetUUIDString(mojaDomain, dom_uuid); + domain_sock_close(dom_uuid); + + return 0; +} + + +struct event_args { + char *uri; + char *path; + int mode; + int wake_fd; +}; + +static void +connectClose(virConnectPtr conn ATTRIBUTE_UNUSED, + int reason, + void *opaque ATTRIBUTE_UNUSED) +{ + switch (reason) { + case VIR_CONNECT_CLOSE_REASON_ERROR: + dbg_printf(2, "Connection closed due to I/O error\n"); + break; + case VIR_CONNECT_CLOSE_REASON_EOF: + dbg_printf(2, "Connection closed due to end of file\n"); + break; + case VIR_CONNECT_CLOSE_REASON_KEEPALIVE: + dbg_printf(2, "Connection closed due to keepalive timeout\n"); + break; + case VIR_CONNECT_CLOSE_REASON_CLIENT: + dbg_printf(2, "Connection closed due to client request\n"); + break; + default: + dbg_printf(2, "Connection closed due to unknown reason\n"); + break; + }; + run = 0; +} + +int +myDomainEventCallback1(virConnectPtr conn, + virDomainPtr dom, int event, int detail, void *opaque) +{ + struct event_args *args = (struct event_args *)opaque; + + if (event == VIR_DOMAIN_EVENT_STARTED || + event == VIR_DOMAIN_EVENT_STOPPED) { + virDomainRef(dom); + if (event == VIR_DOMAIN_EVENT_STARTED) { + domainStarted(dom, args->path, args->mode); + virDomainFree(dom); + if (write(args->wake_fd, "x", 1) != 1) { + dbg_printf(3, "Unable to wake up thread\n"); + } + } else if (event == VIR_DOMAIN_EVENT_STOPPED) { + domainStopped(dom); + virDomainFree(dom); + } + } + + return 0; +} + + +static void * +event_thread(void *arg) +{ + struct event_args *args = (struct event_args *)arg; + virConnectPtr dconn = NULL; + int callback1ret = -1; + + dbg_printf(3, "Libvirt event listener starting\n"); + if (args->uri) + dbg_printf(3," * URI: %s\n", args->uri); + if (args->path) + dbg_printf(3," * Socket path: %s\n", args->path); + dbg_printf(3," * Mode: %s\n", args->mode ? "VMChannel" : "Serial"); + + if (virEventRegisterDefaultImpl() < 0) { + dbg_printf(1, "Failed to register default event impl\n"); + goto out; + } + + dconn = virConnectOpen(args->uri); + if (!dconn) { + dbg_printf(1, "Error connecting to libvirt\n"); + goto out; + } + + virConnectRegisterCloseCallback(dconn, connectClose, NULL, NULL); + + DEBUG0("Registering domain event cbs"); + + registerExisting(dconn, args->path, args->mode); + + callback1ret = + virConnectDomainEventRegister(dconn, myDomainEventCallback1, arg, NULL); + + if (callback1ret != -1) { + if (virConnectSetKeepAlive(dconn, 5, 5) < 0) { + dbg_printf(1, "Failed to start keepalive protocol\n"); + run = 0; + } + while (run) { + if (virEventRunDefaultImpl() < 0) { + dbg_printf(1, "RunDefaultImpl Failed\n"); + } + } + + DEBUG0("Deregistering event handlers"); + virConnectDomainEventDeregister(dconn, myDomainEventCallback1); + } + + DEBUG0("Closing connection"); + if (dconn && virConnectClose(dconn) < 0) { + dbg_printf(1, "error closing libvirt connection\n"); + } + +out: + free(args->uri); + free(args->path); + free(args); + return NULL; +} + + +int +start_event_listener(const char *uri, const char *path, int mode, int *wake_fd) +{ + struct event_args *args = NULL; + int wake_pipe[2]; + + virInitialize(); + + args = malloc(sizeof(*args)); + if (!args) + return -1; + memset(args, 0, sizeof(*args)); + + if (pipe2(wake_pipe, O_CLOEXEC) < 0) { + goto out_fail; + } + + if (uri) { + args->uri = strdup(uri); + if (args->uri == NULL) + goto out_fail; + } + + if (path) { + args->path = strdup(path); + if (args->path == NULL) + goto out_fail; + } + + args->mode = mode; + //args->p_tid = pthread_self(); + *wake_fd = wake_pipe[0]; + args->wake_fd = wake_pipe[1]; + + run = 1; + + return pthread_create(&event_tid, NULL, event_thread, args); + +out_fail: + free(args->uri); + free(args->path); + free(args); + return -1; +} + + +int +stop_event_listener(void) +{ + run = 0; + //pthread_cancel(event_tid); + pthread_join(event_tid, NULL); + event_tid = 0; + + return 0; +} + + diff --git a/agents/virt/server/virt-sockets.c b/agents/virt/server/virt-sockets.c new file mode 100644 index 0000000..7f36f62 --- /dev/null +++ b/agents/virt/server/virt-sockets.c @@ -0,0 +1,242 @@ +#include "config.h" + +#include <pthread.h> +#include <unistd.h> +#include <stdio.h> +#include <list.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include "serial.h" +#include "debug.h" +#include "simpleconfig.h" + +struct socket_list { + list_head(); + char *domain_name; + char *socket_path; + int socket_fd; +}; + +static struct socket_list *socks = NULL; +static pthread_mutex_t sock_list_mutex = PTHREAD_MUTEX_INITIALIZER; + + +static int +connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout) +{ + int ret, flags, err; + unsigned l; + fd_set rfds, wfds; + struct timeval tv; + + /* + Set up non-blocking connect + */ + flags = fcntl(fd, F_GETFL, 0); + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + return -1; + } + + ret = connect(fd, dest, len); + + if ((ret < 0) && (errno != EINPROGRESS)) + return -1; + + if (ret == 0) + goto done; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + if (select(fd + 1, &rfds, &wfds, NULL, &tv) == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (!FD_ISSET(fd, &rfds) && !FD_ISSET(fd, &wfds)) { + errno = EIO; + return -1; + } + + l = sizeof(err); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, + (void *)&err, &l) < 0) { + return -1; + } + + if (err != 0) { + errno = err; + return -1; + } + +done: + if (fcntl(fd, F_SETFL, flags) < 0) { + return -1; + } + return 0; +} + + +int +domain_sock_setup(const char *domain, const char *socket_path) +{ + struct sockaddr_un *sun = NULL; + struct socket_list *node = NULL; + socklen_t sun_len; + int sock = -1; + + sun_len = sizeof(*sun) + strlen(socket_path) + 1; + sun = malloc(sun_len); + if (!sun) + return -1; + + memset((char *)sun, 0, sun_len); + sun->sun_family = PF_LOCAL; + strncpy(sun->sun_path, socket_path, sizeof(sun->sun_path) - 1); + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + goto out_fail; + + if (connect_nb(sock, (struct sockaddr *)sun, SUN_LEN(sun), 3) < 0) + goto out_fail; + + free(sun); + sun = NULL; + + node = calloc(1, sizeof(*node)); + if (!node) + goto out_fail; + + node->domain_name = strdup(domain); + if (!node->domain_name) + goto out_fail; + + node->socket_path = strdup(socket_path); + if (!node->socket_path) + goto out_fail; + + node->socket_fd = sock; + + pthread_mutex_lock(&sock_list_mutex); + list_insert(&socks, node); + pthread_mutex_unlock(&sock_list_mutex); + + dbg_printf(3, "Registered %s on %d\n", domain, sock); + return 0; + +out_fail: + if (node) { + free(node->domain_name); + if (node->socket_path) + free(node->socket_path); + free(node); + } + free(sun); + if (sock >= 0) + close(sock); + return -1; +} + + +int +domain_sock_close(const char *domain) +{ + struct socket_list *node = NULL; + struct socket_list *dead = NULL; + int x; + + pthread_mutex_lock(&sock_list_mutex); + list_for(&socks, node, x) { + if (!strcasecmp(domain, node->domain_name)) { + list_remove(&socks, node); + dead = node; + break; + } + } + pthread_mutex_unlock(&sock_list_mutex); + + if (dead) { + dbg_printf(3, "Unregistered %s, fd%d\n", + dead->domain_name, + dead->socket_fd); + close(dead->socket_fd); + free(dead->domain_name); + free(dead->socket_path); + free(dead); + } + + return 0; +} + + +int +domain_sock_fdset(fd_set *fds, int *max) +{ + struct socket_list *node = NULL; + int x = 0, _max = -1; + + pthread_mutex_lock(&sock_list_mutex); + list_for(&socks, node, x) { + FD_SET(node->socket_fd, fds); + if (node->socket_fd > _max) + _max = node->socket_fd; + } + pthread_mutex_unlock(&sock_list_mutex); + + if (max) + *max = _max; + + return x; +} + + +int +domain_sock_name(int fd, char *outbuf, size_t buflen) +{ + struct socket_list *node = NULL; + int ret = 1, x = 0; + + pthread_mutex_lock(&sock_list_mutex); + list_for(&socks, node, x) { + if (node->socket_fd == fd) { + snprintf(outbuf, buflen, "%s", node->domain_name); + ret = 0; + break; + } + } + pthread_mutex_unlock(&sock_list_mutex); + + return ret; +} + + +int +domain_sock_cleanup(void) +{ + struct socket_list *dead= NULL; + + pthread_mutex_lock(&sock_list_mutex); + while(socks) { + dead = socks; + list_remove(&socks, dead); + close(dead->socket_fd); + free(dead->domain_name); + free(dead->socket_path); + free(dead); + } + pthread_mutex_unlock(&sock_list_mutex); + + return 0; +} + diff --git a/agents/virt/server/virt.c b/agents/virt/server/virt.c new file mode 100644 index 0000000..d4c94e9 --- /dev/null +++ b/agents/virt/server/virt.c @@ -0,0 +1,630 @@ +/* + Copyright Red Hat, Inc. 2006-2017 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ + +#include "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdlib.h> +#include <libvirt/libvirt.h> +#include <string.h> +#include <malloc.h> +#include <stdint.h> +#include <errno.h> +#include <syslog.h> + +#include "debug.h" +#include "uuid-test.h" +#include "virt.h" + +static int +_compare_virt(const void *_left, const void *_right) +{ + virt_state_t *left = (virt_state_t *)_left, + *right = (virt_state_t *)_right; + + return strcasecmp(left->v_name, right->v_name); +} + + +static void +_free_dom_list(virDomainPtr *dom_list, int len) { + int x; + + if (!dom_list || len <= 0) + return; + for (x = 0 ; x < len; x++) + virDomainFree(dom_list[x]); + + free(dom_list); +} + + +virt_list_t *vl_get(virConnectPtr *vp, int vp_count, int my_id) +{ + virt_list_t *vl = NULL; + int d_count = 0; + int i; + + errno = EINVAL; + if (!vp || vp_count < 1) + return NULL; + + for (i = 0 ; i < vp_count ; i++) { + int x; + virDomainPtr *dom_list; + virt_list_t *new_vl; + + int ret = virConnectListAllDomains(vp[i], &dom_list, 0); + if (ret == 0) + continue; + + if (ret < 0) { + int saved_errno = errno; + dbg_printf(2, "Error: virConnectListAllDomains: %d %d\n", + ret, saved_errno); + if (vl) + free(vl); + errno = saved_errno; + return NULL; + } + + d_count += ret; + new_vl = realloc(vl, sizeof(uint32_t) + sizeof(virt_state_t) * d_count); + if (!new_vl) { + _free_dom_list(dom_list, ret); + free(vl); + return NULL; + } + vl = new_vl; + vl->vm_count = d_count; + + /* Ok, we have the domain IDs - let's get their names and states */ + for (x = 0; x < ret; x++) { + char *d_name; + virDomainInfo d_info; + char d_uuid[MAX_DOMAINNAME_LENGTH]; + virDomainPtr dom = dom_list[x]; + + if (!(d_name = (char *)virDomainGetName(dom))) { + _free_dom_list(dom_list, ret); + free(vl); + return NULL; + } + + if (virDomainGetUUIDString(dom, d_uuid) != 0) { + _free_dom_list(dom_list, ret); + free(vl); + return NULL; + } + + if (virDomainGetInfo(dom, &d_info) < 0) { + _free_dom_list(dom_list, ret); + free(vl); + return NULL; + } + + /* Store the name & state */ + strncpy(vl->vm_states[x].v_name, d_name, MAX_DOMAINNAME_LENGTH); + strncpy(vl->vm_states[x].v_uuid, d_uuid, MAX_DOMAINNAME_LENGTH); + vl->vm_states[x].v_state.s_state = d_info.state; + vl->vm_states[x].v_state.s_owner = my_id; + } + + _free_dom_list(dom_list, ret); + } + /* No domains found */ + if (!vl) + return NULL; + + /* We have all the locally running domains & states now */ + /* Sort */ + qsort(&vl->vm_states[0], vl->vm_count, sizeof(vl->vm_states[0]), + _compare_virt); + return vl; +} + +int +vl_add(virt_list_t **vl, virt_state_t *vm) { + virt_list_t *new_vl; + size_t oldlen; + size_t newlen; + + if (!vl) + return -1; + + if (!*vl) { + *vl = malloc(sizeof(uint32_t) + sizeof(virt_state_t)); + if (!*vl) + return -1; + (*vl)->vm_count = 1; + memcpy(&(*vl)->vm_states[0], vm, sizeof(virt_state_t)); + return 0; + } + + oldlen = sizeof(uint32_t) + sizeof(virt_state_t) * (*vl)->vm_count; + newlen = oldlen + sizeof(virt_state_t); + + new_vl = malloc(newlen); + if (!new_vl) + return -1; + + memcpy(new_vl, *vl, oldlen); + memcpy(&new_vl->vm_states[(*vl)->vm_count], vm, sizeof(virt_state_t)); + new_vl->vm_count++; + + free(*vl); + *vl = new_vl; + return 0; +} + +int vl_remove_by_owner(virt_list_t **vl, uint32_t owner) { + int i; + int removed = 0; + virt_list_t *new_vl; + + if (!vl || !*vl) + return 0; + + for (i = 0 ; i < (*vl)->vm_count ; i++) { + if ((*vl)->vm_states[i].v_state.s_owner == owner) { + dbg_printf(2, "Removing %s\n", (*vl)->vm_states[i].v_name); + memset(&(*vl)->vm_states[i].v_state, 0, + sizeof((*vl)->vm_states[i].v_state)); + (*vl)->vm_states[i].v_name[0] = 0xff; + (*vl)->vm_states[i].v_uuid[0] = 0xff; + removed++; + } + } + + if (!removed) + return 0; + + qsort(&(*vl)->vm_states[0], (*vl)->vm_count, sizeof((*vl)->vm_states[0]), + _compare_virt); + (*vl)->vm_count -= removed; + + new_vl = realloc(*vl, sizeof(uint32_t) + (sizeof(virt_state_t) * ((*vl)->vm_count))); + if (new_vl) + *vl = new_vl; + return removed; +} + + +int +vl_update(virt_list_t **vl, virt_state_t *vm) { + virt_state_t *v = NULL; + + if (!vl) + return -1; + + if (!*vl) + return vl_add(vl, vm); + + if (strlen(vm->v_uuid) > 0) + v = vl_find_uuid(*vl, vm->v_uuid); + + if (v == NULL && strlen(vm->v_name) > 0) + v = vl_find_name(*vl, vm->v_name); + + if (v == NULL) { + dbg_printf(2, "Adding new entry for VM %s\n", vm->v_name); + vl_add(vl, vm); + } else { + dbg_printf(2, "Updating entry for VM %s\n", vm->v_name); + memcpy(&v->v_state, &vm->v_state, sizeof(v->v_state)); + } + + return 0; +} + + +void +vl_print(virt_list_t *vl) +{ + int x; + + printf("%-24.24s %-36.36s %-5.5s %-5.5s\n", "Domain", "UUID", + "Owner", "State"); + printf("%-24.24s %-36.36s %-5.5s %-5.5s\n", "------", "----", + "-----", "-----"); + + if (!vl || !vl->vm_count) + return; + + for (x = 0; x < vl->vm_count; x++) { + printf("%-24.24s %-36.36s %-5.5d %-5.5d\n", + vl->vm_states[x].v_name, + vl->vm_states[x].v_uuid, + vl->vm_states[x].v_state.s_owner, + vl->vm_states[x].v_state.s_state); + } +} + + +virt_state_t * +vl_find_name(virt_list_t *vl, const char *name) +{ + int x; + + if (!vl || !name || !vl->vm_count) + return NULL; + + for (x = 0; x < vl->vm_count; x++) { + if (!strcasecmp(vl->vm_states[x].v_name, name)) + return &vl->vm_states[x]; + } + + return NULL; +} + + +virt_state_t * +vl_find_uuid(virt_list_t *vl, const char *uuid) +{ + int x; + + if (!vl || !uuid || !vl->vm_count) + return NULL; + + for (x = 0; x < vl->vm_count; x++) { + if (!strcasecmp(vl->vm_states[x].v_uuid, uuid)) + return &vl->vm_states[x]; + } + + return NULL; +} + + +void +vl_free(virt_list_t *old) +{ + free(old); +} + + +static inline int +wait_domain(const char *vm_name, virConnectPtr vp, int timeout) +{ + int tries = 0; + int response = 1; + int ret; + virDomainPtr vdp; + virDomainInfo vdi; + int uuid_check; + + uuid_check = is_uuid(vm_name); + + if (uuid_check) { + vdp = virDomainLookupByUUIDString(vp, (const char *)vm_name); + } else { + vdp = virDomainLookupByName(vp, vm_name); + } + if (!vdp) + return 0; + + /* Check domain liveliness. If the domain is still here, + we return failure, and the client must then retry */ + /* XXX On the xen 3.0.4 API, we will be able to guarantee + synchronous virDomainDestroy, so this check will not + be necessary */ + do { + if (++tries > timeout) + break; + + sleep(1); + if (uuid_check) { + vdp = virDomainLookupByUUIDString(vp, (const char *)vm_name); + } else { + vdp = virDomainLookupByName(vp, vm_name); + } + if (!vdp) { + dbg_printf(2, "Domain no longer exists\n"); + response = 0; + break; + } + + memset(&vdi, 0, sizeof(vdi)); + ret = virDomainGetInfo(vdp, &vdi); + virDomainFree(vdp); + if (ret < 0) + continue; + + if (vdi.state == VIR_DOMAIN_SHUTOFF) { + dbg_printf(2, "Domain has been shut off\n"); + response = 0; + break; + } + + dbg_printf(4, "Domain still exists (state %d) after %d seconds\n", + vdi.state, tries); + } while (1); + + return response; +} + + +int +vm_off(virConnectPtr *vp, int vp_count, const char *vm_name) +{ + virDomainPtr vdp = NULL; + virDomainInfo vdi; + virDomainPtr (*virt_lookup_fn)(virConnectPtr, const char *); + int ret = -1; + int i; + + if (is_uuid(vm_name)) + virt_lookup_fn = virDomainLookupByUUIDString; + else + virt_lookup_fn = virDomainLookupByName; + + for (i = 0 ; i < vp_count ; i++) { + vdp = virt_lookup_fn(vp[i], vm_name); + if (vdp) + break; + } + + if (!vdp) { + dbg_printf(2, "[virt:OFF] Domain %s does not exist\n", vm_name); + return 1; + } + + if (virDomainGetInfo(vdp, &vdi) == 0 && vdi.state == VIR_DOMAIN_SHUTOFF) + { + dbg_printf(2, "[virt:OFF] Nothing to do - " + "domain %s is already off\n", + vm_name); + virDomainFree(vdp); + return 0; + } + + syslog(LOG_NOTICE, "Destroying domain %s\n", vm_name); + dbg_printf(2, "[virt:OFF] Calling virDomainDestroy for %s\n", vm_name); + + ret = virDomainDestroy(vdp); + virDomainFree(vdp); + + if (ret < 0) { + syslog(LOG_NOTICE, + "Failed to destroy domain %s: %d\n", vm_name, ret); + dbg_printf(2, "[virt:OFF] Failed to destroy domain: %s %d\n", + vm_name, ret); + return 1; + } + + if (ret) { + syslog(LOG_NOTICE, "Domain %s still exists; fencing failed\n", + vm_name); + dbg_printf(2, + "[virt:OFF] Domain %s still exists; fencing failed\n", + vm_name); + return 1; + } + + dbg_printf(2, "[virt:OFF] Success for %s\n", vm_name); + return 0; +} + + +int +vm_on(virConnectPtr *vp, int vp_count, const char *vm_name) +{ + virDomainPtr vdp = NULL; + virDomainInfo vdi; + virDomainPtr (*virt_lookup_fn)(virConnectPtr, const char *); + int ret = -1; + int i; + + if (is_uuid(vm_name)) + virt_lookup_fn = virDomainLookupByUUIDString; + else + virt_lookup_fn = virDomainLookupByName; + + for (i = 0 ; i < vp_count ; i++) { + vdp = virt_lookup_fn(vp[i], vm_name); + if (vdp) + break; + } + + if (!vdp) { + dbg_printf(2, "[virt:ON] Domain %s does not exist\n", vm_name); + return 1; + } + + if (virDomainGetInfo(vdp, &vdi) == 0 && vdi.state != VIR_DOMAIN_SHUTOFF) { + dbg_printf(2, "Nothing to do - domain %s is already running\n", + vm_name); + virDomainFree(vdp); + return 0; + } + + syslog(LOG_NOTICE, "Starting domain %s\n", vm_name); + dbg_printf(2, "[virt:ON] Calling virDomainCreate for %s\n", vm_name); + + ret = virDomainCreate(vdp); + virDomainFree(vdp); + + if (ret < 0) { + syslog(LOG_NOTICE, "Failed to start domain %s: %d\n", vm_name, ret); + dbg_printf(2, "[virt:ON] virDomainCreate() failed for %s: %d\n", + vm_name, ret); + return 1; + } + + if (ret) { + syslog(LOG_NOTICE, "Domain %s did not start\n", vm_name); + dbg_printf(2, "[virt:ON] Domain %s did not start\n", vm_name); + return 1; + } + + syslog(LOG_NOTICE, "Domain %s started\n", vm_name); + dbg_printf(2, "[virt:ON] Success for %s\n", vm_name); + return 0; +} + + +int +vm_status(virConnectPtr *vp, int vp_count, const char *vm_name) +{ + virDomainPtr vdp = NULL; + virDomainInfo vdi; + int ret = 0; + int i; + virDomainPtr (*virt_lookup_fn)(virConnectPtr, const char *); + + if (is_uuid(vm_name)) + virt_lookup_fn = virDomainLookupByUUIDString; + else + virt_lookup_fn = virDomainLookupByName; + + for (i = 0 ; i < vp_count ; i++) { + vdp = virt_lookup_fn(vp[i], vm_name); + if (vdp) + break; + } + + if (!vdp) { + dbg_printf(2, "[virt:STATUS] Unknown VM %s - return OFF\n", vm_name); + return RESP_OFF; + } + + if (virDomainGetInfo(vdp, &vdi) == 0 && vdi.state == VIR_DOMAIN_SHUTOFF) { + dbg_printf(2, "[virt:STATUS] VM %s is OFF\n", vm_name); + ret = RESP_OFF; + } + + if (vdp) + virDomainFree(vdp); + return ret; +} + + +int +vm_reboot(virConnectPtr *vp, int vp_count, const char *vm_name) +{ + virDomainPtr vdp = NULL, nvdp; + virDomainInfo vdi; + char *domain_desc; + virConnectPtr vcp = NULL; + virDomainPtr (*virt_lookup_fn)(virConnectPtr, const char *); + int ret; + int i; + + if (is_uuid(vm_name)) + virt_lookup_fn = virDomainLookupByUUIDString; + else + virt_lookup_fn = virDomainLookupByName; + + for (i = 0 ; i < vp_count ; i++) { + vdp = virt_lookup_fn(vp[i], vm_name); + if (vdp) { + vcp = vp[i]; + break; + } + } + + if (!vdp || !vcp) { + dbg_printf(2, + "[virt:REBOOT] Nothing to do - domain %s does not exist\n", + vm_name); + return 1; + } + + if (virDomainGetInfo(vdp, &vdi) == 0 && vdi.state == VIR_DOMAIN_SHUTOFF) { + dbg_printf(2, "[virt:REBOOT] Nothing to do - domain %s is off\n", + vm_name); + virDomainFree(vdp); + return 0; + } + + syslog(LOG_NOTICE, "Rebooting domain %s\n", vm_name); + dbg_printf(5, "[virt:REBOOT] Rebooting domain %s...\n", vm_name); + + domain_desc = virDomainGetXMLDesc(vdp, 0); + + if (!domain_desc) { + dbg_printf(5, "[virt:REBOOT] Failed getting domain description " + "from libvirt for %s...\n", vm_name); + } + + dbg_printf(2, "[virt:REBOOT] Calling virDomainDestroy(%p) for %s\n", + vdp, vm_name); + + ret = virDomainDestroy(vdp); + if (ret < 0) { + dbg_printf(2, + "[virt:REBOOT] virDomainDestroy() failed for %s: %d/%d\n", + vm_name, ret, errno); + + if (domain_desc) + free(domain_desc); + virDomainFree(vdp); + return 1; + } + + ret = wait_domain(vm_name, vcp, 15); + + if (ret) { + syslog(LOG_NOTICE, "Domain %s still exists; fencing failed\n", vm_name); + dbg_printf(2, + "[virt:REBOOT] Domain %s still exists; fencing failed\n", + vm_name); + + if (domain_desc) + free(domain_desc); + virDomainFree(vdp); + return 1; + } + + if (!domain_desc) + return 0; + + /* 'on' is not a failure */ + ret = 0; + + dbg_printf(3, "[[ XML Domain Info ]]\n"); + dbg_printf(3, "%s\n[[ XML END ]]\n", domain_desc); + + dbg_printf(2, "[virt:REBOOT] Calling virDomainCreateLinux() for %s\n", + vm_name); + + nvdp = virDomainCreateLinux(vcp, domain_desc, 0); + if (nvdp == NULL) { + /* More recent versions of libvirt or perhaps the + * KVM back-end do not let you create a domain from + * XML if there is already a defined domain description + * with the same name that it knows about. You must + * then call virDomainCreate() */ + dbg_printf(2, + "[virt:REBOOT] virDomainCreateLinux() failed for %s; " + "Trying virDomainCreate()\n", + vm_name); + + if (virDomainCreate(vdp) < 0) { + syslog(LOG_NOTICE, "Could not restart %s\n", vm_name); + dbg_printf(1, "[virt:REBOOT] Failed to recreate guest %s!\n", + vm_name); + } + } + + free(domain_desc); + virDomainFree(vdp); + return ret; +} diff --git a/agents/virt/server/virt.h b/agents/virt/server/virt.h new file mode 100644 index 0000000..1d9140c --- /dev/null +++ b/agents/virt/server/virt.h @@ -0,0 +1,62 @@ +/* + Copyright Red Hat, Inc. 2006-2017 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ + +#ifndef _VIRT_H +#define _VIRT_H + +#include <stdint.h> +#include <netinet/in.h> +#include <libvirt/libvirt.h> + +#include "xvm.h" + +typedef struct { + uint32_t s_owner; + int32_t s_state; +} vm_state_t; + +typedef struct { + char v_name[MAX_DOMAINNAME_LENGTH + 1]; + char v_uuid[MAX_DOMAINNAME_LENGTH + 1]; + vm_state_t v_state; +} virt_state_t; + +/** + This is stored in our private checkpoint section. + */ +typedef struct _virt_list { + uint32_t vm_count; + virt_state_t vm_states[0]; +} virt_list_t; + +virt_list_t *vl_get(virConnectPtr *vp, int vp_count, int my_id); +void vl_print(virt_list_t *vl); +void vl_free(virt_list_t *old); +virt_state_t *vl_find_uuid(virt_list_t *vl, const char *name); +virt_state_t *vl_find_name(virt_list_t *vl, const char *name); +int vl_add(virt_list_t **vl, virt_state_t *vm); +int vl_update(virt_list_t **vl, virt_state_t *vm); +int vl_remove_by_owner(virt_list_t **vl, uint32_t owner); + +int vm_off(virConnectPtr *vp, int vp_count, const char *vm_name); +int vm_on(virConnectPtr *vp, int vp_count, const char *vm_name); +int vm_status(virConnectPtr *vp, int vp_count, const char *vm_name); +int vm_reboot(virConnectPtr *vp, int vp_count, const char *vm_name); + +#endif diff --git a/agents/virt/server/vsock.c b/agents/virt/server/vsock.c new file mode 100644 index 0000000..1b88fa5 --- /dev/null +++ b/agents/virt/server/vsock.c @@ -0,0 +1,565 @@ +/* + Copyright Red Hat, Inc. 2017 + + This program 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 2, or (at your option) any + later version. + + This program 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 this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, + MA 02139, USA. +*/ +#include "config.h" + +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <nss.h> +#include <sys/socket.h> +#include <linux/vm_sockets.h> + +/* Local includes */ +#include "list.h" +#include "simpleconfig.h" +#include "static_map.h" +#include "server_plugin.h" +#include "history.h" +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "mcast.h" +#include "tcp.h" +#include "tcp_listener.h" +#include "debug.h" +#include "fdops.h" + +#define NAME "vsock" +#define VSOCK_VERSION "0.2" + +#define VSOCK_MAGIC 0xa32d27c1e + +#define VALIDATE(info) \ +do {\ + if (!info || info->magic != VSOCK_MAGIC)\ + return -EINVAL;\ +} while(0) + +typedef struct _vsock_options { + char *key_file; + int cid; + unsigned int port; + unsigned int hash; + unsigned int auth; + unsigned int flags; +} vsock_options; + + +typedef struct _vsock_info { + uint64_t magic; + void *priv; + map_object_t *map; + history_info_t *history; + char key[MAX_KEY_LEN]; + vsock_options args; + const fence_callbacks_t *cb; + ssize_t key_len; + int listen_sock; +} vsock_info; + + +struct vsock_hostlist_arg { + map_object_t *map; + int cid; + int fd; +}; + + +static int get_peer_cid(int fd, uint32_t *peer_cid) { + struct sockaddr_vm svm; + socklen_t len; + int ret; + + if (!peer_cid) + return -1; + + len = sizeof(svm); + ret = getpeername(fd, (struct sockaddr *) &svm, &len); + if (ret < 0) { + printf("Error getting peer CID: %s\n", strerror(errno)); + return -1; + } + + *peer_cid = svm.svm_cid; + return 0; +} + + +/* + * See if we fenced this node recently (successfully) + * If so, ignore the request for a few seconds. + * + * We purge our history when the entries time out. + */ +static int +check_history(void *a, void *b) { + fence_req_t *old = a, *current = b; + + if (old->request == current->request && + old->seqno == current->seqno && + !strcasecmp((const char *)old->domain, + (const char *)current->domain)) { + return 1; + } + return 0; +} + + +static int +vsock_hostlist(const char *vm_name, const char *vm_uuid, + int state, void *priv) +{ + struct vsock_hostlist_arg *arg = (struct vsock_hostlist_arg *) priv; + host_state_t hinfo; + struct timeval tv; + int ret; + uint32_t peer_cid = 0; + char peer_cid_str[24]; + + ret = get_peer_cid(arg->fd, &peer_cid); + if (ret < 0) { + printf("Unable to get peer CID: %s\n", strerror(errno)); + peer_cid_str[0] = '\0'; + } else + snprintf(peer_cid_str, sizeof(peer_cid_str), "%u", peer_cid); + + /* Noops if auth == AUTH_NONE */ + + if (map_check2(arg->map, peer_cid_str, vm_uuid, vm_name) == 0) { + /* if we don't have access to fence this VM, + * we should not see it in a hostlist either */ + return 0; + } + + strncpy((char *)hinfo.domain, vm_name, sizeof(hinfo.domain) - 1); + strncpy((char *)hinfo.uuid, vm_uuid, sizeof(hinfo.uuid) - 1); + hinfo.state = state; + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(arg->fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +vsock_hostlist_begin(int fd) +{ + struct timeval tv; + char val = (char) RESP_HOSTLIST; + + tv.tv_sec = 1; + tv.tv_usec = 0; + return _write_retry(fd, &val, 1, &tv); +} + + +static int +vsock_hostlist_end(int fd) +{ + host_state_t hinfo; + struct timeval tv; + int ret; + + printf("Sending terminator packet\n"); + + memset(&hinfo, 0, sizeof(hinfo)); + + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = _write_retry(fd, &hinfo, sizeof(hinfo), &tv); + if (ret == sizeof(hinfo)) + return 0; + return 1; +} + + +static int +do_fence_request_vsock(int fd, fence_req_t *req, vsock_info *info) +{ + char response = 1; + struct vsock_hostlist_arg arg; + uint32_t peer_cid = 0; + char peer_cid_str[24]; + int ret; + + ret = get_peer_cid(fd, &peer_cid); + if (ret < 0) { + printf("Unable to get peer CID: %s\n", strerror(errno)); + return -1; + } + + snprintf(peer_cid_str, sizeof(peer_cid_str), "%u", peer_cid); + + /* Noops if auth == AUTH_NONE */ + if (sock_response(fd, info->args.auth, info->key, info->key_len, 10) <= 0) { + printf("CID %u Failed to respond to challenge\n", peer_cid); + close(fd); + return -1; + } + + ret = sock_challenge(fd, info->args.auth, info->key, info->key_len, 10); + if (ret <= 0) { + printf("Remote CID %u failed challenge\n", peer_cid); + close(fd); + return -1; + } + + dbg_printf(2, "Request %d seqno %d target %s from CID %u\n", + req->request, req->seqno, req->domain, peer_cid); + + switch(req->request) { + case FENCE_NULL: + response = info->cb->null((char *)req->domain, info->priv); + break; + case FENCE_ON: + if (map_check(info->map, peer_cid_str, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->on((char *)req->domain, peer_cid_str, + req->seqno, info->priv); + break; + case FENCE_OFF: + if (map_check(info->map, peer_cid_str, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->off((char *)req->domain, peer_cid_str, + req->seqno, info->priv); + break; + case FENCE_REBOOT: + if (map_check(info->map, peer_cid_str, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->reboot((char *)req->domain, peer_cid_str, + req->seqno, info->priv); + break; + case FENCE_STATUS: + if (map_check(info->map, peer_cid_str, + (const char *)req->domain) == 0) { + response = RESP_PERM; + break; + } + response = info->cb->status((char *)req->domain, info->priv); + break; + case FENCE_DEVSTATUS: + response = info->cb->devstatus(info->priv); + break; + case FENCE_HOSTLIST: + arg.map = info->map; + arg.fd = fd; + + vsock_hostlist_begin(arg.fd); + response = info->cb->hostlist(vsock_hostlist, &arg, info->priv); + vsock_hostlist_end(arg.fd); + break; + } + + dbg_printf(3, "Sending response to caller CID %u...\n", peer_cid); + if (_write_retry(fd, &response, 1, NULL) < 0) + perror("write"); + + history_record(info->history, req); + + if (fd != -1) + close(fd); + + return 1; +} + + +static int +vsock_dispatch(listener_context_t c, struct timeval *timeout) +{ + vsock_info *info; + fence_req_t data; + fd_set rfds; + int n; + int client_fd; + int ret; + struct timeval tv; + + if (timeout != NULL) + memcpy(&tv, timeout, sizeof(tv)); + else { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + + info = (vsock_info *) c; + VALIDATE(info); + + FD_ZERO(&rfds); + FD_SET(info->listen_sock, &rfds); + + n = select(info->listen_sock + 1, &rfds, NULL, NULL, timeout); + if (n <= 0) { + if (errno == EINTR || errno == EAGAIN) + n = 0; + else + dbg_printf(2, "select: %s\n", strerror(errno)); + return n; + } + + + client_fd = accept(info->listen_sock, NULL, NULL); + if (client_fd < 0) { + perror("accept"); + return -1; + } + + dbg_printf(3, "Accepted vsock client...\n"); + + ret = _read_retry(client_fd, &data, sizeof(data), &tv); + if (ret != sizeof(data)) { + dbg_printf(3, "Invalid request (read %d bytes)\n", ret); + close(client_fd); + return 0; + } + + swab_fence_req_t(&data); + + if (!verify_request(&data, info->args.hash, info->key, info->key_len)) { + printf("Key mismatch; dropping client\n"); + close(client_fd); + return 0; + } + + dbg_printf(3, "Request %d seqno %d domain %s\n", + data.request, data.seqno, data.domain); + + if (history_check(info->history, &data) == 1) { + printf("We just did this request; dropping client\n"); + close(client_fd); + return 0; + } + + switch(info->args.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + printf("VSOCK request\n"); + do_fence_request_vsock(client_fd, &data, info); + break; + default: + printf("XXX Unhandled authentication\n"); + } + + return 0; +} + + +static int +vsock_config(config_object_t *config, vsock_options *args) +{ + char value[1024]; + int errors = 0; + + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value))==0) + dset(atoi(value)); + + if (sc_get(config, "listeners/vsock/@key_file", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for key_file\n", value); + args->key_file = strdup(value); + } else { + args->key_file = strdup(DEFAULT_KEY_FILE); + if (!args->key_file) { + dbg_printf(1, "Failed to allocate memory\n"); + return -1; + } + } + + args->hash = DEFAULT_HASH; + if (sc_get(config, "listeners/vsock/@hash", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for hash\n", value); + if (!strcasecmp(value, "none")) { + args->hash = HASH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->hash = HASH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->hash = HASH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->hash = HASH_SHA512; + } else { + dbg_printf(1, "Unsupported hash: %s\n", value); + ++errors; + } + } + + args->auth = DEFAULT_AUTH; + if (sc_get(config, "listeners/vsock/@auth", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for auth\n", value); + if (!strcasecmp(value, "none")) { + args->hash = AUTH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->hash = AUTH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->hash = AUTH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->hash = AUTH_SHA512; + } else { + dbg_printf(1, "Unsupported auth: %s\n", value); + ++errors; + } + } + + args->port = DEFAULT_MCAST_PORT; + if (sc_get(config, "listeners/vsock/@port", + value, sizeof(value)-1) == 0) { + dbg_printf(1, "Got %s for port\n", value); + args->port = atoi(value); + if (args->port <= 0) { + dbg_printf(1, "Invalid port: %s\n", value); + ++errors; + } + } + + return errors; +} + + +static int +vsock_init(listener_context_t *c, const fence_callbacks_t *cb, + config_object_t *config, map_object_t *map, void *priv) +{ + vsock_info *info; + int listen_sock, ret; + struct sockaddr_vm svm; + + if (NSS_NoDB_Init(NULL) != SECSuccess) { + printf("Could not initialize NSS\n"); + return 1; + } + + info = calloc(1, sizeof(*info)); + if (!info) + return -1; + + info->priv = priv; + info->cb = cb; + info->map = map; + + ret = vsock_config(config, &info->args); + if (ret < 0) + perror("vsock_config"); + else if (ret > 0) + printf("%d errors found during vsock listener configuration\n", ret); + + if (ret != 0) { + if (info->args.key_file) + free(info->args.key_file); + free(info); + return -1; + } + + if (info->args.auth != AUTH_NONE || info->args.hash != HASH_NONE) { + info->key_len = read_key_file(info->args.key_file, + info->key, sizeof(info->key)); + if (info->key_len < 0) { + printf("Could not read %s; operating without " + "authentication\n", info->args.key_file); + info->args.auth = AUTH_NONE; + info->args.hash = HASH_NONE; + info->key_len = 0; + } + } + + listen_sock = socket(PF_VSOCK, SOCK_STREAM, 0); + if (listen_sock < 0) + goto out_fail; + + memset(&svm, 0, sizeof(svm)); + svm.svm_family = AF_VSOCK; + svm.svm_cid = VMADDR_CID_ANY; + svm.svm_port = info->args.port; + + if (bind(listen_sock, (struct sockaddr *) &svm, sizeof(svm)) < 0) + goto out_fail; + + if (listen(listen_sock, 1) < 0) + goto out_fail; + + info->magic = VSOCK_MAGIC; + info->listen_sock = listen_sock; + info->history = history_init(check_history, 10, sizeof(fence_req_t)); + *c = (listener_context_t)info; + return 0; + +out_fail: + printf("Could not set up listen socket: %s\n", strerror(errno)); + if (listen_sock >= 0) + close(listen_sock); + if (info->args.key_file) + free(info->args.key_file); + free(info); + return -1; +} + + +static int +vsock_shutdown(listener_context_t c) +{ + vsock_info *info = (vsock_info *)c; + + VALIDATE(info); + info->magic = 0; + history_wipe(info->history); + free(info->history); + free(info->args.key_file); + close(info->listen_sock); + free(info); + + return 0; +} + + +static listener_plugin_t vsock_plugin = { + .name = NAME, + .version = VSOCK_VERSION, + .init = vsock_init, + .dispatch = vsock_dispatch, + .cleanup = vsock_shutdown, +}; + +double +LISTENER_VER_SYM(void) +{ + return PLUGIN_VERSION_LISTENER; +} + +const listener_plugin_t * +LISTENER_INFO_SYM(void) +{ + return &vsock_plugin; +} |