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 | |
parent | Initial commit. (diff) | |
download | fence-agents-upstream/4.12.1.tar.xz fence-agents-upstream/4.12.1.zip |
Adding upstream version 4.12.1.upstream/4.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'agents/virt')
71 files changed, 14249 insertions, 0 deletions
diff --git a/agents/virt/Makefile.am b/agents/virt/Makefile.am new file mode 100644 index 0000000..4f50c60 --- /dev/null +++ b/agents/virt/Makefile.am @@ -0,0 +1,31 @@ +############################################################################### +############################################################################### +## +## 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 + +EXTRA_DIST = docs/architecture.txt docs/fence_virt.txt \ + docs/README docs/TODO include \ + fence_virtd.service.in + +SUBDIRS = config common client server man + +all-local: fence_virtd.service + +clean-local: + rm -f $(SPEC) fence_virtd.service + +fence_virtd.service: fence_virtd.service.in + SBINDIR="@sbindir@"; \ + INITCONFDIR="@initconfdir@"; \ + cat $^ > $@ ; \ + echo "EnvironmentFile=-$$INITCONFDIR/fence_virtd" >> $@ ;\ + echo "ExecStart=$$SBINDIR/fence_virtd \$$FENCE_VIRTD_ARGS" >> $@ diff --git a/agents/virt/client/Makefile.am b/agents/virt/client/Makefile.am new file mode 100644 index 0000000..4a1997b --- /dev/null +++ b/agents/virt/client/Makefile.am @@ -0,0 +1,40 @@ +############################################################################### +############################################################################### +## +## Copyright (C) 2009-2019 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 + +sbin_PROGRAMS = fence_virt + +TARGET = $(sbin_PROGRAMS) + +fence_virt_SOURCES = mcast.c serial.c main.c options.c tcp.c vsock.c + +fence_virt_CFLAGS = $(VIRT_AM_CFLAGS) $(nss_CFLAGS) $(xml2_CFLAGS) $(PTHREAD_CFLAGS) $(AM_CFLAGS) + +fence_virt_LDFLAGS = $(VIRT_AM_LDFLAGS) $(COMMON_LDFLAGS) +fence_virt_LDADD = $(VIRT_COMMON_LIBS) $(nss_LIBS) $(xml2_LIBS) $(PTHREAD_LIBS) + +if xvmcompat +install-exec-hook: fence_virt + (cd $(DESTDIR)/${sbindir}; $(LN_S) -nf $^ fence_xvm) + +uninstall-hook: + (cd $(DESTDIR)/${sbindir}; rm -f fence_xvm) +endif + +fence_virt.delay-check: fence_virt + $(eval INPUT=$(subst .delay-check,,$@)) + test `/usr/bin/time -p ./$(INPUT) -w 10 -n test $(FENCE_TEST_ARGS) -- 2>&1 |\ + awk -F"[. ]" -vOFS= '/real/ {print $$2,$$3}' | tail -n 1` -ge 1000 || \ + /usr/bin/time -p ./$(INPUT) -w 0 -n test $(FENCE_TEST_ARGS) -- + +include $(top_srcdir)/make/agentccheck.mk diff --git a/agents/virt/client/main.c b/agents/virt/client/main.c new file mode 100644 index 0000000..188c05d --- /dev/null +++ b/agents/virt/client/main.c @@ -0,0 +1,198 @@ +/* + 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 <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <errno.h> +#include <pthread.h> +#include <libgen.h> +#include <syslog.h> + +/* Local includes */ +#include "xvm.h" +#include "options.h" +#include "debug.h" +#include "client.h" + + +int +main(int argc, char **argv) +{ + fence_virt_args_t args; + const char *my_options; + int ret = 0; + + args_init(&args); + if (!strcmp(basename(argv[0]), "fence_xvm")) { + my_options = "di:a:p:r:C:c:k:M:n:H:uo:t:?hVw:"; + args.mode = MODE_MULTICAST; + } else { + my_options = "dD:P:A:p:M:n:H:o:t:?hVT:S::C:c:k:w:"; + args.mode = MODE_SERIAL; + } + + if (argc == 1) { + args_get_stdin(my_options, &args); + } else { + args_get_getopt(argc, argv, my_options, &args); + } + + if (args.flags & F_HELP) { + args_usage(argv[0], my_options, 0); + + printf("With no command line argument, arguments are " + "read from standard input.\n"); + printf("Arguments read from standard input take " + "the form of:\n\n"); + printf(" arg1=value1\n"); + printf(" arg2=value2\n\n"); + + args_usage(argv[0], my_options, 1); + exit(0); + } + + if (args.flags & F_VERSION) { + printf("%s %s\n", basename(argv[0]), XVM_VERSION); +#ifdef VERSION + printf("fence release %s\n", VERSION); +#endif + exit(0); + } + + openlog(basename(argv[0]), LOG_NDELAY | LOG_PID, LOG_DAEMON); + + args_finalize(&args); + dset(args.debug); + + if (args.debug > 0) + args_print(&args); + + /* Additional validation here */ + if (!args.domain && (args.op != FENCE_DEVSTATUS && + args.op != FENCE_HOSTLIST && + args.op != FENCE_METADATA)) { + printf("No domain specified!\n"); + syslog(LOG_NOTICE, "No domain specified"); + args.flags |= F_ERR; + } + + if (args.net.ipaddr) + args.mode = MODE_TCP; + + if (args.net.cid >= 2) + args.mode = MODE_VSOCK; + + if (args.flags & F_ERR) { + if (args.op != FENCE_VALIDATEALL) + args_usage(argv[0], my_options, (argc == 1)); + exit(1); + } + + if (args.op == FENCE_VALIDATEALL) + exit(0); + + if (args.op == FENCE_METADATA) { + args_metadata(argv[0], my_options); + exit(0); + } + + if (args.delay > 0 && + args.op != FENCE_STATUS && + args.op != FENCE_DEVSTATUS && + args.op != FENCE_HOSTLIST) + sleep(args.delay); + + switch(args.mode) { + case MODE_MULTICAST: + ret = mcast_fence_virt(&args); + break; + case MODE_SERIAL: + ret = serial_fence_virt(&args); + break; + case MODE_TCP: + ret = tcp_fence_virt(&args); + break; + case MODE_VSOCK: + ret = vsock_fence_virt(&args); + break; + default: + ret = 1; + goto out; + } + + switch(ret) { + case RESP_OFF: + if (args.op == FENCE_STATUS) + printf("Status: OFF\n"); + else if (args.domain) + syslog(LOG_NOTICE, "Domain \"%s\" is OFF", args.domain); + break; + case 0: + if (args.op == FENCE_STATUS) + printf("Status: ON\n"); + else if (args.domain) + syslog(LOG_NOTICE, "Domain \"%s\" is ON", args.domain); + break; + case RESP_FAIL: + if (args.domain) { + syslog(LOG_NOTICE, "Fence operation failed for domain \"%s\"", + args.domain); + } else + syslog(LOG_NOTICE, "Fence operation failed"); + printf("Operation failed\n"); + break; + case RESP_PERM: + if (args.domain) { + syslog(LOG_NOTICE, + "Permission denied for Fence operation for domain \"%s\"", + args.domain); + } else + syslog(LOG_NOTICE, "Permission denied for fence operation"); + printf("Permission denied\n"); + break; + default: + if (args.domain) { + syslog(LOG_NOTICE, "Unknown response (%d) for domain \"%s\"", + ret, args.domain); + } else + syslog(LOG_NOTICE, "Unknown response (%d)", ret); + printf("Unknown response (%d)\n", ret); + break; + } + +out: + closelog(); + exit(ret); +} diff --git a/agents/virt/client/mcast.c b/agents/virt/client/mcast.c new file mode 100644 index 0000000..42f0a6b --- /dev/null +++ b/agents/virt/client/mcast.c @@ -0,0 +1,393 @@ +/* + 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 <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 <libgen.h> +#include <nss.h> + +/* Local includes */ +#include "xvm.h" +#include "ip_lookup.h" +#include "simple_auth.h" +#include "options.h" +#include "tcp.h" +#include "mcast.h" +#include "debug.h" +#include "fdops.h" +#include "client.h" + + +static int +tcp_wait_connect(int lfd, int retry_tenths) +{ + int fd; + fd_set rfds; + int n; + struct timeval tv; + + dbg_printf(3, "Waiting for connection from XVM host daemon.\n"); + FD_ZERO(&rfds); + FD_SET(lfd, &rfds); + tv.tv_sec = retry_tenths / 10; + tv.tv_usec = (retry_tenths % 10) * 100000; + + n = select(lfd + 1, &rfds, NULL, NULL, &tv); + if (n == 0) { + errno = ETIMEDOUT; + return -1; + } else if (n < 0) { + return -1; + } + + fd = accept(lfd, NULL, 0); + if (fd < 0) + return -1; + + return fd; +} + + +void +do_read_hostlist(int fd, int timeout) +{ + host_state_t hinfo; + fd_set rfds; + struct timeval tv; + int ret; + + do { + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + ret = _select_retry(fd+1, &rfds, NULL, NULL, &tv); + if (ret == 0) { + printf("Timed out!\n"); + break; + } + + ret = _read_retry(fd, &hinfo, sizeof(hinfo), &tv); + if (ret < sizeof(hinfo)) { + printf("Bad read!\n"); + break; + } + + if (strlen((char *)hinfo.uuid) == 0 && + strlen((char *)hinfo.domain) == 0) + break; + + printf("%-32s %s %s\n", hinfo.domain, hinfo.uuid, + (hinfo.state == 1) ? "on" : "off"); + } while (1); +} + + +static int +tcp_exchange(int fd, fence_auth_type_t auth, void *key, + size_t key_len, int timeout) +{ + fd_set rfds; + struct timeval tv; + char ret = 1; + + /* Ok, we're connected */ + dbg_printf(3, "Issuing TCP challenge\n"); + if (sock_challenge(fd, auth, key, key_len, timeout) <= 0) { + /* Challenge failed */ + printf("Invalid response to challenge\n"); + return 1; + } + + /* Now they'll send us one, so we need to respond here */ + dbg_printf(3, "Responding to TCP challenge\n"); + if (sock_response(fd, auth, key, key_len, timeout) <= 0) { + printf("Invalid response to challenge\n"); + return 1; + } + + dbg_printf(2, "TCP Exchange + Authentication done... \n"); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + ret = 1; + dbg_printf(3, "Waiting for return value from XVM host\n"); + if (_select_retry(fd + 1, &rfds, NULL, NULL, &tv) <= 0) + return -1; + + /* Read return code */ + if (_read_retry(fd, &ret, 1, &tv) < 0) + ret = 1; + + if (ret == (char)RESP_HOSTLIST) /* hostlist */ { + do_read_hostlist(fd, timeout); + ret = 0; + } + + return ret; +} + + +static int +send_multicast_packets(ip_list_t *ipl, fence_virt_args_t *args, + uint32_t seqno, void *key, size_t key_len) +{ + fence_req_t freq; + int mc_sock; + ip_addr_t *ipa; + struct sockaddr_in tgt4; + struct sockaddr_in6 tgt6; + struct sockaddr *tgt; + socklen_t tgt_len; + + for (ipa = ipl->tqh_first; ipa; ipa = ipa->ipa_entries.tqe_next) { + + if (ipa->ipa_family != args->net.family) { + dbg_printf(2, "Ignoring %s: wrong family\n", ipa->ipa_address); + continue; + } + + if (args->net.family == PF_INET) { + mc_sock = ipv4_send_sk(ipa->ipa_address, args->net.addr, + args->net.port, + (struct sockaddr *)&tgt4, + sizeof(struct sockaddr_in)); + tgt = (struct sockaddr *)&tgt4; + tgt_len = sizeof(tgt4); + } else if (args->net.family == PF_INET6) { + mc_sock = ipv6_send_sk(ipa->ipa_address, args->net.addr, + args->net.port, + (struct sockaddr *)&tgt6, + sizeof(struct sockaddr_in6)); + tgt = (struct sockaddr *)&tgt6; + tgt_len = sizeof(tgt6); + } else { + dbg_printf(2, "Unsupported family %d\n", args->net.family); + return -1; + } + + if (mc_sock < 0) + continue; + + /* Build our packet */ + memset(&freq, 0, sizeof(freq)); + if (args->domain && strlen((char *)args->domain)) { + strncpy((char *)freq.domain, args->domain, + sizeof(freq.domain) - 1); + } + freq.request = args->op; + freq.hashtype = args->net.hash; + freq.seqno = seqno; + + /* Store source address */ + if (ipa->ipa_family == PF_INET) { + freq.addrlen = sizeof(struct in_addr); + /* XXX Swap order for in_addr ? XXX */ + if (inet_pton(PF_INET, ipa->ipa_address, freq.address) != 1) { + dbg_printf(2, "Unable to convert address\n"); + close(mc_sock); + return -1; + } + } else if (ipa->ipa_family == PF_INET6) { + freq.addrlen = sizeof(struct in6_addr); + if (inet_pton(PF_INET6, ipa->ipa_address, freq.address) != 1) { + dbg_printf(2, "Unable to convert address\n"); + close(mc_sock); + return -1; + } + } + + freq.flags = 0; + if (args->flags & F_USE_UUID) + freq.flags |= RF_UUID; + freq.family = ipa->ipa_family; + freq.port = args->net.port; + + sign_request(&freq, key, key_len); + + dbg_printf(3, "Sending to %s via %s\n", args->net.addr, + ipa->ipa_address); + + if(sendto(mc_sock, &freq, sizeof(freq), 0, + (struct sockaddr *)tgt, tgt_len) < 0) { + dbg_printf(3, "Unable to send packet to %s via %s\n", + args->net.addr, ipa->ipa_address); + } + + close(mc_sock); + } + + return 0; +} + + +/* TODO: Clean this up!!! */ +int +mcast_fence_virt(fence_virt_args_t *args) +{ + ip_list_t ipl; + char key[MAX_KEY_LEN]; + struct timeval tv; + int lfd = -1, key_len = 0, fd, ret; + int attempts = 0; + uint32_t seqno; + + /* 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; + } + + if (args->net.auth != AUTH_NONE || args->net.hash != HASH_NONE) { + key_len = read_key_file(args->net.key_file, key, sizeof(key)); + if (key_len < 0) { + printf("Could not read %s; trying without " + "authentication\n", args->net.key_file); + args->net.auth = AUTH_NONE; + args->net.hash = HASH_NONE; + key_len = 0; + } + } + + /* Do the real work */ + if (ip_build_list(&ipl) < 0) { + printf("Error building IP address list\n"); + return 1; + } + + attempts = args->timeout * 10 / args->retr_time; + + listen_loop: + do { + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + if (args->net.family == PF_INET) { + lfd = ipv4_listen(NULL, args->net.port, 10); + } else { + lfd = ipv6_listen(NULL, args->net.port, 10); + } + break; + /*case AUTH_X509:*/ + /* XXX Setup SSL listener socket here */ + default: + return 1; + } + + if (lfd < 0) { + printf("Failed to listen: %s\n", strerror(errno)); + usleep(args->retr_time * 100000); + if (--attempts > 0) + goto listen_loop; + } + } while (0); + + if (lfd < 0) + return -1; + + gettimeofday(&tv, NULL); + seqno = (uint32_t)tv.tv_usec; + + do { + if (send_multicast_packets(&ipl, args, seqno, + key, key_len)) { + close(lfd); + return -1; + } + + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + fd = tcp_wait_connect(lfd, args->retr_time); + if (fd < 0 && (errno == ETIMEDOUT || + errno == EINTR)) + continue; + break; + /* case AUTH_X509: + ... = ssl_wait_connect... */ + break; + default: + close(lfd); + return 1; + } + + break; + } while (--attempts); + + if (lfd >= 0) + close(lfd); + + if (fd < 0) { + if (attempts <= 0) { + printf("Timed out waiting for response\n"); + return 1; + } + printf("Operation failed: %s\n", strerror(errno)); + return -1; + } + + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + ret = tcp_exchange(fd, args->net.auth, key, key_len, + args->timeout); + break; + /* case AUTH_X509: + return ssl_exchange(...); */ + default: + ret = 1; + break; + } + + close(fd); + return ret; +} diff --git a/agents/virt/client/options.c b/agents/virt/client/options.c new file mode 100644 index 0000000..ddd6bc4 --- /dev/null +++ b/agents/virt/client/options.c @@ -0,0 +1,1000 @@ +/* + 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. +*/ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.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 <libgen.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "mcast.h" +#include "tcp_listener.h" +#include "options.h" + +#define SCHEMA_COMPAT '\xfe' + + +/* Assignment functions */ + +static inline void +assign_debug(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) { + /* GNU getopt sets optarg to NULL for options w/o a param + We rely on this here... */ + args->debug++; + return; + } + + args->debug = atoi(value); + if (args->debug < 0) { + args->debug = 1; + } +} + + +static inline void +assign_family(fence_virt_args_t *args, struct arg_info *arg, + char *value) +{ + if (!value) + return; + + if (!strcasecmp(value, "ipv4")) { + args->net.family = PF_INET; + } else if (!strcasecmp(value, "ipv6")) { + args->net.family = PF_INET6; + } else if (!strcasecmp(value, "auto")) { + args->net.family = 0; + } else { + printf("Unsupported family: '%s'\n", value); + args->flags |= F_ERR; + } +} + + +static inline void +assign_address(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (args->net.addr) + free(args->net.addr); + args->net.addr = strdup(value); +} + +static inline void +assign_ip_address(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (args->net.ipaddr) + free(args->net.ipaddr); + args->net.ipaddr = strdup(value); +} + +static inline void +assign_channel_address(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (args->serial.address) + free(args->serial.address); + args->serial.address = strdup(value); +} + + +static inline void +assign_port(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + char *p; + int ret; + + if (!value) + return; + + ret = strtol(value, &p, 0); + if (ret <= 0 || ret >= 65536 || *p != '\0') { + printf("Invalid port: '%s'\n", value); + args->flags |= F_ERR; + } else + args->net.port = ret; +} + +static inline void +assign_cid(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + char *p; + unsigned long ret; + + if (!value) { + args->net.cid = 2; + return; + } + + ret = strtoul(value, &p, 0); + if (!p || *p != '\0' || ret < 2 || ret >= 0xffffffff) { + printf("Invalid CID: '%s'\n", value); + args->flags |= F_ERR; + } else + args->net.cid = ret; +} + +static inline void +assign_interface(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + int ret; + + if (!value) + return; + + ret = if_nametoindex(value); + if (ret <= 0) { + printf("Invalid interface: %s: %s\n", value, strerror(errno)); + args->net.ifindex = 0; + } + + args->net.ifindex = ret; +} + + +static inline void +assign_retrans(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + char *p; + int ret; + + if (!value) + return; + + ret = strtol(value, &p, 0); + if (ret <= 0 || *p != '\0') { + printf("Invalid retransmit time: '%s'\n", value); + args->flags |= F_ERR; + } else + args->retr_time = ret; +} + +static inline void +assign_hash(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (!strcasecmp(value, "none")) { + args->net.hash = HASH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->net.hash = HASH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->net.hash = HASH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->net.hash = HASH_SHA512; + } else { + printf("Unsupported hash: %s\n", value); + args->flags |= F_ERR; + } +} + + +static inline void +assign_auth(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (!strcasecmp(value, "none")) { + args->net.auth = AUTH_NONE; + } else if (!strcasecmp(value, "sha1")) { + args->net.auth = AUTH_SHA1; + } else if (!strcasecmp(value, "sha256")) { + args->net.auth = AUTH_SHA256; + } else if (!strcasecmp(value, "sha512")) { + args->net.auth = AUTH_SHA512; + } else { + printf("Unsupported auth type: %s\n", value); + args->flags |= F_ERR; + } +} + +static inline void +assign_key(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + struct stat st; + + if (!value) + return; + + if (args->net.key_file) + free(args->net.key_file); + args->net.key_file = strdup(value); + + if (stat(value, &st) == -1) { + printf("Invalid key file: '%s' (%s)\n", value, + strerror(errno)); + args->flags |= F_ERR; + } +} + + +static inline void +assign_op(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + if (!strcasecmp(value, "null")) { + args->op = FENCE_NULL; + } else if (!strcasecmp(value, "on")) { + args->op = FENCE_ON; + } else if (!strcasecmp(value, "off")) { + args->op = FENCE_OFF; + } else if (!strcasecmp(value, "reboot")) { + args->op = FENCE_REBOOT; + } else if (!strcasecmp(value, "status")) { + args->op = FENCE_STATUS; + } else if (!strcasecmp(value, "monitor")) { + args->op = FENCE_DEVSTATUS; + } else if (!strcasecmp(value, "list") || !strcasecmp(value, "list-status")) { + args->op = FENCE_HOSTLIST; + } else if (!strcasecmp(value, "metadata")) { + args->op = FENCE_METADATA; + } else if (!strcasecmp(value, "validate-all")) { + args->op = FENCE_VALIDATEALL; + } else { + printf("Unsupported operation: %s\n", value); + args->flags |= F_ERR; + } +} + + +static inline void +assign_device(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + args->serial.device = strdup(value); +} + + +static inline void +assign_params(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) + return; + + args->serial.speed = strdup(value); +} + + +static inline void +assign_domain(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (args->domain) { + printf("Domain/UUID may not be specified more than once\n"); + args->flags |= F_ERR; + return; + } + + if (!value) + return; + + args->domain = strdup(value); + + if (strlen(value) <= 0) { + printf("Invalid domain name\n"); + args->flags |= F_ERR; + } + + if (strlen(value) >= MAX_DOMAINNAME_LENGTH) { + errno = ENAMETOOLONG; + printf("Invalid domain name: '%s' (%s)\n", + value, strerror(errno)); + args->flags |= F_ERR; + } +} + + +static inline void +assign_uuid_lookup(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + if (!value) { + /* GNU getopt sets optarg to NULL for options w/o a param + We rely on this here... */ + args->flags |= F_USE_UUID; + return; + } + + args->flags |= ( !!atoi(value) ? F_USE_UUID : 0); +} + + +static inline void +assign_timeout(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + char *p; + int ret; + + if (!value) + return; + + ret = strtol(value, &p, 0); + if (ret <= 0 || *p != '\0') { + printf("Invalid timeout: '%s'\n", value); + args->flags |= F_ERR; + } else + args->timeout = ret; +} + +static inline void +assign_delay(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + char *p; + int ret; + + if (!value) + return; + + ret = strtol(value, &p, 0); + if (ret < 0 || *p != '\0') { + printf("Invalid delay: '%s'\n", value); + args->flags |= F_ERR; + } else + args->delay = ret; +} + +static inline void +assign_help(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + args->flags |= F_HELP; +} + + +static inline void +assign_version(fence_virt_args_t *args, struct arg_info *arg, char *value) +{ + args->flags |= F_VERSION; +} + + +static void +print_desc_xml(const char *desc) +{ + const char *d; + + for (d = desc; *d; d++) { + switch (*d) { + case '<': + printf("<"); + break; + case '>': + printf(">"); + break; + default: + printf("%c", *d); + } + } +} + + +/** ALL valid command line and stdin arguments for this fencing agent */ +static struct arg_info _arg_info[] = { + { '\xff', NULL, "agent", + NULL, 0, 0, "string", NULL, + "Not user serviceable", + NULL }, + + { '\xff', NULL, "self", + NULL, 0, 0, "string", NULL, + "Not user serviceable", + NULL }, + + { '\xff', NULL, "nodename", + NULL, 0, 0, "string", NULL, + "Not user serviceable", + NULL }, + + { 'd', "-d", "debug", + NULL, 0, 0, "boolean", NULL, + "Specify (stdin) or increment (command line) debug level", + assign_debug }, + + { 'i', "-i <family>", "ip_family", + NULL, 0, 0, "string", "auto", + "IP Family ([auto], ipv4, ipv6)", + assign_family }, + + { 'a', "-a <address>", "multicast_address", + NULL, 0, 0, "string", NULL, + "Multicast address (default=" IPV4_MCAST_DEFAULT " / " IPV6_MCAST_DEFAULT ")", + assign_address }, + + { 'T', "-T <address>", "ipaddr", + NULL, 0, 0, "string", "127.0.0.1", + "IP address to connect to in TCP mode (default=" IPV4_TCP_ADDR_DEFAULT " / " IPV6_TCP_ADDR_DEFAULT ")", + assign_ip_address }, + + { 'S', "-S <cid>", "vsock", + NULL, 0, 0, "integer", "2", + "vm socket CID to connect to in vsock mode", + assign_cid }, + + { 'A', "-A <address>", "channel_address", + NULL, 0, 0, "string", "10.0.2.179", + "VM Channel IP address (default=" DEFAULT_CHANNEL_IP ")", + assign_channel_address }, + + { 'p', "-p <port>", "ipport", + NULL, 0, 0, "string", "1229", + "TCP, Multicast, VMChannel, or VM socket port (default=1229)", + assign_port }, + + { 'I', "-I <interface>", "interface", + NULL, 0, 0, "string", NULL, + "Network interface name to listen on", + assign_interface }, + + { 'r', "-r <retrans>", "retrans", + NULL, 0, 0, "string", "20", + "Multicast retransmit time (in 1/10sec; default=20)", + assign_retrans }, + + { 'c', "-c <hash>", "hash", + NULL, 0, 0, "string", "sha256", + "Packet hash strength (none, sha1, [sha256], sha512)", + assign_hash }, + + { 'C', "-C <auth>", "auth", + NULL, 0, 0, "string", "sha256", + "Authentication (none, sha1, [sha256], sha512)", + assign_auth }, + + { 'k', "-k <file>", "key_file", + NULL, 0, 0, "string", DEFAULT_KEY_FILE, + "Shared key file (default=" DEFAULT_KEY_FILE ")", + assign_key }, + + { 'D', "-D <device>", "serial_device", + NULL, 0, 0, "string", DEFAULT_SERIAL_DEVICE, + "Serial device (default=" DEFAULT_SERIAL_DEVICE ")", + assign_device }, + + { 'P', "-P <param>", "serial_params", + NULL, 0, 0, "string", DEFAULT_SERIAL_SPEED, + "Serial Parameters (default=" DEFAULT_SERIAL_SPEED ")", + assign_params }, + + { '\xff', NULL, "option", + /* Deprecated */ + NULL, 0, 0, "string", "reboot", + "Fencing option (null, off, on, [reboot], status, list, list-status, monitor, validate-all, metadata)", + assign_op }, + + { 'o', "-o <operation>", "action", + NULL, 0, 0, "string", "reboot", + "Fencing action (null, off, on, [reboot], status, list, list-status, monitor, validate-all, metadata)", + assign_op }, + + { 'n', "-n <domain>", "plug", + "port", 0, 0, "string", NULL, + "Virtual Machine (domain name) to fence", + assign_domain }, + + { 'H', "-H <domain>", "port", + NULL, 1, 0, "string", NULL, + "Virtual Machine (domain name) to fence", + assign_domain }, + + { SCHEMA_COMPAT, NULL, "domain", + NULL, 0, 0, "string", NULL, + "Virtual Machine (domain name) to fence (deprecated; use port)", + assign_domain }, + + { 'u', "-u", "use_uuid", + NULL, 0, 0, "string", "0", + "Treat [domain] as UUID instead of domain name. This is provided for compatibility with older fence_xvmd installations.", + assign_uuid_lookup }, + + { 't', "-t <timeout>", "timeout", + NULL, 0, 0, "string", "30", + "Fencing timeout (in seconds; default=30)", + assign_timeout }, + + { 'h', "-h", NULL, + NULL, 0, 0, "boolean", "0", + "Help", + assign_help }, + + { '?', "-?", NULL, + NULL, 0, 0, "boolean", "0", + "Help (alternate)", + assign_help }, + + { 'w', "-w <delay>", "delay", + NULL, 0, 0, "string", "0", + "Fencing delay (in seconds; default=0)", + assign_delay }, + + { 'V', "-V", NULL, + NULL, 0, 0, "boolean", "0", + "Display version and exit", + assign_version }, + + /* Terminator */ + { 0, NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, NULL } +}; + + +static struct arg_info * +find_arg_by_char(char arg) +{ + int x = 0; + + for (x = 0; _arg_info[x].opt != 0; x++) { + if (_arg_info[x].opt == arg) + return &_arg_info[x]; + } + + return NULL; +} + + +static struct arg_info * +find_arg_by_string(char *arg) +{ + int x = 0; + + for (x = 0; _arg_info[x].opt != 0; x++) { + if (!_arg_info[x].stdin_opt) + continue; + if (!strcasecmp(_arg_info[x].stdin_opt, arg)) + return &_arg_info[x]; + } + + return NULL; +} + + +/* ============================================================= */ + +/** + Initialize an args structure. + + @param args Pointer to args structure to initialize. + */ +void +args_init(fence_virt_args_t *args) +{ + args->domain = NULL; + //args->uri = NULL; + args->op = FENCE_REBOOT; + args->net.key_file = strdup(DEFAULT_KEY_FILE); + args->net.hash = DEFAULT_HASH; + args->net.auth = DEFAULT_AUTH; + args->net.addr = NULL; + args->net.ipaddr = NULL; + args->net.cid = 0; + args->net.port = DEFAULT_MCAST_PORT; + args->net.ifindex = 0; + args->net.family = 0; /* auto */ + args->serial.device = NULL; + args->serial.speed = strdup(DEFAULT_SERIAL_SPEED); + args->serial.address = strdup(DEFAULT_CHANNEL_IP); + args->timeout = 30; + args->retr_time = 20; + args->flags = 0; + args->debug = 0; + args->delay = 0; +} + + +#define _pr_int(piece) printf(" %s = %d\n", #piece, piece) +#define _pr_str(piece) printf(" %s = %s\n", #piece, piece) + + +/** + Prints out the contents of an args structure for debugging. + + @param args Pointer to args structure to print out. + */ +void +args_print(fence_virt_args_t *args) +{ + printf("-- args @ %p --\n", args); + _pr_str(args->domain); + _pr_int(args->op); + _pr_int(args->mode); + _pr_int(args->debug); + _pr_int(args->timeout); + _pr_int(args->delay); + _pr_int(args->retr_time); + _pr_int(args->flags); + + _pr_str(args->net.addr); + _pr_str(args->net.ipaddr); + _pr_int(args->net.cid); + _pr_str(args->net.key_file); + _pr_int(args->net.port); + _pr_int(args->net.hash); + _pr_int(args->net.auth); + _pr_int(args->net.family); + _pr_int(args->net.ifindex); + + _pr_str(args->serial.device); + _pr_str(args->serial.speed); + _pr_str(args->serial.address); + printf("-- end args --\n"); +} + + +/** + Print out arguments and help information based on what is allowed in + the getopt string optstr. + + @param progname Program name. + @param optstr Getopt(3) style options string + @param print_stdin 0 = print command line options + description, + 1 = print fence-style stdin args + description + */ +static char * +find_rev(const char *start, char *curr, char c) +{ + + while (curr > start) { + if (*curr == c) + return curr; + --curr; + } + + return NULL; +} + + +static void +output_help_text(int arg_width, int help_width, const char *arg, const char *desc) +{ + char out_buf[4096]; + char *p, *start; + const char *arg_print = arg; + int len; + + memset(out_buf, 0, sizeof(out_buf)); + strncpy(out_buf, desc, sizeof(out_buf) - 1); + start = out_buf; + + do { + p = NULL; + len = strlen(start); + if (len > help_width) { + p = start + help_width; + p = find_rev(start, p, ' '); + if (p) { + *p = 0; + p++; + } + } + printf(" %*.*s %*.*s\n", + -arg_width, arg_width, + arg_print, + -help_width, help_width, + start); + if (!p) + return; + if (arg == arg_print) + arg_print = " "; + start = p; + } while(1); +} + + +void +args_usage(char *progname, const char *optstr, int print_stdin) +{ + int x; + struct arg_info *arg; + + if (!print_stdin) { + if (progname) { + printf("usage: %s [args]\n\nNOTE: reboot-action does not power on nodes that are powered off.\n\n", progname); + } else { + printf("usage: fence_virt [args]\n\nNOTE: reboot-action does not power on nodes that are powered off.\n\n"); + } + } + + for (x = 0; x < strlen(optstr); x++) { + arg = find_arg_by_char(optstr[x]); + if (!arg || arg->deprecated) + continue; + + if (print_stdin) { + if (arg && arg->stdin_opt) + output_help_text(20, 55, arg->stdin_opt, arg->desc); + } else { + output_help_text(20, 55, arg->opt_desc, arg->desc); + } + } + + printf("\n"); +} + + +void +args_metadata(char *progname, const char *optstr) +{ + int x; + struct arg_info *arg; + + printf("<?xml version=\"1.0\" ?>\n"); + printf("<resource-agent name=\"%s\" shortdesc=\"Fence agent for virtual machines\">\n", basename(progname)); + printf("<longdesc>%s is an I/O Fencing agent which can be used with " + "virtual machines.\n\nNOTE: reboot-action does not power on nodes that are powered off." + "</longdesc>\n", basename(progname)); + printf("<vendor-url>https://libvirt.org</vendor-url>\n"); + printf("<parameters>\n"); + + for (x = 0; x < strlen(optstr); x++) { + arg = find_arg_by_char(optstr[x]); + if (!arg) + continue; + if (!arg->stdin_opt) + continue; + + if (arg->obsoletes) + printf("\t<parameter name=\"%s\" unique=\"0\" required=\"%d\" obsoletes=\"%s\">\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value) ? 0 : 1, arg->obsoletes); + else if (arg->deprecated) + printf("\t<parameter name=\"%s\" unique=\"0\" required=\"%d\" deprecated=\"%d\">\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value) ? 0 : 1, arg->deprecated); + else + printf("\t<parameter name=\"%s\" unique=\"0\" required=\"%d\">\n", arg->stdin_opt, (!strcmp(arg->content_type, "boolean") || arg->default_value || !strcmp(arg->stdin_opt, "multicast_address")) ? 0 : 1); + + printf("\t\t<getopt mixed=\"-%c\" />\n",arg->opt); + if (arg->default_value) { + printf("\t\t<content type=\"%s\" default=\"%s\" />\n", arg->content_type, arg->default_value); + } else { + printf("\t\t<content type=\"%s\" />\n", arg->content_type); + } + printf("\t\t<shortdesc lang=\"en\">"); + print_desc_xml(arg->desc); + printf("</shortdesc>\n"); + printf("\t</parameter>\n"); + } + + for (x = 0; _arg_info[x].opt != 0; x++) { + if (_arg_info[x].opt != SCHEMA_COMPAT) + continue; + + arg = &_arg_info[x]; + + printf("\t<parameter name=\"%s\" unique=\"0\" required=\"%d\" deprecated=\"1\">\n", arg->stdin_opt, + (!strcmp(arg->content_type, "boolean") || arg->default_value || !strcmp(arg->stdin_opt, "domain")) ? 0 : 1); + printf("\t\t<getopt mixed=\"\" />\n"); + if (arg->default_value) { + printf("\t\t<content type=\"%s\" default=\"%s\" />\n", arg->content_type, arg->default_value); + } else { + printf("\t\t<content type=\"%s\" />\n", arg->content_type); + } + printf("\t\t<shortdesc lang=\"en\">"); + print_desc_xml(arg->desc); + printf("</shortdesc>\n"); + printf("\t</parameter>\n"); + } + + printf("</parameters>\n"); + printf("<actions>\n"); + printf("\t<action name=\"null\" />\n"); + printf("\t<action name=\"on\" />\n"); + printf("\t<action name=\"off\" />\n"); + printf("\t<action name=\"reboot\" />\n"); + printf("\t<action name=\"metadata\" />\n"); + printf("\t<action name=\"status\" />\n"); + printf("\t<action name=\"monitor\" />\n"); + printf("\t<action name=\"list\" />\n"); + printf("\t<action name=\"list-status\" />\n"); + printf("\t<action name=\"validate-all\" />\n"); + printf("</actions>\n"); + printf("</resource-agent>\n"); +} + + +/** + Remove leading and trailing whitespace from a line of text. + + @param line Line to clean up + @param linelen Max size of line + @return 0 on success, -1 on failure + */ +static int +cleanup(char *line, size_t linelen) +{ + char *p; + int x; + + /* Remove leading whitespace. */ + p = line; + for (x = 0; x < linelen; x++) { + switch (line[x]) { + case '\t': + case ' ': + break; + case '\n': + case '\r': + return -1; + default: + goto eol; + } + } +eol: + /* Move the remainder down by as many whitespace chars as we + chewed up */ + if (x) + memmove(p, &line[x], linelen-x); + + /* Remove trailing whitespace. */ + for (x=0; x < linelen; x++) { + switch(line[x]) { + case '\t': + case ' ': + case '\r': + case '\n': + line[x] = 0; + case 0: + /* End of line */ + return 0; + } + } + + return -1; +} + + +/** + Parse args from stdin and assign to the specified args structure. + + @param optstr Command line option string in getopt(3) format + @param args Args structure to fill in. + */ +void +args_get_stdin(const char *optstr, fence_virt_args_t *args) +{ + char in[256]; + char *name, *val; + struct arg_info *arg; + + while (fgets(in, sizeof(in), stdin)) { + + if (in[0] == '#') + continue; + + if (cleanup(in, sizeof(in)) == -1) + continue; + + name = in; + if ((val = strchr(in, '='))) { + *val = 0; + ++val; + } + + arg = find_arg_by_string(name); + if (!arg || (arg->opt != '\xff' && + arg->opt != SCHEMA_COMPAT && + !strchr(optstr, arg->opt))) { + fprintf(stderr, + "Parse error: Ignoring unknown option '%s'\n", + name); + continue; + } + + if (arg->assign) + arg->assign(args, arg, val); + } +} + + +/** + Parse args from stdin and assign to the specified args structure. + + @param optstr Command line option string in getopt(3) format + @param args Args structure to fill in. + */ +void +args_get_getopt(int argc, char **argv, const char *optstr, fence_virt_args_t *args) +{ + int opt; + struct arg_info *arg; + + while ((opt = getopt(argc, argv, optstr)) != EOF) { + + arg = find_arg_by_char(opt); + + if (!arg) { + args->flags |= F_ERR; + continue; + } + + if (arg->assign) + arg->assign(args, arg, optarg); + } +} + + +void +args_finalize(fence_virt_args_t *args) +{ + char *addr = NULL; + + if (!args->net.addr) { + switch(args->net.family) { + case 0: + case PF_INET: + addr = (char *)IPV4_MCAST_DEFAULT; + break; + case PF_INET6: + addr = (char *)IPV6_MCAST_DEFAULT; + break; + default: + args->flags |= F_ERR; + break; + } + } + + if (!args->net.addr) + args->net.addr = addr; + + if (!args->net.addr) { + printf("No multicast address available\n"); + args->flags |= F_ERR; + } + + if (!args->net.addr) + return; + if (args->net.family) + return; + + /* Set family */ + if (strchr(args->net.addr, ':')) + args->net.family = PF_INET6; + if (strchr(args->net.addr, '.')) + args->net.family = PF_INET; + if (!args->net.family) { + printf("Could not determine address family\n"); + args->flags |= F_ERR; + } +} diff --git a/agents/virt/client/serial.c b/agents/virt/client/serial.c new file mode 100644 index 0000000..238ef4a --- /dev/null +++ b/agents/virt/client/serial.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2002-2003, 2009 Red Hat, Inc. + * + * License: GPLv2+ + * + * Written by Lon Hohberger <lhh@redhat.com> + * + * Serial client for fence_virt (incomplete, but + * a good start) + * + * Based on: + * Ubersimpledumbterminal "ser" version 1.0.3 + */ + +#include "config.h" + +#include <stdio.h> +#include <termios.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/select.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <arpa/inet.h> + +#include "fdops.h" +#include "xvm.h" +#include "options.h" +#include "client.h" +#include "tcp.h" + + +static int +char_to_speed(const char *speed) +{ + if (!speed || !strlen(speed)) + return B9600; + if (!strcmp(speed,"2400")) + return B2400; + if (!strcmp(speed,"9600")) + return B9600; + if (!strcmp(speed,"19200")) + return B19200; + if (!strcmp(speed,"38400")) + return B38400; + if (!strcmp(speed,"57600")) + return B57600; + if (!strcmp(speed,"115200")) + return B115200; + return -1; +} + + +static int +char_to_flags(const char *param) +{ + int db_f = CS8, par_f = 0, sb_f = 0, x; + + if (!param || !strlen(param)) + return (db_f | par_f | sb_f); + + if (strlen(param) < 3) { + errno = EINVAL; + return -1; + } + + for (x = 0; x < 3; x++) { + switch (param[x]) { + case '5': + db_f = CS5; + break; + case '6': + db_f = CS6; + break; + case '7': + db_f = CS7; + break; + case '8': + db_f = CS8; + break; + case 'n': + case 'N': + par_f = 0; + break; + case 'e': + case 'E': + par_f = PARENB; + break; + case 'o': + case 'O': + par_f = PARENB | PARODD; + break; + case '1': + sb_f = 0; + break; + case '2': + sb_f = CSTOPB; + break; + default: + printf("Fail: %c\n", param[x]); + errno = EINVAL; + return -1; + } + } + + return (db_f | par_f | sb_f); +} + + +static int +open_port(char *file, char *cspeed, char *cparam) +{ + struct termios ti; + int fd, speed = B115200, flags = 0; + struct flock lock; + + if ((speed = char_to_speed(cspeed)) == -1) { + errno = EINVAL; + return -1; + } + + if ((flags = char_to_flags(cparam)) == -1) { + errno = EINVAL; + return -1; + } + + if ((fd = open(file, O_RDWR | O_EXCL)) == -1) { + perror("open"); + return -1; + } + + memset(&lock,0,sizeof(lock)); + lock.l_type = F_WRLCK; + if (fcntl(fd, F_SETLK, &lock) == -1) { + perror("Failed to lock serial port"); + close(fd); + return -1; + } + + memset(&ti, 0, sizeof(ti)); + ti.c_cflag = (speed | CLOCAL | CRTSCTS | CREAD | flags); + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + perror("tcsetattr"); + close(fd); + return -1; + } + + (void) tcflush(fd, TCIOFLUSH); + + return fd; +} + + +static void +hangup(int fd, int delay) +{ + unsigned int bits; + + if (ioctl(fd, TIOCMGET, &bits)) { + perror("ioctl1"); + return; + } + + bits &= ~(TIOCM_DTR | TIOCM_CTS | TIOCM_RTS | TIOCM_DSR | TIOCM_CD); + + if (ioctl(fd, TIOCMSET, &bits)) { + perror("ioctl2"); + return; + } + + usleep(delay); + + bits |= (TIOCM_DTR | TIOCM_CTS | TIOCM_RTS | TIOCM_DSR | TIOCM_CD); + + if (ioctl(fd, TIOCMSET, &bits)) { + perror("ioctl3"); + return; + } +} + +static int +wait_for(int fd, const char *pattern, size_t size, struct timeval *tout) +{ + char *pos = (char *)pattern; + char c; + int n; + struct timeval tv; + size_t remain = size; + + if (tout) { + memcpy(&tv, tout, sizeof(tv)); + tout = &tv; + } + + while (remain) { + n = _read_retry(fd, &c, 1, &tv); + if (n < 1) + return -1; + + if (c == *pos) { + ++pos; + --remain; + } else { + pos = (char *)pattern; + remain = size; + } + } + + return 0; +} + +int +serial_fence_virt(fence_virt_args_t *args) +{ + struct in_addr ina; + struct in6_addr in6a; + serial_req_t req; + int fd, ret; + char speed[32], *flags = NULL; + struct timeval tv; + serial_resp_t resp; + + if (args->serial.device) { + strncpy(speed, args->serial.speed, sizeof(speed) - 1); + + //printf("Port: %s Speed: %s\n", args->serial.device, speed); + + if ((flags = strchr(speed, ','))) { + *flags = 0; + flags++; + } + + fd = open_port(args->serial.device, speed, flags); + if (fd == -1) { + perror("open_port"); + return -1; + } + + hangup(fd, 300000); + } else { + fd = -1; + if (inet_pton(PF_INET, args->serial.address, &ina)) { + fd = ipv4_connect(&ina, args->net.port, 3); + } else if (inet_pton(PF_INET6, args->serial.address, &in6a)) { + fd = ipv6_connect(&in6a, args->net.port, 3); + } + + if (fd < 0) { + perror("vmchannel connect"); + printf("Failed to connect to %s:%d\n", args->serial.address, + args->net.port); + return -1; + } + } + + + memset(&req, 0, sizeof(req)); + req.magic = SERIAL_MAGIC; + req.request = (uint8_t)args->op; + gettimeofday(&tv, NULL); + req.seqno = (int)tv.tv_usec; + + if (args->domain) + strncpy((char *)req.domain, args->domain, sizeof(req.domain) - 1); + + tv.tv_sec = 3; + tv.tv_usec = 0; + swab_serial_req_t(&req); + ret = _write_retry(fd, &req, sizeof(req), &tv); + if (ret < sizeof(req)) { + if (ret < 0) { + close(fd); + return ret; + } + printf("Failed to send request\n"); + } + + tv.tv_sec = args->timeout; + tv.tv_usec = 0; + resp.magic = SERIAL_MAGIC; + do { + if (wait_for(fd, (const char *)&resp.magic, + sizeof(resp.magic), &tv) == 0) { + ret = _read_retry(fd, &resp.response, sizeof(resp.response), &tv); + } else { + /* The other end died or closed the connection */ + close(fd); + return -1; + } + + swab_serial_resp_t(&resp); + } while(resp.magic != SERIAL_MAGIC && (tv.tv_sec || tv.tv_usec)); + + if (resp.magic != SERIAL_MAGIC) { + close(fd); + return -1; + } + ret = resp.response; + if (resp.response == RESP_HOSTLIST) /* hostlist */ { + /* ok read hostlist */ + do_read_hostlist(fd, args->timeout); + ret = 0; + } + + close(fd); + return ret; +} diff --git a/agents/virt/client/tcp.c b/agents/virt/client/tcp.c new file mode 100644 index 0000000..986fdd9 --- /dev/null +++ b/agents/virt/client/tcp.c @@ -0,0 +1,171 @@ +/* + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <nss.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "tcp.h" +#include "debug.h" +#include "fdops.h" +#include "client.h" + +static int +tcp_exchange(int fd, fence_auth_type_t auth, void *key, + size_t key_len, int timeout) +{ + fd_set rfds; + struct timeval tv; + char ret = 1; + + /* Ok, we're connected */ + dbg_printf(3, "Issuing TCP challenge\n"); + if (sock_challenge(fd, auth, key, key_len, timeout) <= 0) { + /* Challenge failed */ + printf("Invalid response to challenge\n"); + return 1; + } + + /* Now they'll send us one, so we need to respond here */ + dbg_printf(3, "Responding to TCP challenge\n"); + if (sock_response(fd, auth, key, key_len, timeout) <= 0) { + printf("Invalid response to challenge\n"); + return 1; + } + + dbg_printf(2, "TCP Exchange + Authentication done... \n"); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + ret = 1; + dbg_printf(3, "Waiting for return value from fence_virtd host\n"); + if (_select_retry(fd + 1, &rfds, NULL, NULL, &tv) <= 0) + return -1; + + /* Read return code */ + if (_read_retry(fd, &ret, 1, &tv) < 0) + ret = 1; + + if (ret == (char)RESP_HOSTLIST) /* hostlist */ { + do_read_hostlist(fd, timeout); + ret = 0; + } + + return ret; +} + +int +tcp_fence_virt(fence_virt_args_t *args) +{ + char key[MAX_KEY_LEN]; + struct timeval tv; + int key_len = 0, fd = -1; + int ret; + struct in_addr ina; + struct in6_addr in6a; + fence_req_t freq; + + /* 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; + } + + if (args->net.auth != AUTH_NONE || args->net.hash != HASH_NONE) { + key_len = read_key_file(args->net.key_file, key, sizeof(key)); + if (key_len < 0) { + printf("Could not read %s; trying without " + "authentication\n", args->net.key_file); + args->net.auth = AUTH_NONE; + args->net.hash = HASH_NONE; + key_len = 0; + } + } + + /* Same wire protocol as fence_xvm */ + memset(&freq, 0, sizeof(freq)); + if (args->domain && strlen((char *)args->domain)) + strncpy((char *)freq.domain, args->domain, sizeof(freq.domain) - 1); + freq.request = args->op; + freq.hashtype = args->net.hash; + freq.flags = 0; + if (args->flags & F_USE_UUID) + freq.flags |= RF_UUID; + gettimeofday(&tv, NULL); + freq.seqno = (uint32_t) tv.tv_usec; + sign_request(&freq, key, key_len); + + /* XXX fixme */ + if (inet_pton(PF_INET, args->net.ipaddr, &ina)) { + fd = ipv4_connect(&ina, args->net.port, 3); + } else if (inet_pton(PF_INET6, args->net.ipaddr, &in6a)) { + fd = ipv6_connect(&in6a, args->net.port, 3); + } + + if (fd < 0) { + printf("Unable to connect to fence_virtd host %s:%d %s\n", + args->net.ipaddr, args->net.port, strerror(errno)); + return 1; + } + + ret = _write_retry(fd, &freq, sizeof(freq), NULL); + if (ret != sizeof(freq)) { + perror("write"); + close(fd); + return 1; + } + + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + ret = tcp_exchange(fd, args->net.auth, key, key_len, + args->timeout); + break; + /* case AUTH_X509: + return ssl_exchange(...); */ + default: + dbg_printf(3, "Unknown auth type: %d\n", args->net.auth); + ret = 1; + break; + } + + close(fd); + return ret; +} diff --git a/agents/virt/client/vsock.c b/agents/virt/client/vsock.c new file mode 100644 index 0000000..7557c54 --- /dev/null +++ b/agents/virt/client/vsock.c @@ -0,0 +1,176 @@ +/* + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <nss.h> +#include <sys/socket.h> +#include <linux/vm_sockets.h> + +/* Local includes */ +#include "xvm.h" +#include "simple_auth.h" +#include "options.h" +#include "debug.h" +#include "fdops.h" +#include "client.h" + +static int +sock_exchange(int fd, fence_auth_type_t auth, void *key, + size_t key_len, int timeout) +{ + fd_set rfds; + struct timeval tv; + char ret = 1; + + /* Ok, we're connected */ + dbg_printf(3, "Issuing challenge\n"); + if (sock_challenge(fd, auth, key, key_len, timeout) <= 0) { + /* Challenge failed */ + printf("Invalid response to challenge\n"); + return 1; + } + + /* Now they'll send us one, so we need to respond here */ + dbg_printf(3, "Responding to challenge\n"); + if (sock_response(fd, auth, key, key_len, timeout) <= 0) { + printf("Invalid response to challenge\n"); + return 1; + } + + dbg_printf(2, "vsock Exchange + Authentication done... \n"); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + ret = 1; + dbg_printf(3, "Waiting for return value from fence_virtd host\n"); + if (_select_retry(fd + 1, &rfds, NULL, NULL, &tv) <= 0) + return -1; + + /* Read return code */ + if (_read_retry(fd, &ret, 1, &tv) < 0) + ret = 1; + + if (ret == (char)RESP_HOSTLIST) /* hostlist */ { + do_read_hostlist(fd, timeout); + ret = 0; + } + + return ret; +} + +int +vsock_fence_virt(fence_virt_args_t *args) +{ + char key[MAX_KEY_LEN]; + struct timeval tv; + int key_len = 0, fd = -1; + int ret; + struct sockaddr_vm svm; + fence_req_t freq; + + /* 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; + } + + if (args->net.auth != AUTH_NONE || args->net.hash != HASH_NONE) { + key_len = read_key_file(args->net.key_file, key, sizeof(key)); + if (key_len < 0) { + printf("Could not read %s; trying without " + "authentication\n", args->net.key_file); + args->net.auth = AUTH_NONE; + args->net.hash = HASH_NONE; + key_len = 0; + } + } + + /* Same wire protocol as fence_xvm */ + memset(&freq, 0, sizeof(freq)); + if (args->domain && strlen((char *)args->domain)) + strncpy((char *)freq.domain, args->domain, sizeof(freq.domain) - 1); + freq.request = args->op; + freq.hashtype = args->net.hash; + freq.flags = 0; + if (args->flags & F_USE_UUID) + freq.flags |= RF_UUID; + gettimeofday(&tv, NULL); + freq.seqno = (uint32_t) tv.tv_usec; + sign_request(&freq, key, key_len); + + fd = socket(PF_VSOCK, SOCK_STREAM, 0); + if (fd < 0) { + printf("Unable to create vsock: %s", strerror(errno)); + return 1; + } + + memset(&svm, 0, sizeof(svm)); + svm.svm_family = AF_VSOCK; + svm.svm_cid = args->net.cid; + svm.svm_port = args->net.port; + + if (connect(fd, (struct sockaddr *) &svm, sizeof(svm)) < 0) { + printf("Unable to connect to fence_virtd host %d:%d %s\n", + args->net.cid, args->net.port, strerror(errno)); + close(fd); + return 1; + } + + ret = _write_retry(fd, &freq, sizeof(freq), NULL); + if (ret != sizeof(freq)) { + perror("write"); + close(fd); + return 1; + } + + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + ret = sock_exchange(fd, args->net.auth, key, key_len, + args->timeout); + break; + /* case AUTH_X509: + return ssl_exchange(...); */ + default: + dbg_printf(3, "Unknown auth type: %d\n", args->net.auth); + ret = 1; + break; + } + + close(fd); + return ret; +} diff --git a/agents/virt/common/Makefile.am b/agents/virt/common/Makefile.am new file mode 100644 index 0000000..d5e4314 --- /dev/null +++ b/agents/virt/common/Makefile.am @@ -0,0 +1,24 @@ +############################################################################### +############################################################################### +## +## Copyright (C) 2009-2019 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_LTLIBRARIES = libfence_virt.la + +libfence_virt_la_SOURCES = mcast.c ip_lookup.c simple_auth.c tcp.c \ + debug.c fdops.c log.c + +libfence_virt_la_CFLAGS = $(VIRT_AM_CFLAGS) $(nss_CFLAGS) $(AM_CFLAGS) -Wno-cast-align + +libfence_virt_la_LDFLAGS = $(VIRT_AM_LDFLAGS) $(VIRT_COMMON_LDFLAGS) + +libfence_virt_la_LIBADD = $(nss_LIBS) diff --git a/agents/virt/common/bcast.c b/agents/virt/common/bcast.c new file mode 100644 index 0000000..2b498b7 --- /dev/null +++ b/agents/virt/common/bcast.c @@ -0,0 +1,347 @@ +/* + * 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 <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/socket.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> + +/* Local includes */ +#include "bcast.h" +#include "debug.h" + +LOGSYS_DECLARE_SUBSYS ("XVM", SYSLOGLEVEL); + +/** + Sets up a multicast receive socket + */ +int +ipv4_bcast_recv_sk(char *addr, int port) +{ + int sock, val; + struct sockaddr_in sin; + + /* Store broadcast address */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + if (inet_pton(PF_INET, addr, + (void *)&sin.sin_addr.s_addr) < 0) { + log_printf(LOG_ERR, "Invalid broadcast address: %s\n", addr); + return -1; + } + + dbg_printf(4, "Setting up ipv4 broadcast receive (%s:%d)\n", addr, port); + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_printf(LOG_ERR, "socket: %s\n", strerror(errno)); + close(sock); + sock = -1; + return 1; + } + + val = 1; + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, 1, sizeof(val)) < 0) { + log_printf(LOG_ERR, "setsockopt: %s\n", strerror(errno)); + close(sock); + return 1; + } + + /* + * Bind broadcast address + */ + if (bind(sock, (struct sockaddr *) &sin, + sizeof(struct sockaddr_in)) < 0) { + printf("bind failed: %s\n", strerror(errno)); + close(sock); + return -1; + } + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + +/** + Set up multicast send socket + */ +int +ipv4_bcast_send_sk(char *send_addr, char *addr, int port, struct sockaddr *tgt, + socklen_t tgt_len, int ttl) +{ + int val; + struct ip_mreq mreq; + struct sockaddr_in mcast; + struct sockaddr_in src; + int sock; + + if (tgt_len < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + /* Store multicast address */ + mcast.sin_family = PF_INET; + mcast.sin_port = htons(port); + if (inet_pton(PF_INET, addr, + (void *)&mcast.sin_addr.s_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + mreq.imr_multiaddr.s_addr = mcast.sin_addr.s_addr; + + /* Store sending address */ + src.sin_family = PF_INET; + src.sin_port = htons(port); + if (inet_pton(PF_INET, send_addr, + (void *)&src.sin_addr.s_addr) < 0) { + printf("Invalid source address: %s\n", send_addr); + return -1; + } + mreq.imr_interface.s_addr = src.sin_addr.s_addr; + + + /************************* + * SET UP MULTICAST SEND * + *************************/ + dbg_printf(4, "Setting up ipv4 multicast send (%s:%d)\n", addr, port); + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group (pass 1)\n"); + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast membership to transmit " + "socket %s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group (pass 2)\n"); + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &src.sin_addr, + sizeof(src.sin_addr)) == -1) { + printf("Failed to bind multicast transmit socket to " + "%s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * set time to live to 2 hops. + */ + dbg_printf(4, "Setting TTL to %d for fd%d\n", ttl, sock); + val = ttl; + if (setsockopt(sock, SOL_IP, IP_MULTICAST_TTL, &val, + sizeof(val))) + printf("warning: setting TTL failed %s\n", strerror(errno)); + + memcpy((struct sockaddr_in *)tgt, &mcast, sizeof(struct sockaddr_in)); + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + + +/** + Sets up a multicast receive (ipv6) socket + */ +int +ipv6_recv_sk(char *addr, int port) +{ + int sock, val; + struct ipv6_mreq mreq; + struct sockaddr_in6 sin; + + memset(&mreq, 0, sizeof(mreq)); + memset(&sin, 0, sizeof(sin)); + sin.sin6_family = PF_INET6; + sin.sin6_port = htons(port); + if (inet_pton(PF_INET6, addr, + (void *)&sin.sin6_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + + memcpy(&mreq.ipv6mr_multiaddr, &sin.sin6_addr, + sizeof(struct in6_addr)); + + + /******************************** + * SET UP MULTICAST RECV SOCKET * + ********************************/ + dbg_printf(4, "Setting up ipv6 multicast receive (%s:%d)\n", addr, port); + sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + printf("socket: %s\n", strerror(errno)); + close(sock); + sock = -1; + return 1; + } + + /* + * When using Multicast, bind to the LOCAL address, not the MULTICAST + * address. + */ + memset(&sin, 0, sizeof(sin)); + sin.sin6_family = PF_INET6; + sin.sin6_port = htons(port); + sin.sin6_addr = in6addr_any; + if (bind(sock, (struct sockaddr *) &sin, + sizeof(struct sockaddr_in6)) < 0) { + printf("bind failed: %s\n", strerror(errno)); + close(sock); + return -1; + } + + dbg_printf(4, "Disabling IP Multicast loopback\n"); + val = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) { + printf("Failed to disable multicast loopback\n"); + close(sock); + return -1; + } + + /* + * Join multicast group + */ + dbg_printf(4, "Joining IP Multicast group\n"); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast to socket %s: %s\n", + addr, strerror(errno)); + close(sock); + return -1; + } + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + +/** + Set up ipv6 multicast send socket + */ +int +ipv6_send_sk(char *send_addr, char *addr, int port, struct sockaddr *tgt, + socklen_t tgt_len, int ttl) +{ + int val; + struct ipv6_mreq mreq; + struct sockaddr_in6 mcast; + struct sockaddr_in6 src; + int sock; + + if (tgt_len < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + memset(&mreq, 0, sizeof(mreq)); + + /* Store multicast address */ + mcast.sin6_family = PF_INET6; + mcast.sin6_port = htons(port); + if (inet_pton(PF_INET6, addr, + (void *)&mcast.sin6_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + + memcpy(&mreq.ipv6mr_multiaddr, &mcast.sin6_addr, + sizeof(struct in6_addr)); + + /* Store sending address */ + src.sin6_family = PF_INET6; + src.sin6_port = htons(port); + if (inet_pton(PF_INET6, send_addr, + (void *)&src.sin6_addr) < 0) { + printf("Invalid source address: %s\n", send_addr); + return -1; + } + + /************************* + * SET UP MULTICAST SEND * + *************************/ + dbg_printf(4, "Setting up ipv6 multicast send (%s:%d)\n", addr, port); + sock = socket(PF_INET6, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + dbg_printf(4, "Disabling IP Multicast loopback\n"); + val = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) { + printf("Failed to disable multicast loopback\n"); + close(sock); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group\n"); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast membership to transmit " + "socket %s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * Join Multicast group (part 2) + */ + /* + if (setsockopt(sock, IPPROTO_IPV6, IP_MULTICAST_IF, &src.sin6_addr, + sizeof(src.sin6_addr)) == -1) { + printf("Failed to bind multicast transmit socket to " + "%s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + */ + + /* + * set time to live to 2 hops. + */ + val = ttl; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val))) + printf("warning: setting TTL failed %s\n", strerror(errno)); + + memcpy((struct sockaddr_in *)tgt, &mcast, sizeof(struct sockaddr_in6)); + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} diff --git a/agents/virt/common/debug.c b/agents/virt/common/debug.c new file mode 100644 index 0000000..5cf5359 --- /dev/null +++ b/agents/virt/common/debug.c @@ -0,0 +1,38 @@ +/* + 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. +*/ + +#include "config.h" + +#include <stdio.h> +#include "debug.h" + +static int _debug = 0; + +void +dset(int threshold) +{ + _debug = threshold; + dbg_printf(3, "Debugging threshold is now %d\n", threshold); +} + +int +dget(void) +{ + return _debug; +} diff --git a/agents/virt/common/fdops.c b/agents/virt/common/fdops.c new file mode 100644 index 0000000..329e9b7 --- /dev/null +++ b/agents/virt/common/fdops.c @@ -0,0 +1,202 @@ +/* + Copyright Red Hat, Inc. 2002-2003 + + 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. +*/ +/** @file + * Wrapper functions around read/write/select to retry in the event + * of interrupts. + */ + +#include "config.h" + +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> + +#include "fdops.h" + +/** + * This is a wrapper around select which will retry in the case we receive + * EINTR. This is necessary for _read_retry, since it wouldn't make sense + * to have _read_retry terminate if and only if two EINTRs were received + * in a row - one during the read() call, one during the select call... + * + * See select(2) for description of parameters. + */ +int +_select_retry(int fdmax, fd_set * rfds, fd_set * wfds, fd_set * xfds, + struct timeval *timeout) +{ + int rv; + + while (1) { + rv = select(fdmax, rfds, wfds, xfds, timeout); + if (rv == -1) { + /* return on EBADF/EINVAL/ENOMEM; continue on EINTR/EAGAIN/ENOMEM */ + if (errno == EINTR || errno == EAGAIN || errno == ENOMEM) + continue; + } + return rv; + } +} + +/** + * Retries a write in the event of a non-blocked interrupt signal. + * + * @param fd File descriptor to which we are writing. + * @param buf Data buffer to send. + * @param count Number of bytes in buf to send. + * @param timeout (struct timeval) telling us how long we should retry. + * @return The number of bytes written to the file descriptor, + * or -1 on error (with errno set appropriately). + */ +ssize_t +_write_retry(int fd, void *buf, int count, struct timeval * timeout) +{ + int n, total = 0, remain = count, rv = 0; + fd_set wfds, xfds; + char *tmp_buf = (char *)buf; + + while (total < count) { + + /* Create the write FD set of 1... */ + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + FD_ZERO(&xfds); + FD_SET(fd, &xfds); + + /* wait for the fd to be available for writing */ + rv = _select_retry(fd + 1, NULL, &wfds, &xfds, timeout); + if (rv == -1) + return -1; + else if (rv == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (FD_ISSET(fd, &xfds)) { + errno = EPIPE; + return -1; + } + + /* + * Attempt to write to fd + */ + n = write(fd, tmp_buf + total, remain); + + /* + * When we know our fd was select()ed and we receive 0 bytes + * when we write, the fd was closed. + */ + if ((n == 0) && (rv == 1)) { + errno = EPIPE; + return -1; + } + + if (n == -1) { + if ((errno == EAGAIN) || (errno == EINTR)) { + /* + * Not ready? + */ + continue; + } + + /* Other errors: EIO, EINVAL, etc */ + return -1; + } + + total += n; + remain -= n; + } + + return total; +} + +/** + * Retry reads until we (a) time out or (b) get our data. Of course, if + * timeout is NULL, it'll wait forever. + * + * @param sockfd File descriptor we want to read from. + * @param buf Preallocated buffer into which we will read data. + * @param count Number of bytes to read. + * @param timeout (struct timeval) describing how long we should retry. + * @return The number of bytes read on success, or -1 on failure. + Note that we will always return (count) or (-1). + */ +ssize_t +_read_retry(int sockfd, void *buf, int count, struct timeval * timeout) +{ + int n, total = 0, remain = count, rv = 0; + fd_set rfds, xfds; + char *tmp_buf = (char *)buf; + + while (total < count) { + FD_ZERO(&rfds); + FD_SET(sockfd, &rfds); + FD_ZERO(&xfds); + FD_SET(sockfd, &xfds); + + /* + * Select on the socket, in case it closes while we're not + * looking... + */ + rv = _select_retry(sockfd + 1, &rfds, NULL, &xfds, timeout); + if (rv == -1) + return -1; + else if (rv == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (FD_ISSET(sockfd, &xfds)) { + errno = EPIPE; + return -1; + } + + /* + * Attempt to read off the socket + */ + n = read(sockfd, tmp_buf + total, remain); + + /* + * When we know our socket was select()ed and we receive 0 bytes + * when we read, the socket was closed. + */ + if ((n == 0) && (rv == 1)) { + errno = EPIPE; + return -1; + } + + if (n == -1) { + if ((errno == EAGAIN) || (errno == EINTR)) { + /* + * Not ready? Wait for data to become available + */ + continue; + } + + /* Other errors: EPIPE, EINVAL, etc */ + return -1; + } + + total += n; + remain -= n; + } + + return total; +} diff --git a/agents/virt/common/ip_lookup.c b/agents/virt/common/ip_lookup.c new file mode 100644 index 0000000..aded8ea --- /dev/null +++ b/agents/virt/common/ip_lookup.c @@ -0,0 +1,326 @@ +/* + Copyright Red Hat, Inc. 2004, 2006 + + 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 + * Build lists of IPs on the system, excepting loopback ipv6 link-local + */ + +#include "config.h" + +#include <asm/types.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <netdb.h> + +/* Local includes */ +#include "ip_lookup.h" +#include "debug.h" + +static int +send_addr_dump(int fd, int family) +{ + struct nlmsghdr *nh; + struct rtgenmsg *g; + char buf[256]; + struct sockaddr_nl addr; + + memset(&addr,0,sizeof(addr)); + addr.nl_family = PF_NETLINK; + + memset(buf, 0, sizeof(buf)); + nh = (struct nlmsghdr *)buf; + g = (struct rtgenmsg *)(buf + sizeof(struct nlmsghdr)); + + nh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + nh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP; + nh->nlmsg_type = RTM_GETADDR; + g->rtgen_family = family; + + return sendto(fd, buf, nh->nlmsg_len, 0, (struct sockaddr *)&addr, + sizeof(addr)); +} + + +static int +add_ip(ip_list_t *ipl, char *ipaddr, char family) +{ + ip_addr_t *ipa; + + if (family == PF_INET6) { + /* Avoid loopback */ + if (!strcmp(ipaddr, "::1")) + return -1; + + /* Avoid link-local addresses */ + if (!strncmp(ipaddr, "fe80", 4)) + return -1; + if (!strncmp(ipaddr, "fe90", 4)) + return -1; + if (!strncmp(ipaddr, "fea0", 4)) + return -1; + if (!strncmp(ipaddr, "feb0", 4)) + return -1; + } + + ipa = calloc(1, sizeof(*ipa)); + if (!ipa) + return -1; + ipa->ipa_family = family; + ipa->ipa_address = strdup(ipaddr); + + dbg_printf(4, "Adding IP %s to list (family %d)\n", ipaddr, family); + TAILQ_INSERT_TAIL(ipl, ipa, ipa_entries); + + return 0; +} + + +static int +add_ip_addresses(int family, ip_list_t *ipl) +{ + /* List ipv4 addresses */ + struct nlmsghdr *nh; + struct ifaddrmsg *ifa; + struct rtattr *rta, *nrta; + struct nlmsgerr *err; + char buf[10240]; + char outbuf[256]; + char label[256]; + int x, fd, len; + + dbg_printf(5, "Connecting to Netlink...\n"); + fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + if (fd < 0) { + perror("socket"); + exit(1); + } + + dbg_printf(5, "Sending address dump request\n"); + if (send_addr_dump(fd, family) < 0) { + perror("sendto"); + close(fd); + return -1; + } + memset(buf, 0, sizeof(buf)); + + dbg_printf(5, "Waiting for response\n"); + x = recvfrom(fd, buf, sizeof(buf), 0, NULL, 0); + if (x < 0) { + perror("recvfrom"); + close(fd); + return -1; + } + + dbg_printf(5, "Received %d bytes\n", x); + + nh = (struct nlmsghdr *)buf; + while (NLMSG_OK(nh, x)) { + + switch(nh->nlmsg_type) { + case NLMSG_DONE: + close(fd); + return 0; + + case NLMSG_ERROR: + err = (struct nlmsgerr*)NLMSG_DATA(nh); + if (nh->nlmsg_len < + NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + fprintf(stderr, "ERROR truncated"); + } else { + errno = -err->error; + perror("RTNETLINK answers"); + } + close(fd); + return -1; + + case RTM_NEWADDR: + break; + + default: + nh = NLMSG_NEXT(nh, x); + continue; + } + + /* RTM_NEWADDR */ + len = NLMSG_PAYLOAD(nh,0); + ifa = NLMSG_DATA(nh); + + /* Make sure we got the type we expect back */ + if (ifa->ifa_family != family) { + nh = NLMSG_NEXT(nh, x); + continue; + } + + rta = IFA_RTA(ifa); + len -= sizeof(struct ifaddrmsg); + do { + /* Make sure we've got a valid rtaddr field */ + if (!RTA_OK(rta, len)) { + dbg_printf(5, "!RTA_OK(rta, len)\n"); + break; + } + + if (rta->rta_type == IFA_ADDRESS) { + inet_ntop(family, RTA_DATA(rta), outbuf, + sizeof(outbuf) ); + add_ip(ipl, outbuf, family); + } + + if (rta->rta_type == IFA_LABEL) { + memset(label, 0, sizeof(label)); + strncpy(label, (char *)RTA_DATA(rta), sizeof(label) - 1); + dbg_printf(5, "Skipping label: %s\n", + label); + } + + nrta = RTA_NEXT(rta, len); + len -= (nrta - rta); + rta = nrta; + } while (RTA_OK(rta, len)); + + nh = NLMSG_NEXT(nh, x); + } + + dbg_printf(5, "Closing Netlink connection\n"); + close(fd); + return 0; +} + + +int +ip_search(ip_list_t *ipl, char *ip_name) +{ + ip_addr_t *ipa; + + dbg_printf(5, "Looking for IP address %s in IP list %p...", ip_name, ipl); + ipa = ipl->tqh_first; + for (ipa = ipl->tqh_first; ipa; ipa = ipa->ipa_entries.tqe_next) { + if (!strcmp(ip_name, ipa->ipa_address)) { + dbg_printf(4,"Found\n"); + return 0; + } + } + dbg_printf(5, "Not found\n"); + return 1; +} + + +int +ip_free_list(ip_list_t *ipl) +{ + ip_addr_t *ipa; + + dbg_printf(5, "Tearing down IP list @ %p\n", ipl); + while ((ipa = ipl->tqh_first)) { + TAILQ_REMOVE(ipl, ipa, ipa_entries); + free(ipa->ipa_address); + free(ipa); + } + return 0; +} + + +int +ip_build_list(ip_list_t *ipl) +{ + dbg_printf(5, "Build IP address list\n"); + TAILQ_INIT(ipl); + if (add_ip_addresses(PF_INET6, ipl) < 0) { + ip_free_list(ipl); + return -1; + } + if (add_ip_addresses(PF_INET, ipl) < 0) { + ip_free_list(ipl); + return -1; + } + return 0; +} + + +/** + Look up the interface name which corresponds to the given hostname and + return the list of matching attrinfo structures. We do this by looking + up all the possible physical and virtual network interfaces on the machine + and checking the hostname/IP mappings for each active IP address incurred. + + @param nodename Interface name + @param ret_ai Structure pointer to allocate & return. + @return -1 on failure or 0 on success. + */ +int +ip_lookup(char *nodename, struct addrinfo **ret_ai) +{ + char ip_name[256]; + struct addrinfo *ai = NULL; + struct addrinfo *n; + void *p; + ip_list_t ipl; + int ret = -1; + + dbg_printf(5, "Looking for IP matching %s\n", nodename); + /* Build list of IP addresses configured locally */ + if (ip_build_list(&ipl) < 0) + return -1; + + /* Get list of addresses for the host-name/ip */ + if (getaddrinfo(nodename, NULL, NULL, &ai) != 0) + return -1; + + + /* Traverse list of addresses for given host-name/ip */ + for (n = ai; n; n = n->ai_next) { + if (n->ai_family != PF_INET && n->ai_family != PF_INET6) + continue; + + if (n->ai_family == PF_INET) + p = &(((struct sockaddr_in *)n->ai_addr)->sin_addr); + else + p = &(((struct sockaddr_in6 *)n->ai_addr)->sin6_addr); + + if (!inet_ntop(n->ai_family, p, ip_name, + sizeof(ip_name))) + continue; + + /* Search local interfaces for this IP address */ + if (ip_search(&ipl, ip_name) != 0) + continue; + + /* Found it */ + ret = 0; + break; + } + + /* Clean up */ + if (!ret_ai) + freeaddrinfo(ai); + else + *ret_ai = ai; + + ip_free_list(&ipl); + + return ret; +} + diff --git a/agents/virt/common/log.c b/agents/virt/common/log.c new file mode 100644 index 0000000..61018ed --- /dev/null +++ b/agents/virt/common/log.c @@ -0,0 +1,204 @@ +/** + * Syslog wrapper that does not block + * + * Lon Hohberger, 2009 + */ + +#include "config.h" + +#include <pthread.h> +#include <unistd.h> +#include <sys/syslog.h> +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/time.h> +#include <errno.h> + +#include "list.h" + +struct log_entry { + list_head(); + char *message; + int sev; + int bufsz; +}; + +#define MAX_QUEUE_LENGTH 10 +#define LOGLEN 256 + +static struct log_entry *_log_entries = NULL; +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t log_cond = PTHREAD_COND_INITIALIZER; +static int log_size = 0; +static int dropped = 0; +static pthread_t thread_id = 0; + +void __real_syslog(int severity, const char *fmt, ...); +void __wrap_syslog(int severity, const char *fmt, ...); +void __wrap_closelog(void); + +static void * +_log_thread(void *arg) +{ + struct timeval tv; + struct timespec ts; + struct log_entry *entry; + + do { + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + 10; + ts.tv_nsec = tv.tv_usec; + + pthread_mutex_lock(&log_mutex); + + while (!(entry = _log_entries)) { + if (pthread_cond_timedwait(&log_cond, + &log_mutex, + &ts) == ETIMEDOUT) + goto out; + } + + list_remove(&_log_entries, entry); + --log_size; + if (log_size < 0) + raise(SIGSEGV); + pthread_mutex_unlock(&log_mutex); + + __real_syslog(entry->sev, entry->message); + free(entry->message); + free(entry); + } while (1); + +out: + thread_id = (pthread_t)0; + pthread_mutex_unlock(&log_mutex); + return NULL; +} + + +static int +insert_entry(int sev, char *buf, int bufsz) +{ + struct log_entry *lent; + pthread_attr_t attrs; + + lent = malloc(sizeof(*lent)); + if (!lent) + return -1; + lent->sev = sev; + lent->message = buf; + lent->bufsz = bufsz; + + pthread_mutex_lock(&log_mutex); + if (log_size >= MAX_QUEUE_LENGTH) { + free(lent->message); + free(lent); + + ++dropped; + lent = (struct log_entry *)(le(_log_entries)->le_prev); + + lent->sev = LOG_WARNING; + snprintf(lent->message, lent->bufsz, + "%d message(s) lost due to syslog load\n", + dropped + 1); + /* Dropped +1 because we overwrote a message to + * give the 'dropped' message */ + } else { + ++log_size; + dropped = 0; + list_insert(&_log_entries, lent); + } + + if (!thread_id) { + pthread_attr_init(&attrs); + pthread_attr_setinheritsched(&attrs, PTHREAD_INHERIT_SCHED); + + if (pthread_create(&thread_id, &attrs, _log_thread, NULL) < 0) + thread_id = 0; + pthread_mutex_unlock(&log_mutex); + } else { + pthread_mutex_unlock(&log_mutex); + pthread_cond_signal(&log_cond); + } + + return 0; +} + + +__attribute__((__format__ (__printf__, 2, 0))) +void +__wrap_syslog(int severity, const char *fmt, ...) +{ + va_list args; + char *logmsg; + + logmsg = malloc(LOGLEN); + if (!logmsg) + return; + memset(logmsg, 0, LOGLEN); + + va_start(args, fmt); + vsnprintf(logmsg + strlen(logmsg), LOGLEN - strlen(logmsg), + fmt, args); + va_end(args); + + insert_entry(severity, logmsg, LOGLEN); + + return; +} + + +void __real_closelog(void); + +void +__wrap_closelog(void) +{ + struct log_entry *lent; +#ifdef DEBUG + int lost = 0; +#endif + + if (thread_id != 0) { + pthread_cancel(thread_id); + pthread_join(thread_id, NULL); + thread_id = 0; + } + __real_closelog(); + while (_log_entries) { +#ifdef DEBUG + ++lost; +#endif + lent = _log_entries; + list_remove(&_log_entries, lent); + free(lent->message); + free(lent); + } + +#ifdef DEBUG + printf("%d lost\n", lost); +#endif +} + + +#ifdef STANDALONE +int +main(int argc, char**argv) +{ + int x; + + for (x = 0; x < 100; x++) { + syslog(1, "Yo %d\n", x); + } + sleep(1); + + closelog(); + + return 0; +} + +#endif diff --git a/agents/virt/common/mcast.c b/agents/virt/common/mcast.c new file mode 100644 index 0000000..cc79c64 --- /dev/null +++ b/agents/virt/common/mcast.c @@ -0,0 +1,388 @@ +/* + Copyright Red Hat, Inc. 2003, 2004, 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 <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/socket.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> + +/* Local includes */ +#include "mcast.h" +#include "debug.h" + +/** + Sets up a multicast receive socket + */ +int +ipv4_recv_sk(char *addr, int port, unsigned int ifindex) +{ + int sock; + struct ip_mreqn mreq; + struct sockaddr_in sin; + + memset(&mreq, 0, sizeof(mreq)); + memset(&sin, 0, sizeof(sin)); + + /* Store multicast address */ + if (inet_pton(PF_INET, addr, + (void *)&mreq.imr_multiaddr.s_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + + /******************************** + * SET UP MULTICAST RECV SOCKET * + ********************************/ + dbg_printf(4, "Setting up ipv4 multicast receive (%s:%d)\n", addr, port); + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + printf("socket: %s\n", strerror(errno)); + sock = -1; + return 1; + } + + /* + * When using Multicast, bind to the LOCAL address, not the MULTICAST + * address. + */ + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, (struct sockaddr *) &sin, + sizeof(struct sockaddr_in)) < 0) { + printf("bind failed: %s\n", strerror(errno)); + close(sock); + return -1; + } + + /* + * Join multicast group + */ + /* mreq.imr_multiaddr.s_addr is set above */ + if (ifindex == 0) { + dbg_printf(4, "Setting mcast addr to INADDR_ANY due to ifindex of 0\n"); + mreq.imr_address.s_addr = htonl(INADDR_ANY); + } else { + mreq.imr_ifindex = ifindex; + } + dbg_printf(4, "Joining multicast group\n"); + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) == -1) { + printf("Failed to bind multicast receive socket to " + "%s: %s\n", addr, strerror(errno)); + printf("Check network configuration.\n"); + close(sock); + return -1; + } + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + +/** + Set up multicast send socket + */ +int +ipv4_send_sk(char *send_addr, char *addr, int port, struct sockaddr *tgt, + socklen_t tgt_len) +{ + int val; + struct ip_mreq mreq; + struct sockaddr_in mcast; + struct sockaddr_in src; + int sock; + + if (tgt_len < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + memset(&mcast, 0, sizeof(mcast)); + memset(&src, 0, sizeof(src)); + + /* Store multicast address */ + mcast.sin_family = PF_INET; + mcast.sin_port = htons(port); + if (inet_pton(PF_INET, addr, + (void *)&mcast.sin_addr.s_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + mreq.imr_multiaddr.s_addr = mcast.sin_addr.s_addr; + + /* Store sending address */ + src.sin_family = PF_INET; + src.sin_port = htons(port); + if (inet_pton(PF_INET, send_addr, + (void *)&src.sin_addr.s_addr) < 0) { + printf("Invalid source address: %s\n", send_addr); + return -1; + } + mreq.imr_interface.s_addr = src.sin_addr.s_addr; + + + /************************* + * SET UP MULTICAST SEND * + *************************/ + dbg_printf(4, "Setting up ipv4 multicast send (%s:%d)\n", addr, port); + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group (pass 1)\n"); + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast membership to transmit " + "socket %s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group (pass 2)\n"); + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &src.sin_addr, + sizeof(src.sin_addr)) == -1) { + printf("Failed to bind multicast transmit socket to " + "%s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * set time to live to 2 hops. + */ + dbg_printf(4, "Setting TTL to 2 for fd%d\n", sock); + val = 2; + if (setsockopt(sock, SOL_IP, IP_MULTICAST_TTL, &val, + sizeof(val))) + printf("warning: setting TTL failed %s\n", strerror(errno)); + + memcpy((struct sockaddr_in *)tgt, &mcast, sizeof(struct sockaddr_in)); + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + + +/** + Sets up a multicast receive (ipv6) socket + */ +int +ipv6_recv_sk(char *addr, int port, unsigned int ifindex) +{ + int sock, val; + struct ipv6_mreq mreq; + struct sockaddr_in6 sin; + + memset(&mreq, 0, sizeof(mreq)); + memset(&sin, 0, sizeof(sin)); + sin.sin6_family = PF_INET6; + sin.sin6_port = htons(port); + if (inet_pton(PF_INET6, addr, + (void *)&sin.sin6_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + + memcpy(&mreq.ipv6mr_multiaddr, &sin.sin6_addr, + sizeof(struct in6_addr)); + + mreq.ipv6mr_interface = ifindex; + + /******************************** + * SET UP MULTICAST RECV SOCKET * + ********************************/ + dbg_printf(4, "Setting up ipv6 multicast receive (%s:%d)\n", addr, port); + sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + printf("socket: %s\n", strerror(errno)); + sock = -1; + return 1; + } + + /* + * When using Multicast, bind to the LOCAL address, not the MULTICAST + * address. + */ + memset(&sin, 0, sizeof(sin)); + sin.sin6_family = PF_INET6; + sin.sin6_port = htons(port); + sin.sin6_addr = in6addr_any; + if (bind(sock, (struct sockaddr *) &sin, + sizeof(struct sockaddr_in6)) < 0) { + printf("bind failed: %s\n", strerror(errno)); + close(sock); + return -1; + } + + dbg_printf(4, "Disabling IP Multicast loopback\n"); + val = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) { + printf("Failed to disable multicast loopback\n"); + close(sock); + return -1; + } + + /* + * Join multicast group + */ + dbg_printf(4, "Joining IP Multicast group\n"); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast to socket %s: %s\n", + addr, strerror(errno)); + close(sock); + return -1; + } + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} + + +/** + Set up ipv6 multicast send socket + */ +int +ipv6_send_sk(char *send_addr, char *addr, int port, struct sockaddr *tgt, + socklen_t tgt_len) +{ + int val; + struct ipv6_mreq mreq; + struct sockaddr_in6 mcast; + struct sockaddr_in6 src; + int sock; + + if (tgt_len < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + memset(&mcast, 0, sizeof(mcast)); + memset(&src, 0, sizeof(src)); + memset(&mreq, 0, sizeof(mreq)); + + /* Store multicast address */ + mcast.sin6_family = PF_INET6; + mcast.sin6_port = htons(port); + if (inet_pton(PF_INET6, addr, + (void *)&mcast.sin6_addr) < 0) { + printf("Invalid multicast address: %s\n", addr); + return -1; + } + + memcpy(&mreq.ipv6mr_multiaddr, &mcast.sin6_addr, + sizeof(struct in6_addr)); + + /* Store sending address */ + src.sin6_family = PF_INET6; + src.sin6_port = htons(port); + if (inet_pton(PF_INET6, send_addr, + (void *)&src.sin6_addr) < 0) { + printf("Invalid source address: %s\n", send_addr); + return -1; + } + + /************************* + * SET UP MULTICAST SEND * + *************************/ + dbg_printf(4, "Setting up ipv6 multicast send (%s:%d)\n", addr, port); + sock = socket(PF_INET6, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + dbg_printf(4, "Disabling IP Multicast loopback\n"); + val = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) { + printf("Failed to disable multicast loopback\n"); + close(sock); + return -1; + } + + /* + * Join Multicast group. + */ + dbg_printf(4, "Joining IP Multicast group\n"); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)) == -1) { + printf("Failed to add multicast membership to transmit " + "socket %s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + + /* + * Join Multicast group (part 2) + */ + /* + if (setsockopt(sock, IPPROTO_IPV6, IP_MULTICAST_IF, &src.sin6_addr, + sizeof(src.sin6_addr)) == -1) { + printf("Failed to bind multicast transmit socket to " + "%s: %s\n", addr, strerror(errno)); + close(sock); + return -1; + } + */ + + /* + * set time to live to 2 hops. + */ + val = 2; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val))) + printf("warning: setting TTL failed %s\n", strerror(errno)); + + memcpy((struct sockaddr_in *)tgt, &mcast, sizeof(struct sockaddr_in6)); + + dbg_printf(4, "%s: success, fd = %d\n", __FUNCTION__, sock); + return sock; +} diff --git a/agents/virt/common/simple_auth.c b/agents/virt/common/simple_auth.c new file mode 100644 index 0000000..9f694c4 --- /dev/null +++ b/agents/virt/common/simple_auth.c @@ -0,0 +1,466 @@ +/* + 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. +*/ + +#include "config.h" + +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sechash.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> + +/* Local includes */ +#include "xvm.h" +#include "fdops.h" +#include "simple_auth.h" +#include "debug.h" + + +static void +print_hash(unsigned char *hash, size_t hashlen) +{ + int x; + + for (x = 0; x < hashlen; x++) + printf("%02x", (hash[x]&0xff)); +} + + +static int +sha_sign(fence_req_t *req, void *key, size_t key_len) +{ + unsigned char hash[SHA512_LENGTH]; + HASHContext *h; + HASH_HashType ht; + unsigned int rlen; + int devrand; + int ret; + + switch(req->hashtype) { + case HASH_SHA1: + ht = HASH_AlgSHA1; + break; + case HASH_SHA256: + ht = HASH_AlgSHA256; + break; + case HASH_SHA512: + ht = HASH_AlgSHA512; + break; + default: + dbg_printf(1, "Unknown hash type: %d\n", req->hashtype); + return -1; + } + + dbg_printf(4, "Opening /dev/urandom\n"); + devrand = open("/dev/urandom", O_RDONLY); + if (devrand < 0) { + dbg_printf(1, "Error: open: /dev/urandom: %s", strerror(errno)); + return -1; + } + + ret = _read_retry(devrand, req->random, sizeof(req->random), NULL); + if (ret <= 0) { + dbg_printf(1, "Error: read: /dev/urandom: %s", strerror(errno)); + close(devrand); + return -1; + } + close(devrand); + + memset(hash, 0, sizeof(hash)); + h = HASH_Create(ht); + if (!h) + return -1; + + HASH_Begin(h); + HASH_Update(h, key, key_len); + HASH_Update(h, (void *)req, sizeof(*req)); + HASH_End(h, hash, &rlen, sizeof(hash)); + HASH_Destroy(h); + + memcpy(req->hash, hash, sizeof(req->hash)); + return 0; +} + + +static int +sha_verify(fence_req_t *req, void *key, size_t key_len) +{ + unsigned char hash[SHA512_LENGTH]; + unsigned char pkt_hash[SHA512_LENGTH]; + HASHContext *h = NULL; + HASH_HashType ht; + unsigned int rlen; + int ret; + + switch(req->hashtype) { + case HASH_SHA1: + ht = HASH_AlgSHA1; + break; + case HASH_SHA256: + ht = HASH_AlgSHA256; + break; + case HASH_SHA512: + ht = HASH_AlgSHA512; + break; + default: + dbg_printf(3, "%s: no-op (HASH_NONE)\n", __FUNCTION__); + return 0; + } + + if (!key || !key_len) { + dbg_printf(3, "%s: Hashing requested when we have no key data\n", + __FUNCTION__); + return 0; + } + + memset(hash, 0, sizeof(hash)); + h = HASH_Create(ht); + if (!h) + return 0; + + memcpy(pkt_hash, req->hash, sizeof(pkt_hash)); + memset(req->hash, 0, sizeof(req->hash)); + + HASH_Begin(h); + HASH_Update(h, key, key_len); + HASH_Update(h, (void *)req, sizeof(*req)); + HASH_End(h, hash, &rlen, sizeof(hash)); + HASH_Destroy(h); + + memcpy(req->hash, pkt_hash, sizeof(req->hash)); + + ret = !memcmp(hash, pkt_hash, sizeof(hash)); + if (!ret) { + printf("Hash mismatch:\nPKT = "); + print_hash(pkt_hash, sizeof(pkt_hash)); + printf("\nEXP = "); + print_hash(hash, sizeof(hash)); + printf("\n"); + } + + return ret; +} + + +int +sign_request(fence_req_t *req, void *key, size_t key_len) +{ + memset(req->hash, 0, sizeof(req->hash)); + switch(req->hashtype) { + case HASH_NONE: + dbg_printf(3, "%s: no-op (HASH_NONE)\n", __FUNCTION__); + return 0; + case HASH_SHA1: + case HASH_SHA256: + case HASH_SHA512: + return sha_sign(req, key, key_len); + default: + break; + } + return -1; +} + + +int +verify_request(fence_req_t *req, fence_hash_t min, + void *key, size_t key_len) +{ + if (req->hashtype < min) { + printf("Hash type not strong enough (%d < %d)\n", + req->hashtype, min); + return 0; + } + switch(req->hashtype) { + case HASH_NONE: + return 1; + case HASH_SHA1: + case HASH_SHA256: + case HASH_SHA512: + return sha_verify(req, key, key_len); + default: + break; + } + return 0; +} + + +static int +sha_challenge(int fd, fence_auth_type_t auth, void *key, + size_t key_len, int timeout) +{ + fd_set rfds; + struct timeval tv; + unsigned char hash[MAX_HASH_LENGTH]; + unsigned char challenge[MAX_HASH_LENGTH]; + unsigned char response[MAX_HASH_LENGTH]; + int devrand; + int ret; + HASHContext *h; + HASH_HashType ht; + unsigned int rlen; + + devrand = open("/dev/urandom", O_RDONLY); + if (devrand < 0) { + dbg_printf(1, "Error: open /dev/urandom: %s", strerror(errno)); + return 0; + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = _read_retry(devrand, challenge, sizeof(challenge), &tv); + if (ret < 0) { + dbg_printf(1, "Error: read: /dev/urandom: %s", strerror(errno)); + close(devrand); + return 0; + } + close(devrand); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = _write_retry(fd, challenge, sizeof(challenge), &tv); + if (ret < 0) { + dbg_printf(2, "Error: write: %s", strerror(errno)); + return 0; + } + + switch(auth) { + case HASH_SHA1: + ht = HASH_AlgSHA1; + break; + case HASH_SHA256: + ht = HASH_AlgSHA256; + break; + case HASH_SHA512: + ht = HASH_AlgSHA512; + break; + default: + return 0; + } + + memset(hash, 0, sizeof(hash)); + h = HASH_Create(ht); + if (!h) + return 0; + + HASH_Begin(h); + HASH_Update(h, key, key_len); + HASH_Update(h, challenge, sizeof(challenge)); + HASH_End(h, hash, &rlen, sizeof(hash)); + HASH_Destroy(h); + + memset(response, 0, sizeof(response)); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (_select_retry(fd + 1, &rfds, NULL, NULL, &tv) <= 0) { + dbg_printf(0, "Error: select: %s\n", strerror(errno)); + return 0; + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = _read_retry(fd, response, sizeof(response), &tv); + if (ret < 0) { + dbg_printf(0, "Error reading challenge response: %s", strerror(errno)); + return 0; + } else if (ret < sizeof(response)) { + dbg_printf(0, + "read data from socket is too short(actual: %d, expected: %zu)\n", + ret, sizeof(response)); + return 0; + } + + ret = !memcmp(response, hash, sizeof(response)); + if (!ret) { + printf("Hash mismatch:\nC = "); + print_hash(challenge, sizeof(challenge)); + printf("\nH = "); + print_hash(hash, sizeof(hash)); + printf("\nR = "); + print_hash(response, sizeof(response)); + printf("\n"); + } + + return ret; +} + + +static int +sha_response(int fd, fence_auth_type_t auth, void *key, + size_t key_len, int timeout) +{ + fd_set rfds; + struct timeval tv; + unsigned char challenge[MAX_HASH_LENGTH]; + unsigned char hash[MAX_HASH_LENGTH]; + HASHContext *h; + HASH_HashType ht; + unsigned int rlen; + int ret; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (_select_retry(fd + 1, &rfds, NULL, NULL, &tv) <= 0) { + dbg_printf(2, "Error: select: %s\n", strerror(errno)); + return 0; + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (_read_retry(fd, challenge, sizeof(challenge), &tv) < 0) { + dbg_printf(2, "Error reading challenge hash: %s\n", strerror(errno)); + return 0; + } + + switch(auth) { + case AUTH_SHA1: + ht = HASH_AlgSHA1; + break; + case AUTH_SHA256: + ht = HASH_AlgSHA256; + break; + case AUTH_SHA512: + ht = HASH_AlgSHA512; + break; + default: + dbg_printf(3, "%s: no-op (AUTH_NONE)\n", __FUNCTION__); + return 0; + } + + memset(hash, 0, sizeof(hash)); + h = HASH_Create(ht); /* */ + if (!h) + return 0; + + HASH_Begin(h); + HASH_Update(h, key, key_len); + HASH_Update(h, challenge, sizeof(challenge)); + HASH_End(h, hash, &rlen, sizeof(hash)); + HASH_Destroy(h); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = _write_retry(fd, hash, sizeof(hash), &tv); + if (ret < 0) { + perror("write"); + return 0; + } else if (ret < sizeof(hash)) { + dbg_printf(2, + "Only part of hash is written(actual: %d, expected: %zu)\n", + ret, + sizeof(hash)); + return 0; + } + + return 1; +} + + +int +sock_challenge(int fd, fence_auth_type_t auth, void *key, size_t key_len, + int timeout) +{ + switch(auth) { + case AUTH_NONE: + dbg_printf(3, "%s: no-op (AUTH_NONE)\n", __FUNCTION__); + return 1; + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + return sha_challenge(fd, auth, key, key_len, timeout); + default: + break; + } + return -1; +} + + +int +sock_response(int fd, fence_auth_type_t auth, void *key, size_t key_len, + int timeout) +{ + switch(auth) { + case AUTH_NONE: + dbg_printf(3, "%s: no-op (AUTH_NONE)\n", __FUNCTION__); + return 1; + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + return sha_response(fd, auth, key, key_len, timeout); + default: + break; + } + return -1; +} + + +int +read_key_file(char *file, char *key, size_t max_len) +{ + int fd; + int nread, remain = max_len; + char *p; + + dbg_printf(3, "Reading in key file %s into %p (%d max size)\n", + file, key, (int)max_len); + fd = open(file, O_RDONLY); + if (fd < 0) { + dbg_printf(2, "Error opening key file: %s\n", strerror(errno)); + return -1; + } + + memset(key, 0, max_len); + p = key; + remain = max_len; + + while (remain) { + nread = read(fd, p, remain); + if (nread < 0) { + if (errno == EINTR) + continue; + dbg_printf(2, "Error from read: %s\n", strerror(errno)); + close(fd); + return -1; + } + + if (nread == 0) { + dbg_printf(3, "Stopped reading @ %d bytes\n", + (int)max_len-remain); + break; + } + + p += nread; + remain -= nread; + } + + close(fd); + dbg_printf(3, "Actual key length = %d bytes\n", (int)max_len-remain); + + return (int)(max_len - remain); +} diff --git a/agents/virt/common/tcp.c b/agents/virt/common/tcp.c new file mode 100644 index 0000000..5796770 --- /dev/null +++ b/agents/virt/common/tcp.c @@ -0,0 +1,386 @@ +/* + Copyright Red Hat, Inc. 2002-2004, 2006 + Copyright Mission Critical Linux, 2000 + + 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. +*/ +/** @file + * + * @author Lon H. Hohberger <lhh at redhat.com> + * @author Jeff Moyer <jmoyer at redhat.com> + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "tcp.h" +#include "debug.h" + +static int connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout); +static int get_addr(const char *hostname, int family, struct sockaddr_storage *addr); + +/** + Set close-on-exec bit option for a socket. + + @param fd Socket to set CLOEXEC flag + @return 0 on success, -1 on failure + @see fcntl + */ +static int +set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD, 0); + flags |= FD_CLOEXEC; + return fcntl(fd, F_SETFD, flags); +} + + +/** + Bind to a port on the local IPv6 stack + + @param addr_str Address to listen on, NULL for inaddr6_any + @param port Port to bind to + @param backlog same as backlog for listen(2) + @return 0 on success, -1 on failure + @see ipv4_bind + */ +int +ipv6_listen(const char *addr_str, uint16_t port, int backlog) +{ + struct sockaddr_in6 _sin6; + int fd, opt=1; + + dbg_printf(4, "%s: Setting up ipv6 listen socket for %s:%d\n", + __FUNCTION__, addr_str, port); + + memset(&_sin6, 0, sizeof(_sin6)); + _sin6.sin6_family = PF_INET6; + _sin6.sin6_port = htons(port); + _sin6.sin6_flowinfo = 0; + + if (addr_str == NULL) { + _sin6.sin6_addr = in6addr_any; + } else { + struct sockaddr_storage ss; + + if (get_addr(addr_str, AF_INET6, &ss) == -1) { + dbg_printf(4, "%s: Can't get addr for %s\n", + __FUNCTION__, addr_str); + return -1; + } + + memcpy(&_sin6.sin6_addr, + &((struct sockaddr_in6 *)&ss)->sin6_addr, sizeof(_sin6.sin6_addr)); + } + + fd = socket(PF_INET6, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof (opt)) < 0) { + close(fd); + return -1; + } + + if (set_cloexec(fd) < 0) { + close(fd); + return -1; + } + + if (bind(fd, (struct sockaddr *)&_sin6, sizeof(_sin6)) < 0) { + close(fd); + return -1; + } + + if (listen(fd, backlog) < 0){ + close(fd); + return -1; + } + + dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd); + return fd; +} + + +/** + Bind to a port on the local IPv4 stack + + @param addr_str Address to listen on, NULL for inaddr_any + @param port Port to bind to + @param backlog same as backlog for listen(2) + @return 0 on success, -1 on failure + @see ipv6_bind + */ +int +ipv4_listen(const char *addr_str, uint16_t port, int backlog) +{ + struct sockaddr_in _sin; + int fd, opt=1; + + dbg_printf(4, "%s: Setting up ipv4 listen socket for %s:%d\n", + __FUNCTION__, addr_str, port); + + _sin.sin_family = PF_INET; + _sin.sin_port = htons(port); + + if (addr_str == NULL) { + _sin.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + struct sockaddr_storage ss; + + if (get_addr(addr_str, AF_INET, &ss) == -1) { + dbg_printf(4, "%s: Can't get addr for %s\n", + __FUNCTION__, addr_str); + return -1; + } + + memcpy(&_sin.sin_addr, + &((struct sockaddr_in *)&ss)->sin_addr, sizeof(_sin.sin_addr)); + } + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof (opt)) < 0) { + close(fd); + return -1; + } + + if (set_cloexec(fd) < 0) { + close(fd); + return -1; + } + + if (bind(fd, (struct sockaddr *)&_sin, sizeof(_sin)) < 0) { + close(fd); + return -1; + } + + if (listen(fd, backlog) < 0) { + close(fd); + return -1; + } + + dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd); + return fd; +} + + + +/** + Connect via ipv6 socket to a given IP address and port. + + @param in6_addr IPv6 address to connect to + @param port Port to connect to + @param timeout Timeout, in seconds, to wait for a completed + connection + @return 0 on success, -1 on failure + @see connect_nb, ipv4_connect + */ +int +ipv6_connect(struct in6_addr *in6_addr, uint16_t port, int timeout) +{ + struct sockaddr_in6 _sin6; + int fd, ret; + + dbg_printf(4, "%s: Connecting to client\n", __FUNCTION__); + fd = socket(PF_INET6, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + memset(&_sin6, 0, sizeof(_sin6)); + _sin6.sin6_family = PF_INET6; + _sin6.sin6_port = htons(port); + _sin6.sin6_flowinfo = 0; + memcpy(&_sin6.sin6_addr, in6_addr, sizeof(_sin6.sin6_addr)); + + ret = connect_nb(fd, (struct sockaddr *)&_sin6, sizeof(_sin6), timeout); + if (ret < 0) { + close(fd); + return -1; + } + dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd); + return fd; +} + + +/** + Connect via ipv4 socket to a given IP address and port. + + @param in_addr IPv4 address to connect to + @param port Port to connect to + @param timeout Timeout, in seconds, to wait for a completed + connection + @return 0 on success, -1 on failure + @see connect_nb, ipv6_connect + */ +int +ipv4_connect(struct in_addr *in_addr, uint16_t port, int timeout) +{ + struct sockaddr_in _sin; + int fd, ret; + + dbg_printf(4, "%s: Connecting to client\n", __FUNCTION__); + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + _sin.sin_family = PF_INET; + _sin.sin_port = htons(port); + memcpy(&_sin.sin_addr, in_addr, sizeof(_sin.sin_addr)); + + ret = connect_nb(fd, (struct sockaddr *)&_sin, sizeof(_sin), timeout); + if (ret < 0) { + close(fd); + return -1; + } + + dbg_printf(4, "%s: Success; fd = %d\n", __FUNCTION__, fd); + return fd; +} + + +/** + Connect in a non-blocking fashion to the designated address. + + @param fd File descriptor to connect + @param dest sockaddr (ipv4 or ipv6) to connect to. + @param len Length of dest + @param timeout Timeout, in seconds, to wait for a completed + connection. + @return 0 on success, -1 on failure. + */ +static int +connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout) +{ + int ret, flags = 1, err; + unsigned l; + fd_set rfds, wfds; + struct timeval tv; + + /* + * Use TCP Keepalive + */ + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, + sizeof(flags))<0) { + return -1; + } + + /* + Set up non-blocking connect + */ + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + return -1; + } + 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) { + 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; + } + /* XXX check for -1 from select */ + + if (FD_ISSET(fd, &rfds) || FD_ISSET(fd, &wfds)) { + l = sizeof(err); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, + (void *)&err, &l) < 0) { + close(fd); + return -1; + } + + if (err != 0) { + close(fd); + errno = err; + return -1; + } + + if (fcntl(fd, F_SETFL, flags) < 0) { + close(fd); + return -1; + } + return 0; + } + } + + errno = EIO; + return -1; +} + +static int +get_addr(const char *hostname, int family, struct sockaddr_storage *addr) +{ + struct addrinfo *res; + size_t len; + struct addrinfo ai; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = family; + + if (getaddrinfo(hostname, NULL, &ai, &res) != 0) + return -1; + + switch (res->ai_addr->sa_family) { + case AF_INET: + len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + len = sizeof(struct sockaddr_in6); + break; + default: + goto out_fail; + } + + if (len < (size_t) res->ai_addrlen) + goto out_fail; + + memcpy(addr, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + + return 0; + +out_fail: + freeaddrinfo(res); + return -1; +} diff --git a/agents/virt/config/Makefile.am b/agents/virt/config/Makefile.am new file mode 100644 index 0000000..19d9742 --- /dev/null +++ b/agents/virt/config/Makefile.am @@ -0,0 +1,44 @@ +############################################################################### +############################################################################### +## +## Copyright (C) 2009-2019 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 + +EXTRA_DIST = config.l config.y fence_virt.conf + +noinst_LIBRARIES = libsimpleconfig.a + +libsimpleconfig_a_SOURCES = \ + simpleconfig.c + +nodist_libsimpleconfig_a_SOURCES = \ + y.tab.c \ + config.c + +libsimpleconfig_a_CFLAGS = $(VIRT_AM_CFLAGS) $(AM_CFLAGS) -Wno-unused + +noinst_HEADERS = config-stack.h + + +sysconf_DATA = fence_virt.conf + +# local rules +y.tab.c: config.y + $(YACC) -d $^ + +config.c: y.tab.c config.l + $(LEX) -oconfig.c $(srcdir)/config.l + +install-exec-hook: + chmod 600 $(DESTDIR)$(sysconfdir)/fence_virt.conf + +clean-local: + rm -f config.tab.c config.tab.h config.c y.tab.c y.tab.h diff --git a/agents/virt/config/config-stack.h b/agents/virt/config/config-stack.h new file mode 100644 index 0000000..1eb3cfa --- /dev/null +++ b/agents/virt/config/config-stack.h @@ -0,0 +1,38 @@ +#ifndef _CONFIG_STACK_H +#define _CONFIG_STACK_H + +int yyparse (void); +extern FILE *yyin; + +struct value { + char *id; + char *val; + struct value *next; +}; + + +struct node { + char *id; + char *val; + struct node *nodes; + struct value *values; + struct node *next; +}; + + +struct parser_context { + struct value *val_list; + struct node *node_list; + struct parser_context *next; +}; + +extern struct value *val_list; +extern struct node *node_list; +extern struct parser_context *context_stack; + +int _sc_value_add(char *id, char *val, struct value **list); +int _sc_node_add(char *id, char *val, struct value *vallist, + struct node *nodelist, struct node **list); + + +#endif diff --git a/agents/virt/config/config.l b/agents/virt/config/config.l new file mode 100644 index 0000000..78462f3 --- /dev/null +++ b/agents/virt/config/config.l @@ -0,0 +1,106 @@ +%{ +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include "config-stack.h" +#include "y.tab.h" +#include "simpleconfig.h" + +/* Some distributions can't use the output from flex without help */ +#define ECHO if(fwrite( yytext, yyleng, 1, yyout )) + +struct value *val_list = NULL; +struct node *node_list = NULL; +struct parser_context *context_stack = NULL; + +int _line_count = 1; + +%} +%% +[\n] { + ++_line_count; +} + +[ \t]* {} + +\#[^\n]* {} + +"{" { + struct parser_context *c = NULL; + //printf("obrace\n"); + + c = malloc(sizeof(*c)); + assert(c); + + c->next = context_stack; + c->val_list = val_list; + c->node_list = node_list; + + context_stack = c; + val_list = NULL; + node_list = NULL; + + return T_OBRACE; +} + +"}" { + return T_CBRACE; +} + +";" { + return T_SEMI; +} + +"=" { + return T_EQ; +} + +[^ \t{};=\"\n]+ { + yylval.sval = strdup(yytext); + return T_ID; +} + +\"[^\"]+\" { + yylval.sval = strdup(yytext+1); + yylval.sval[strlen(yytext)-2] = 0; + return T_VAL; +} + +%% +int +yywrap(void) +{ + return 1; +} + + +#ifdef STANDALONE +int +main(int argc, char *argv[]) +{ + char value[80]; + config_object_t *c = NULL; + + yyout = fopen("/dev/null","w"); + + c = sc_init(); + sc_parse(c, NULL); + sc_dump(c, stdout); + if (argc == 2) { + if (sc_get(c, argv[1], value, sizeof(value)) == 0) + printf("%s = %s\n", argv[1], value); + else + printf("Not found\n"); + } else if (argc == 3) { + printf("---------\n"); + if (sc_set(c, argv[1], argv[2]) == 0) + sc_dump(c, stdout); + } + + sc_release(c); + + return 0; +} +#endif diff --git a/agents/virt/config/config.y b/agents/virt/config/config.y new file mode 100644 index 0000000..2ae0380 --- /dev/null +++ b/agents/virt/config/config.y @@ -0,0 +1,140 @@ +%{ +#include "config.h" +#include <stdio.h> +#include <malloc.h> +#include <string.h> +#include <assert.h> +#include "config-stack.h" + +extern int yylex (void); +int yyerror(const char *foo); + +int +_sc_value_add(char *id, char *val, struct value **list) +{ + struct value *v; + + v = malloc(sizeof(*v)); + assert(v); + + memset(v, 0, sizeof(*v)); + v->id = id; + v->val = val; + //snprintf(v->id, sizeof(v->id), "%s", id); + //snprintf(v->val, sizeof(v->val), "%s", val); + //printf("add %s %s on to %p\n", id, val, *list); + + v->next = *list; + *list = v; + + //printf("new list %p\n", *list); + return 0; +} + + +int +_sc_node_add(char *id, char *val, struct value *vallist, + struct node *nodelist, struct node **list) +{ + struct node *n; + + n = malloc(sizeof(*n)); + assert(n); + + //printf("nodes %p values %p\n", nodelist, vallist); + + memset(n, 0, sizeof(*n)); + //snprintf(n->id, sizeof(n->id), "%s", id); + n->id = id; /* malloc'd during parsing */ + n->val = val; /* malloc'd during parsing */ + n->values = vallist; + n->nodes = nodelist; + n->next = *list; + *list = n; + + return 0; +} + +%} + +%token <sval> T_ID +%token <sval> T_VAL +%token T_OBRACE T_CBRACE T_EQ T_SEMI + +%start stuff + +%union { + char *sval; + int ival; +} + +%% +node: + T_ID T_OBRACE stuff T_CBRACE { + struct parser_context *c = NULL; + + c = context_stack; + _sc_node_add($1, NULL, val_list, node_list, &c->node_list); + val_list = c->val_list; + node_list = c->node_list; + context_stack = c->next; + + free(c); + } + | + T_ID T_EQ T_VAL T_OBRACE stuff T_CBRACE { + struct parser_context *c = NULL; + + c = context_stack; + _sc_node_add($1, $3, val_list, node_list, &c->node_list); + val_list = c->val_list; + node_list = c->node_list; + context_stack = c->next; + + free(c); + } + | + T_ID T_OBRACE T_CBRACE { + struct parser_context *c = NULL; + + c = context_stack; + _sc_node_add($1, NULL, val_list, node_list, &c->node_list); + val_list = c->val_list; + node_list = c->node_list; + context_stack = c->next; + + free(c); + } + | + T_ID T_EQ T_VAL T_OBRACE T_CBRACE { + struct parser_context *c = NULL; + + c = context_stack; + _sc_node_add($1, $3, val_list, node_list, &c->node_list); + val_list = c->val_list; + node_list = c->node_list; + context_stack = c->next; + + free(c); + } + ; + +stuff: + node stuff | assign stuff | node | assign + ; + +assign: + T_ID T_EQ T_VAL T_SEMI { + _sc_value_add($1, $3, &val_list); + } + ; +%% + +extern int _line_count; + +int +yyerror(const char *foo) +{ + printf("%s on line %d\n", foo, _line_count); + return 0; +} diff --git a/agents/virt/config/fence_virt.conf b/agents/virt/config/fence_virt.conf new file mode 100644 index 0000000..03e2c58 --- /dev/null +++ b/agents/virt/config/fence_virt.conf @@ -0,0 +1,20 @@ +fence_virtd { + listener = "multicast"; + backend = "libvirt"; +} + +listeners { + multicast { + key_file = "/etc/cluster/fence_xvm.key"; + address = "225.0.0.12"; + # Needed on Fedora systems + interface = "virbr0"; + } +} + +backends { + libvirt { + uri = "qemu:///system"; + } +} + diff --git a/agents/virt/config/simpleconfig.c b/agents/virt/config/simpleconfig.c new file mode 100644 index 0000000..3315b32 --- /dev/null +++ b/agents/virt/config/simpleconfig.c @@ -0,0 +1,494 @@ +#include "config.h" + +#include <stdio.h> +#include <pthread.h> +#include <string.h> +#include <malloc.h> +#include <stdlib.h> +#include <assert.h> + +#include "simpleconfig.h" +#include "config-stack.h" +#include "static_map.h" + + +static pthread_mutex_t parser_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int +print_value(struct value *v, int depth, FILE *fp) +{ + int x; + + if (v->val == NULL) + return 0; + + for (x = 0; x < depth; x++) + fprintf(fp, "\t"); + fprintf(fp, "%s = \"%s\";\n", v->id, v->val); + + return 0; +} + + +static void +_sc_dump_d(struct node *node, int depth, FILE *fp) +{ + struct node *n; + struct value *v; + int x; + + if (!node) { + //printf("Empty node\n"); + return; + } + + for (x = 0; x < depth; x++) + fprintf(fp, "\t"); + + if (node->val) { + fprintf(fp, "%s = \"%s\" {\n", node->id, node->val); + } else { + fprintf(fp, "%s {\n", node->id); + } + + for (n = node->nodes; n; n = n->next) { + _sc_dump_d(n, depth+1, fp); + } + + for (v = node->values; v; v = v->next) { + print_value(v, depth+1, fp); + } + + for (x = 0; x < depth; x++) + fprintf(fp, "\t"); + fprintf(fp, "}\n\n"); +} + + +static void +_sc_dump(void *config, FILE *fp) +{ + struct node *n, *node; + struct value *v, *values; + + if (!config) + return; + + values = ((struct parser_context *)config)->val_list; + node = ((struct parser_context *)config)->node_list; + + for (n = node; n; n = n->next) { + _sc_dump_d(n, 0, fp); + } + + for (v = values; v; v = v->next) { + print_value(v, 0, fp); + } +} + + +static int +free_value(struct value *v) +{ + if (v) { + free(v->id); + free(v->val); + free(v); + } + + return 0; +} + + +static void +_sc_free_node(struct node *node) +{ + struct node *n; + struct value *v; + + if (!node) + return; + + while (node->nodes) { + n = node->nodes; + if (n) { + node->nodes = node->nodes->next; + _sc_free_node(n); + } + } + + while (node->values) { + v = node->values; + node->values = node->values->next; + free_value(v); + } + + free(node->id); + free(node); +} + + +static int +_sc_free(void *config) +{ + struct node *n, *nlist; + struct value *v, *vlist; + + if (!config) + return -1; + + vlist = ((struct parser_context *)config)->val_list; + nlist = ((struct parser_context *)config)->node_list; + + while (nlist) { + n = nlist; + nlist = nlist->next; + _sc_free_node(n); + } + + ((struct parser_context *)config)->node_list = NULL; + + while (vlist) { + v = vlist; + vlist = vlist->next; + free_value(v); + } + + ((struct parser_context *)config)->val_list = NULL; + + free(config); + + return 0; +} + + +static int +_sc_get(void *config, const char *key, char *value, size_t valuesz) +{ + char buf[1024]; + struct node *n, *node; + struct value *v, *values; + char *ptr; + char *slash; + char *bracket; + char *id; + int req_index = 0; + int curr_index = 0; + int found; + + if (!config) + return -1; + + node = ((struct parser_context *)config)->node_list; + values = ((struct parser_context *)config)->val_list; + + assert(strlen(key) < sizeof(buf)); + + ptr = (char *)key; + + while ((slash = strchr(ptr, '/'))) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, ptr, (slash - ptr)); + ptr = ++slash; + + id = NULL; + bracket = strchr(buf, '['); + if (bracket) { + *bracket = 0; + ++bracket; + + id = bracket; + + bracket = strchr(bracket, ']'); + if (!bracket) + return 1; + *bracket = 0; + + if (id[0] == '@') { + ++id; + if (!strlen(id)) { + return 1; + } + } else { + req_index = atoi(id); + if (req_index <= 0) + return 1; + id = NULL; + } + } + + found = 0; + curr_index = 0; + + for (n = node; n; n = n->next) { + + if (strcasecmp(n->id, buf)) + continue; + + ++curr_index; + + if (req_index && (curr_index != req_index)) { + continue; + } else if (id && strcasecmp(n->val, id)) { + continue; + } + + node = n->nodes; + values = n->values; + found = 1; + break; + } + + if (!found) + return 1; + } + + if (ptr[0] != '@') { + + strncpy(buf, ptr, sizeof(buf) - 1); + id = NULL; + bracket = strchr(buf, '['); + if (bracket) { + *bracket = 0; + ++bracket; + + id = bracket; + + bracket = strchr(bracket, ']'); + if (!bracket) + return 1; + *bracket = 0; + + if (id[0] == '@') { + ++id; + if (!strlen(id)) { + return 1; + } + } else { + req_index = atoi(id); + if (req_index <= 0) + return 1; + id = NULL; + } + } + + found = 0; + curr_index = 0; + + for (n = node; n; n = n->next) { + + if (strcasecmp(n->id, buf)) + continue; + + ++curr_index; + + if (req_index && (curr_index != req_index)) { + continue; + } else if (id && strcasecmp(n->val, id)) { + continue; + } + if (node->val) { + strncpy(value, node->val, valuesz); + return 0; + } + return 1; + } + } + + ++ptr; + found = 0; + id = NULL; + + strncpy(buf, ptr, sizeof(buf) - 1); + bracket = strchr(buf, '['); + + req_index = 0; + curr_index = 0; + + if (bracket) { + *bracket = 0; + ++bracket; + + id = bracket; + + bracket = strchr(bracket, ']'); + if (!bracket) + return 1; + *bracket = 0; + + req_index = atoi(id); + if (req_index <= 0) + return 1; + id = NULL; + } + + for (v = values; v; v = v->next) { + + if (strcasecmp(v->id, buf)) + continue; + + ++curr_index; + if (req_index && (curr_index != req_index)) + continue; + snprintf(value, valuesz, "%s", v->val); + return 0; + } + + return 1; +} + + +static int +_sc_set(void *config, const char *key, const char *value) +{ + char buf[1024]; + struct node *n, **nodes = &((struct parser_context *)config)->node_list; + struct value *v, **values = &((struct parser_context *)config)->val_list; + char *ptr; + char *slash; + char *id_dup, *val_dup; + int found = 0; + + ptr = (char *)key; + while ((slash = strchr(ptr, '/'))) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, ptr, (slash - ptr)); + ptr = ++slash; + found = 0; + + for (n = *nodes; n; n = n->next) { + if (strcasecmp(n->id, buf)) + continue; + + nodes = &n->nodes; + values = &n->values; + found = 1; + break; + } + + if (!found) { + id_dup = strdup(buf); + if (!id_dup) + return -1; + _sc_node_add(id_dup, NULL, NULL, NULL, nodes); + n = *nodes; + nodes = &n->nodes; + values = &n->values; + } + } + + if (ptr[0] != '@') + return 1; + ++ptr; + + for (v = *values; v; v = v->next) { + if (strcasecmp(v->id, ptr)) + continue; + + ptr = v->val; + if (value) { + v->val = strdup(value); + if (!v->val) { + v->val = ptr; + return -1; + } + } else { + v->val = NULL; + } + free(ptr); + + return 0; + } + + if (!value) + return 0; + + id_dup = strdup(ptr); + if (!id_dup) + return -1; + + val_dup = strdup(value); + if (!val_dup) { + free(id_dup); + return -1; + } + _sc_value_add(id_dup, val_dup, values); + + return 0; +} + + +static int +_sc_parse(const char *filename, void **config) +{ + struct parser_context *c; + FILE *fp = NULL; + int ret = 0; + + if (!config) + return -1; + + pthread_mutex_lock(&parser_mutex); + if (filename) { + fp = fopen(filename, "r"); + yyin = fp; + if (fp) + ret = yyparse(); + else + ret = 1; + } else { + ret = 1; + } + + c = malloc(sizeof(*c)); + if (!c) { + ret = -1; + goto out_unlock; + } + c->node_list = node_list; + c->val_list = val_list; + c->next = NULL; + val_list = NULL; + node_list = NULL; + *config = (void *)c; + + if (fp) + fclose(fp); + +out_unlock: + pthread_mutex_unlock(&parser_mutex); + return ret; +} + + +static const config_object_t sc_object = { + .get = _sc_get, + .set = _sc_set, + .parse = _sc_parse, + .free = _sc_free, + .dump = _sc_dump, + .info = NULL +}; + + +config_object_t * +sc_init(void) +{ + config_object_t *o; + + o = malloc(sizeof(*o)); + if (!o) + return NULL; + memset(o, 0, sizeof(*o)); + memcpy(o, &sc_object, sizeof(*o)); + + return o; +} + + +void +sc_release(config_object_t *c) +{ + sc_free(c); + free(c); +} diff --git a/agents/virt/docs/README b/agents/virt/docs/README new file mode 100644 index 0000000..e2b19bc --- /dev/null +++ b/agents/virt/docs/README @@ -0,0 +1,125 @@ +TODO: update + +I. Fence_xvm - Virtual machine fencing agent + +Fence_xvm is an agent which establishes a communications link between +a cluster of virtual machines (VC) and a cluster of domain0/physical +nodes which are hosting the virtual cluster. Its operations are +fairly simple. + + (a) Start a listener service. + (b) Send a multicast packet requesting that a VM be fenced. + (c) Authenticate client. + (e) Read response. + (f) Exit with success/failure, depending on the response received. + +If any of the above steps fail, the fencing agent exits with a failure +code and fencing is retried by the virtual cluster at a later time. +Because of the simplicty of fence_xvm, it is not necessary that +fence_xvm be run from within a virtualized guest - all it needs is +libnspr and libnss and a shared private key (for authentication; we +would hate to receive a false positive response from a node not in the +cluster!). + + +II. Fence_virtd - Virtual machine fencing host + +Fence_virtd is a daemon which runs on physical hosts (e.g. in domain0) +of the cluster hosting the virtual cluster. It listens on a port +for multicast traffic from virtual cluster(s), and takes actions. +Multiple disjoint virtual clusters can coexist on a single physical +host cluster, but this requires multiple instances of fence_virtd. + +NOTE: fence_virtd *MUST* be run on ALL nodes in a given cluster which +will be hosting virtual machines if fence_xvm is to be used for +fencing! + +There are a couple of ways the multicast packet is handled, +depending on the state of the host OS. It might be hosting the VM, +or it might not. Furthermore, the VM might "reside" on a host which +has failed. + +In order to be able to guarantee safe fencing of a VM even if the +last- known host is down, we must store the last-known locations of +each virtual machine in some sort of cluster-wide way. For this, we +use the corosync CPG API. Every few seconds, fence_virtd queries the +hypervisor via libvirt and stores any local VM states and sends those +states over CPG to all other members. In the event of a physical node +failure (which consequently causes the failure of one or more guests), +we can then read the stored VM state corresponding to the guest we need +to fence to find out the previous owner. With that information, we can +infer if the known host node has been fenced. If so, then the VM is clean +as well. The physical cluster must, therefore, have fencing in order for +fence_virtd to work. + +Operation of a node hosting a VM which needs to be fenced: + + (a) Receive multicast packet + (b) Authenticate multicast packet + (c) Open connection to host contained within multicast + packet. + (d) Authenticate server. + (e) Carry out fencing operation (e.g. call libvirt to destroy or + reboot the VM; there is no "on" method at this point). + (f) If operation succeeds, send success response. + +Operation of high-node-ID: + + (a) Receive multicast packet + (b) Authenticate multicast packet + (c) Read VM state from stored CPG messages + (d) Check liveliness of nodeID hosting VM (if alive, do nothing) + (e) Open connection to host contained within multicast + packet. + (f) Check with CMAN to see if last-known host has been fenced. + (g) If last-known host has been fenced, send success response. + (h) Authenticate server & send response. + +NOTE: There is always a possibility that a VM is started again +before the fencing operation and CPG update for that VM +occurs. If the VM has booted and rejoined the cluster, fencing will +not be necessary. If it is in the process of booting, but has not +yet joined the cluster, fencing will also not be necessary - because +it will not be using cluster resources yet. + + +III. Security considerations + +While fencing is generally expected to run on a more or less trusted +network, there are cases where it may not be. + +* The multicast packet is subject to replay attacks, but because no +fencing action is taken based solely on the information contained +within the packet, this should not allow an attacker to maliciously +fence a VM from outside the cluster, though it may be possible to +cause a DoS of fence_virtd if enough multicast packets are sent. + +* The only currently supported authentication mechanisms are simple +challenge-response based on a shared private key and pseudorandom +number generation. + +* An attacker with access to the shared key(s) can easily fence any +known VM, even if they are not on a cluster node. + +* Different shared keys should be used for different virtual +clusters on the same subnet (whether in the same physical cluster +or not). Additionally, multiple fence_virtd instances must be run +(each listening on a different multicast IP + port combination). + +IV. Configuration + +Generate a random key file. An example of how to generate it is: + + dd if=/dev/urandom of=/etc/cluster/fence_xvm.key bs=4096 count=1 + +Distribute the generated key file to all domUs in a cluster as well +as all dom0s which will be hosting that particular cluster of domUs. +The key should not be placed on shared file systems (because shared +file systems require the cluster, which requires fencing...). + +Start fence_virtd on all hosts + +Configure fence_xvm on the domU cluster... + +rest...tbd + diff --git a/agents/virt/docs/TODO b/agents/virt/docs/TODO new file mode 100644 index 0000000..17456cf --- /dev/null +++ b/agents/virt/docs/TODO @@ -0,0 +1,7 @@ +High Priority / Blockers for v1.0; + +* endian-clean / 64-bit clean data structure analysis + +Future Stuff: + +* clean up development bits so third parties can develop plugins diff --git a/agents/virt/docs/architecture.txt b/agents/virt/docs/architecture.txt new file mode 100644 index 0000000..54fda11 --- /dev/null +++ b/agents/virt/docs/architecture.txt @@ -0,0 +1,16 @@ +The actual architecture of fence_virtd is very simple. We have a set +of listener plugins which listens for fencing requests for virtual +machines. + +These plugins are assigned callbacks which are entry functions in to +the backend plugins. The backend plugins perform the actual fencing +request. + +In the middle, we have only enough code to provide basic integration +functions between the listener and backend plugins. This includes a +very simple confiugration plugin which we pass to each of the plugins. + +Because we are passing function pointers in to the plugins themselves +for configuration (rather than having the plugins call an API directly, +for example), we are able to swap out the configuration subsystem for +other, more full-featured configuration systems, such as libccs. diff --git a/agents/virt/docs/fence_virt.txt b/agents/virt/docs/fence_virt.txt new file mode 100644 index 0000000..e554ce4 --- /dev/null +++ b/agents/virt/docs/fence_virt.txt @@ -0,0 +1,127 @@ +We need a fencing agent which can work in a variety of guest cluster +configurations and host configurations. + +Requirements + +1. Nonrequirement of guest to host networking. Virtual machines + may be configured to run using a nework unknown to the host + operating system. Therefore, the ability to run without network + communication between the guest and the hsot is required. + +2. Ease of configuration. The absolute minimum possible configuration + must be available. + +3. Nonrequirement of host clustering software. Multiple layers of + configuration sucks. While I fundamentally disagree with the general + idea that running CMAN on the host constitutes a "heavyweight + cluster", perception is important. + +4. Ability to support RHEV-M, oVirt server, and other virtual machine + management technologies. This is beneficial from a security standpoint + since it is assumed the management server will be aware of what VMs + are allowed to fence what other VMs. + +5. Upgrade compatibility with fence_xvm from a configuration standpoint. + This may be provided by a symlink over fence_xvm. If this feature + can not be provided as a matter of design, a method to convert an + existing fence_xvm/fence_xvmd configuration to fence_virt must be + present. + + +Guest to Host Interaction +------------------------- + +The proposal is to use various communications media plugins in order +to facilitate flexibility with respect to how virtual machine +environments are configured. + +There are at least 3 simple plugins for guest/client to host/server +communications: + + * Direct serial. The guest sends fencing requests out via /dev/ttySX + in the guest. The host is listening on a Unix domain socket[1], + and forwards fencing requests accordingly. + + This satisifies most of the requirements, but adds a conundrum + when configuring guest clusters, as /dev/ttySX may be /dev/ttySY + on another guest. So, either we must account for this per-guest + configuration discrepancy or we must make it an administrative + requirement to provide the same serial device on each host + + * Multicast. This violates the networking requirement, but this is + okay since this method of operation is optional. This operational + mode provides for one of the simpler configurations: all that is + needed is the guest's name or UUID. The guest to host + communications operates in the same manner as fence_xvm/fence_xvmd, + except that there is an implied requirement on restricting the + multicast packets accepted to be from the local guests. + + * VM Channel over Serial. This works like direct serial, but + instead of owning the whole device, the device may be shared between + multiple applications. The server subscribes to a channel and + listens for fencing requests on the channel; the client in the + guest OS connects to the channel and issues fencing requests across + it. One interesting thing is that it may be possible to provide + unprivileged users the ability to fence using this method (I + do not claim to know if this is useful or not). + + +Host to Hypervisor interaction +------------------------------ + +Similar to the way we have plugins for guest to host interaction, +we also have plugins which actually do the real work. These plugins +are responsible for all of the actual real work performed, including +tracking VMs if required, forwarding requests to the appropriate hosts +or management services, and handling the responses. + +We propose at 5 plugins in this case: + + * Libvirt (local-only). There is no intracommunication and no + migration support is provided + + * Cluster CPG (+ libvirt). This the way fence_xvmd + operates today. This setup has the most requirements on the + infrastructure, as it requires guest to host networking _and_ + host-to-host clustering in order to keep track of virtual + machines. The benefit is that it is self-contained and requires + no external management nodes. VM states are stored so that other + CPG group members know the locations of other VMs and can make + some decisions about whether a VM is dead based on whether a host + is dead (i.e. if fencing is in use or can be performed on the + host). + + * Libvirt-QMF ... ??? Subscription to the appropriate cluster + specific AMQP channel is required on the host side, but this + handles routing the message very easily. The fencing request + is forwarded to the other listeners on the channel, the VM owner + takes the action requested and returns a value. When new VMs + are created, the event is broadcast out via the AMQP channel so + other hosts know the locations of other VMs and can make some + decisions about whether a VM is dead based on whether a host + is dead (i.e. if fencing is in use or can be performed on the + host). + + * oVirt Manager. The request is forwarded to the oVirt Manager + and the oVirt manager is responsible for taking the appropriate + action and responding to the request. + + * RHEV-M. The request is forwarded to the RHEV-M node, which is + responsible for taking the appropriate action and responding to + the request. + + +These plugins have no requirements on which guest to host communication +plugin is used (you could, if you wanted, use 'direct serial' with +'cluster cpg', or 'multicast' with 'RHEV-H' for example). + +These plugins must also be able to discover where appropriate. For +example, the cpg plugin can only be used if corosync/openais +is running. A defined plugin preference order should be specified/documented +so that the host daemon behaves in a predictable manner in absence of +host-side configuration data (about which plugin to use). + + +[1] TCP was also explored, however, the security is much better + using a Unix domain socket, despite the additional complexity + of listening for VM creation events. diff --git a/agents/virt/fence_virtd.service.in b/agents/virt/fence_virtd.service.in new file mode 100644 index 0000000..9d2836f --- /dev/null +++ b/agents/virt/fence_virtd.service.in @@ -0,0 +1,23 @@ +[Unit] +Description=Fence-Virt system host daemon +Documentation=man:fence_virtd(8) +Documentation=man:fence_virt.conf(5) + +After=basic.target +After=network.target +After=syslog.target +After=libvirtd.service +After=corosync.service + +Requires=basic.target +Requires=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=forking +Restart=on-failure +Environment="FENCE_VIRTD_ARGS=-w" + +# Autogenerated below here diff --git a/agents/virt/include/bcast.h b/agents/virt/include/bcast.h new file mode 100644 index 0000000..5113f04 --- /dev/null +++ b/agents/virt/include/bcast.h @@ -0,0 +1,16 @@ +#ifndef _XVM_MCAST_H +#define _XVM_MCAST_H + +#define IPV4_MCAST_DEFAULT "225.0.0.12" +#define IPV6_MCAST_DEFAULT "ff05::3:1" + +int ipv4_recv_sk(char *addr, int port); +int ipv4_send_sk(char *src_addr, char *addr, int port, + struct sockaddr *src, socklen_t slen, + int ttl); +int ipv6_recv_sk(char *addr, int port); +int ipv6_send_sk(char *src_addr, char *addr, int port, + struct sockaddr *src, socklen_t slen, + int ttl); + +#endif diff --git a/agents/virt/include/client.h b/agents/virt/include/client.h new file mode 100644 index 0000000..48b92c3 --- /dev/null +++ b/agents/virt/include/client.h @@ -0,0 +1,9 @@ +#ifndef _CLIENT_H +#define _CLIENT_H + +int tcp_fence_virt(fence_virt_args_t *args); +int serial_fence_virt(fence_virt_args_t *args); +int mcast_fence_virt(fence_virt_args_t *args); +int vsock_fence_virt(fence_virt_args_t *args); +void do_read_hostlist(int fd, int timeout); +#endif diff --git a/agents/virt/include/debug.h b/agents/virt/include/debug.h new file mode 100644 index 0000000..d6a5416 --- /dev/null +++ b/agents/virt/include/debug.h @@ -0,0 +1,31 @@ +/* + Copyright Red Hat, Inc. 2007 + + 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 _DBG_H +#define _DBG_H + +void dset(int); +int dget(void); + +#define dbg_printf(level, fmt, args...) \ +do { \ + if (dget()>=level) \ + printf(fmt, ##args); \ +} while(0) + +#endif diff --git a/agents/virt/include/fdops.h b/agents/virt/include/fdops.h new file mode 100644 index 0000000..f3194de --- /dev/null +++ b/agents/virt/include/fdops.h @@ -0,0 +1,14 @@ +#ifndef _FDOPS_H +#define _FDOPS_H +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> + +int _select_retry(int fdmax, fd_set * rfds, fd_set * wfds, fd_set * xfds, + struct timeval *timeout); +ssize_t _write_retry(int fd, void *buf, int count, struct timeval * timeout); +ssize_t _read_retry(int sockfd, void *buf, int count, + struct timeval * timeout); + +#endif diff --git a/agents/virt/include/history.h b/agents/virt/include/history.h new file mode 100644 index 0000000..3d28518 --- /dev/null +++ b/agents/virt/include/history.h @@ -0,0 +1,25 @@ +#ifndef _HISTORY_H +#define _HISTORY_H + +typedef struct _history_node { + list_head(); + void *data; + time_t when; +} history_node; + +typedef int (*history_compare_fn)(void *, void *); + +typedef struct _history_info { + history_node *hist; + history_compare_fn compare_func; + time_t timeout; + size_t element_size; +} history_info_t; + +history_info_t *history_init(history_compare_fn func, + time_t expiration, size_t element_size); +int history_check(history_info_t *hinfo, void *stuff); +int history_record(history_info_t *hinfo, void *data); +int history_wipe(history_info_t *hinfo); + +#endif diff --git a/agents/virt/include/ip_lookup.h b/agents/virt/include/ip_lookup.h new file mode 100644 index 0000000..3386c71 --- /dev/null +++ b/agents/virt/include/ip_lookup.h @@ -0,0 +1,40 @@ +/* + Copyright Red Hat, Inc. 2004,2006 + + 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 + * Header for ip_lookup.c + */ +#ifndef _IP_LOOKUP_H +#define _IP_LOOKUP_H + +#include <sys/queue.h> + +typedef struct _ip_address { + TAILQ_ENTRY(_ip_address) ipa_entries; + char ipa_family; + char *ipa_address; +} ip_addr_t; + +typedef TAILQ_HEAD(_ip_list, _ip_address) ip_list_t; + +int ip_search(ip_list_t *ipl, char *ip_name); +int ip_free_list(ip_list_t *ipl); +int ip_build_list(ip_list_t *ipl); +int ip_lookup(char *, struct addrinfo **); + +#endif diff --git a/agents/virt/include/list.h b/agents/virt/include/list.h new file mode 100644 index 0000000..01340d6 --- /dev/null +++ b/agents/virt/include/list.h @@ -0,0 +1,84 @@ +#ifndef _LIST_H +#define _LIST_H + +/** + Simple list handlig macros. + Needs rewrite or inclusion of /usr/include/linux/list.h as a replacement. + */ + +/* Must be first if structure is going to use it. */ +struct list_entry { + struct list_entry *le_next, *le_prev; +}; + +#define list_head() struct list_entry _list_head + +#define le(p) (&((*p)._list_head)) + +#define list_insert(list, newnode) \ +do { \ + if (!(*list)) { \ + le(newnode)->le_next = \ + le(newnode)->le_prev = le(newnode); \ + *list = (void *)le(newnode); \ + } else { \ + le(*list)->le_prev->le_next = le(newnode); \ + le(newnode)->le_next = le(*list); \ + le(newnode)->le_prev = le(*list)->le_prev; \ + le(*list)->le_prev = le(newnode); \ + } \ +} while (0) + + +#define list_prepend(list, newnode) \ +do { \ + list_insert(list, newnode); \ + *list = newnode; \ +} while (0) + + +#define list_remove(list, oldnode) \ +do { \ + if (le(oldnode) == le(*list)) { \ + *list = (void *)le(*list)->le_next; \ + } \ + if (le(oldnode) == le(*list)) { \ + le(oldnode)->le_next = NULL; \ + le(oldnode)->le_prev = NULL; \ + *list = NULL; \ + } else { \ + le(oldnode)->le_next->le_prev = le(oldnode)->le_prev; \ + le(oldnode)->le_prev->le_next = le(oldnode)->le_next; \ + le(oldnode)->le_prev = NULL; \ + le(oldnode)->le_next = NULL; \ + } \ +} while (0) + + +/* + * list_for(list, tmp, counter) { + * stuff; + * } + * + * counter = # of items in list when done. + * * sets cnt to 0 before even checking list; + * * checks for valid list + * * traverses list, incrementing counter. If we get to the for loop, + * there must be at least one item in the list + * * cnt ends up being the number of items in the list. + */ +#define list_for(list, curr, cnt) \ + if ((!(cnt=0)) && (*list != NULL)) \ + for (curr = *list; \ + (cnt == 0) || (curr != *list); \ + curr = (void*)le(curr)->le_next, \ + cnt++) + +#define list_for_rev(list, curr, cnt) \ + if ((!(cnt=0)) && (*list != NULL)) \ + for (curr = (void *)(le(*list)->le_prev); \ + (cnt == 0) || ((void *)curr != le(*list)->le_prev); \ + curr = (void*)(le(curr)->le_prev), \ + cnt++) + +#endif diff --git a/agents/virt/include/mcast.h b/agents/virt/include/mcast.h new file mode 100644 index 0000000..f86ad96 --- /dev/null +++ b/agents/virt/include/mcast.h @@ -0,0 +1,32 @@ +/* + 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. +*/ +#ifndef _XVM_MCAST_H +#define _XVM_MCAST_H + +#define IPV4_MCAST_DEFAULT "225.0.0.12" +#define IPV6_MCAST_DEFAULT "ff05::3:1" + +int ipv4_recv_sk(char *addr, int port, unsigned int ifindex); +int ipv4_send_sk(char *src_addr, char *addr, int port, + struct sockaddr *src, socklen_t slen); +int ipv6_recv_sk(char *addr, int port, unsigned int ifindex); +int ipv6_send_sk(char *src_addr, char *addr, int port, + struct sockaddr *src, socklen_t slen); + +#endif diff --git a/agents/virt/include/options.h b/agents/virt/include/options.h new file mode 100644 index 0000000..6e2c1c1 --- /dev/null +++ b/agents/virt/include/options.h @@ -0,0 +1,99 @@ +/* + 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. +*/ +#ifndef _XVM_OPTIONS_H +#define _XVM_OPTIONS_H + +typedef enum { + F_FOREGROUND = 0x1, + F_NOCCS = 0x2, + F_ERR = 0x4, + F_HELP = 0x8, + F_USE_UUID = 0x10, + F_VERSION = 0x20, + F_CCSERR = 0x40, + F_CCSFAIL = 0x80, + F_NOCLUSTER = 0x100 +} arg_flags_t; + +typedef enum { + MODE_MULTICAST = 0, + /*MODE_BROADCAST = 1,*/ + MODE_SERIAL = 2, + MODE_VMCHANNEL = 3, + MODE_TCP = 4, + MODE_VSOCK = 5 +} client_mode_t; + +typedef struct { + char *domain; + fence_cmd_t op; + client_mode_t mode; + int debug; + int timeout; + int delay; + int retr_time; + arg_flags_t flags; + + struct network_args { + char *addr; + char *ipaddr; + char *key_file; + uint32_t cid; + int port; + fence_hash_t hash; + fence_auth_type_t auth; + int family; + unsigned int ifindex; + } net; + + struct serial_args { + char *device; /* Serial device */ + char *speed; + char *address; /* vmchannel IP */ + } serial; +} fence_virt_args_t; + +/* Private structure for commandline / stdin fencing args */ +struct arg_info { + const char opt; + const char *opt_desc; + const char *stdin_opt; + const char *obsoletes; + int deprecated; + int eh; + const char *content_type; + const char *default_value; + const char *desc; + void (*assign)(fence_virt_args_t *, struct arg_info *, char *); +}; + + +/* Get options */ +void args_init(fence_virt_args_t *args); +void args_finalize(fence_virt_args_t *args); + +void args_get_getopt(int argc, char **argv, const char *optstr, + fence_virt_args_t *args); +void args_get_stdin(const char *optstr, fence_virt_args_t *args); +void args_get_ccs(const char *optstr, fence_virt_args_t *args); +void args_usage(char *progname, const char *optstr, int print_stdin); +void args_print(fence_virt_args_t *args); +void args_metadata(char *progname, const char *optstr); + +#endif diff --git a/agents/virt/include/server_plugin.h b/agents/virt/include/server_plugin.h new file mode 100644 index 0000000..04ceae0 --- /dev/null +++ b/agents/virt/include/server_plugin.h @@ -0,0 +1,130 @@ +/* */ + +#ifndef _SERVER_PLUGIN_H +#define _SERVER_PLUGIN_H + +#include "config.h" + +#define PLUGIN_VERSION_LISTENER ((double)0.3) +#define PLUGIN_VERSION_BACKEND ((double)0.2) + +typedef void * listener_context_t; +typedef void * backend_context_t; + +/* These callbacks hand requests off to the + appropriate backend. */ + +/* Do nothing. Returns 1 (failure) to caller */ +typedef int (*fence_null_callback)(const char *vm_name, + void *priv); + +/* Turn the VM 'off'. Returns 0 to caller if successful or + nonzero if unsuccessful. */ +typedef int (*fence_off_callback)(const char *vm_name, const char *src, + uint32_t seqno, void *priv); + +/* Turn the VM 'on'. Returns 0 to caller if successful or + nonzero if unsuccessful. */ +typedef int (*fence_on_callback)(const char *vm_name, const char *src, + uint32_t seqno, void *priv); + +/* Reboot a VM. Returns 0 to caller if successful or + nonzero if unsuccessful. */ +typedef int (*fence_reboot_callback)(const char *vm_name, const char *src, + uint32_t seqno, void *priv); + +/* Get status of a VM. Returns 0 to caller if VM is alive or + nonzero if VM is not alive. */ +typedef int (*fence_status_callback)(const char *vm_name, + void *priv); + +/* Get status of backend. Returns 0 to caller if backend + is responding to requests. */ +typedef int (*fence_devstatus_callback)(void *priv); + + +/* VMs available to fence. Returns 0 to caller if backend + is responding to requests and a host list can be produced */ +typedef int (*hostlist_callback)(const char *vm_name, const char *uuid, + int state, void *arg); +typedef int (*fence_hostlist_callback)(hostlist_callback cb, + void *arg, void *priv); + +typedef int (*backend_init_fn)(backend_context_t *c, + config_object_t *config); +typedef int (*backend_cleanup_fn)(backend_context_t c); + +typedef struct _fence_callbacks { + fence_null_callback null; + fence_off_callback off; + fence_on_callback on; + fence_reboot_callback reboot; + fence_status_callback status; + fence_devstatus_callback devstatus; + fence_hostlist_callback hostlist; +} fence_callbacks_t; + +typedef struct backend_plugin { + const char *name; + const char *version; + const fence_callbacks_t *callbacks; + backend_init_fn init; + backend_cleanup_fn cleanup; +} backend_plugin_t; + +double backend_plugin_version(void); +const backend_plugin_t * backend_plugin_info(void); + +#define BACKEND_VER_SYM backend_plugin_version +#define BACKEND_INFO_SYM backend_plugin_info +#define BACKEND_VER_STR "backend_plugin_version" +#define BACKEND_INFO_STR "backend_plugin_info" + +typedef int (*listener_init_fn)(listener_context_t *c, + const fence_callbacks_t *cb, + config_object_t *config, + map_object_t *map, + void *priv); +typedef int (*listener_dispatch_fn)(listener_context_t c, + struct timeval *timeout); +typedef int (*listener_cleanup_fn)(listener_context_t c); + + +typedef struct listener_plugin { + const char *name; + const char *version; + listener_init_fn init; + listener_dispatch_fn dispatch; + listener_cleanup_fn cleanup; +} listener_plugin_t; + +double listener_plugin_version(void); +const listener_plugin_t * listener_plugin_info(void); + +#define LISTENER_VER_SYM listener_plugin_version +#define LISTENER_INFO_SYM listener_plugin_info +#define LISTENER_VER_STR "listener_plugin_version" +#define LISTENER_INFO_STR "listener_plugin_info" + +typedef enum { + PLUGIN_NONE = 0, + PLUGIN_LISTENER = 1, + PLUGIN_BACKEND = 2 +} plugin_type_t; + +#ifdef __cplusplus +extern "C" { +#endif + +const backend_plugin_t *plugin_find_backend(const char *name); +const listener_plugin_t *plugin_find_listener(const char *name); + +void plugin_dump(void); +int plugin_load(const char *filename); +void plugin_unload(void); +int plugin_search(const char *pathname); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/agents/virt/include/simple_auth.h b/agents/virt/include/simple_auth.h new file mode 100644 index 0000000..4cd57ed --- /dev/null +++ b/agents/virt/include/simple_auth.h @@ -0,0 +1,35 @@ +/* + 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. +*/ +#ifndef _XVM_SIMPLE_AUTH_H +#define _XVM_SIMPLE_AUTH_H + +#include <sys/types.h> + +/* 2-way challenge/response simple auth */ +#define DEFAULT_KEY_FILE "/etc/cluster/fence_xvm.key" + +int read_key_file(char *, char *, size_t); +int sock_challenge(int, fence_auth_type_t, void *, size_t, int); +int sock_response(int, fence_auth_type_t, void *, size_t, int); +int sign_request(fence_req_t *, void *, size_t); +int verify_request(fence_req_t *, fence_hash_t, void *, size_t); + +/* SSL certificate-based authentication TBD */ + +#endif diff --git a/agents/virt/include/simpleconfig.h b/agents/virt/include/simpleconfig.h new file mode 100644 index 0000000..6aba85f --- /dev/null +++ b/agents/virt/include/simpleconfig.h @@ -0,0 +1,56 @@ +#ifndef _SIMPLECONFIG_H +#define _SIMPLECONFIG_H + +typedef int (*config_get_t)(void *config, const char *key, + char *value, size_t valuesz); +typedef int (*config_set_t)(void *config, const char *key, + const char *value); +typedef int (*config_parse_t)(const char *filename, void **config); +typedef int (*config_free_t)(void *config); +typedef void (*config_dump_t)(void *config, FILE *fp); + +/* + * We use an abstract object here so we do not have to link loadable + * modules against the configuration library. + */ + +typedef struct { + config_get_t get; + config_set_t set; + config_parse_t parse; + config_free_t free; + config_dump_t dump; + void *info; +} config_object_t; + +/* + * These macros may be called from within a loadable module + */ +#define sc_get(obj, key, value, valuesz) \ + obj->get(obj->info, key, value, valuesz) +#define sc_set(obj, key, value) \ + obj->set(obj->info, key, value) +#define sc_parse(obj, filename) \ + obj->parse(filename, &obj->info) +#define sc_free(obj) \ + obj->free(obj->info) +#define sc_dump(obj, fp) \ + obj->dump(obj->info, fp) + +/* + * Do not call the below functions from loadable modules. Doing so + * requires linking the configuration library in to the modules, which + * is what we want to avoid. + */ + +/* Returns a copy of our simple config object */ +config_object_t *sc_init(void); + +/* Frees a previously-allocated copy of our simple config object */ +void sc_release(config_object_t *c); + +int check_file_permissions(const char *fname); + +int do_configure(config_object_t *config, const char *filename); + +#endif diff --git a/agents/virt/include/static_map.h b/agents/virt/include/static_map.h new file mode 100644 index 0000000..736e823 --- /dev/null +++ b/agents/virt/include/static_map.h @@ -0,0 +1,34 @@ +#ifndef _STATIC_MAP_H +#define _STATIC_MAP_H + +typedef int (*map_load_t)(void *config, void **perm_info); +typedef int (*map_check_t)(void *info, const char *src, const char *tgt_uuid, const char *tgt_name); +typedef void (*map_cleanup_t)(void **info); + +typedef struct { + map_load_t load; + map_check_t check; + map_cleanup_t cleanup; + void *info; +} map_object_t; + +/* + * These macros may be called from within a loadable module + */ +#define map_load(obj, config) \ + obj->load(config, &obj->info) +#define map_check(obj, src, tgt_uuid) \ + obj->check(obj->info, src, tgt_uuid, NULL) +#define map_check2(obj, src, tgt_uuid, tgt_name) \ + obj->check(obj->info, src, tgt_uuid, tgt_name) +#define map_free(obj) \ + obj->cleanup(obj->info) + +/* Returns a copy of our simple config object */ +void *map_init(void); + +/* Frees a previously-allocated copy of our simple config object */ +void map_release(void *c); + + +#endif diff --git a/agents/virt/include/tcp.h b/agents/virt/include/tcp.h new file mode 100644 index 0000000..609f3e9 --- /dev/null +++ b/agents/virt/include/tcp.h @@ -0,0 +1,27 @@ +/* + 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. +*/ +#ifndef _XVM_TCP_H +#define _XVM_TCP_H + +int ipv4_connect(struct in_addr *in_addr, uint16_t port, int timeout); +int ipv6_connect(struct in6_addr *in6_addr, uint16_t port, int timeout); +int ipv4_listen(const char *addr_str, uint16_t port, int backlog); +int ipv6_listen(const char *addr_str, uint16_t port, int backlog); + +#endif diff --git a/agents/virt/include/tcp_listener.h b/agents/virt/include/tcp_listener.h new file mode 100644 index 0000000..75dec93 --- /dev/null +++ b/agents/virt/include/tcp_listener.h @@ -0,0 +1,7 @@ +#ifndef __TCP_LISTENER_H +#define __TCP_LISTENER_H + +#define IPV4_TCP_ADDR_DEFAULT "127.0.0.1" +#define IPV6_TCP_ADDR_DEFAULT "::1" + +#endif diff --git a/agents/virt/include/xvm.h b/agents/virt/include/xvm.h new file mode 100644 index 0000000..bcd7db2 --- /dev/null +++ b/agents/virt/include/xvm.h @@ -0,0 +1,158 @@ +/* + 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. +*/ +#ifndef _XVM_H +#define _XVM_H + +#include <stdint.h> +#include <sechash.h> +#include <netinet/in.h> +#include <byteswap.h> +#include <endian.h> + +#define XVM_VERSION "1.9.0" + +#define MAX_DOMAINNAME_LENGTH 64 /* XXX MAXHOSTNAMELEN */ +#define MAX_ADDR_LEN sizeof(struct sockaddr_in6) +#define DOMAIN0NAME "Domain-0" +#define DOMAIN0UUID "00000000-0000-0000-0000-000000000000" + +typedef enum { + HASH_NONE = 0x0, /* No packet signing */ + HASH_SHA1 = 0x1, /* SHA1 signing */ + HASH_SHA256 = 0x2, /* SHA256 signing */ + HASH_SHA512 = 0x3 /* SHA512 signing */ +} fence_hash_t; + +#define DEFAULT_HASH HASH_SHA256 + +typedef enum { + AUTH_NONE = 0x0, /* Plain TCP */ + AUTH_SHA1 = 0x1, /* Challenge-response (SHA1) */ + AUTH_SHA256 = 0x2, /* Challenge-response (SHA256) */ + AUTH_SHA512 = 0x3 /* Challenge-response (SHA512) */ + /* AUTH_SSL_X509 = 0x10 SSL X509 certificates */ +} fence_auth_type_t; + +#define DEFAULT_AUTH AUTH_SHA256 + +typedef enum { + FENCE_NULL = 0x0, + FENCE_OFF = 0x1, /* Turn the VM off */ + FENCE_REBOOT = 0x2, /* Hit the reset button */ + FENCE_ON = 0x3, /* Turn the VM on */ + FENCE_STATUS = 0x4, /* virtual machine status (off/on) */ + FENCE_DEVSTATUS = 0x5, /* Status of the fencing device */ + FENCE_HOSTLIST = 0x6, /* List VMs controllable */ + FENCE_METADATA = 0x7, /* Print fence agent metadata */ + FENCE_VALIDATEALL = 0x8 /* Validate command-line or stdin arguments and exit */ +} fence_cmd_t; + +#define DEFAULT_TTL 4 + +#ifndef DEFAULT_HYPERVISOR_URI +#define DEFAULT_HYPERVISOR_URI "qemu:///system" +#endif + +#define MAX_HASH_LENGTH SHA512_LENGTH +#define MAX_KEY_LEN 4096 + +typedef struct __attribute__ ((packed)) _fence_req { + uint8_t request; /* Fence request */ + uint8_t hashtype; /* Hash type used */ + uint8_t addrlen; /* Length of address */ + uint8_t flags; /* Special flags */ +#define RF_UUID 0x1 /* Flag specifying UUID */ + uint8_t domain[MAX_DOMAINNAME_LENGTH]; /* Domain to fence*/ + uint8_t address[MAX_ADDR_LEN]; /* We're this IP */ +#define DEFAULT_MCAST_PORT 1229 + uint16_t port; /* Port we bound to */ + uint8_t random[6]; /* Random Data */ + uint32_t seqno; /* Request identifier; can be random */ + uint32_t family; /* Address family */ + uint8_t hash[MAX_HASH_LENGTH]; /* Binary hash */ +} fence_req_t; + +#if __BYTE_ORDER == __BIG_ENDIAN +#define swab_fence_req_t(req) \ +do { \ + (req)->seqno = bswap_32((req)->seqno); \ + (req)->family = bswap_32((req)->family); \ + (req)->port = bswap_32((req)->port); \ +} while(0) +#else +#define swab_fence_req_t(req) +#endif + + +/* for host list */ +typedef struct __attribute__ ((packed)) _host_info { + uint8_t domain[MAX_DOMAINNAME_LENGTH]; + uint8_t uuid[MAX_DOMAINNAME_LENGTH]; + uint8_t state; + uint8_t pad; +} host_state_t; + + +#define DEFAULT_SERIAL_DEVICE "/dev/ttyS1" +#define DEFAULT_SERIAL_SPEED "115200,8N1" +#define DEFAULT_CHANNEL_IP "10.0.2.179" +#define SERIAL_MAGIC 0x61626261 /* endian doesn't matter */ + +typedef struct __attribute__((packed)) _serial_fence_req { + uint32_t magic; + uint8_t request; + uint8_t flags; + uint8_t domain[MAX_DOMAINNAME_LENGTH]; + uint32_t seqno; +} serial_req_t; + +#if __BYTE_ORDER == __BIG_ENDIAN +#define swab_serial_req_t(req) \ +do { \ + (req)->magic = bswap_32((req)->magic); \ + (req)->seqno = bswap_32((req)->seqno); \ +} while(0) +#else +#define swab_serial_req_t(req) +#endif + + +typedef struct __attribute__((packed)) _serial_fense_resp { + uint32_t magic; + uint8_t response; +} serial_resp_t; + +#if __BYTE_ORDER == __BIG_ENDIAN +#define swab_serial_resp_t(req) \ +do { \ + (req)->magic = bswap_32((req)->magic); \ +} while(0) +#else +#define swab_serial_resp_t(req) +#endif + + +#define RESP_SUCCESS 0 +#define RESP_FAIL 1 +#define RESP_OFF 2 +#define RESP_PERM 3 +#define RESP_HOSTLIST 253 + + +#endif diff --git a/agents/virt/man/.gitignore b/agents/virt/man/.gitignore new file mode 100644 index 0000000..4a80f6e --- /dev/null +++ b/agents/virt/man/.gitignore @@ -0,0 +1,2 @@ +fence_virt.8 +fence_virt.wiki diff --git a/agents/virt/man/Makefile.am b/agents/virt/man/Makefile.am new file mode 100644 index 0000000..5b8ae36 --- /dev/null +++ b/agents/virt/man/Makefile.am @@ -0,0 +1,28 @@ +############################################################################### +############################################################################### +## +## 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 + +dist_man8_MANS = fence_xvm.8 fence_virtd.8 +dist_man5_MANS = fence_virt.conf.5 +man8_MANS = fence_virt.8 + +fence_virt.8: $(top_builddir)/agents/virt/client/fence_virt $(top_srcdir)/lib/fence2man.xsl + set -e && \ + ../client/$(@:%.8=%) -o metadata > .$(@F).tmp && \ + xmllint --noout --relaxng $(top_srcdir)/lib/metadata.rng .$(@F).tmp && \ + xsltproc $(top_srcdir)/lib/fence2man.xsl .$(@F).tmp > $@ + xsltproc $(top_srcdir)/lib/fence2wiki.xsl .$(@F).tmp | grep -v '<?xml' > $(@F:%.8=%.wiki) + + +clean-local: + rm -f fence_virt.8* .*.8.tmp *.wiki diff --git a/agents/virt/man/fence_virt.conf.5 b/agents/virt/man/fence_virt.conf.5 new file mode 100644 index 0000000..f920a66 --- /dev/null +++ b/agents/virt/man/fence_virt.conf.5 @@ -0,0 +1,335 @@ +.TH fence_virt.conf 5 + +.SH NAME +fence_virt.conf - configuration file for fence_virtd + +.SH DESCRIPTION + +The fence_virt.conf file contains configuration information for fence_virtd, +a fencing request routing daemon for clusters of virtual machines. + +The file is tree-structured. There are parent/child relationships and sibling +relationships between the nodes. + + foo { + bar { + baz = "1"; + } + } + +There are three primary sections of fence_virt.conf. + +.SH SECTIONS +.SS fence_virtd + +This section contains global information about how fence_virtd is to operate. +The most important pieces of information are as follows: + +.TP +.B listener +. +the listener plugin for receiving fencing requests from clients + +.TP +.B backend +. +the plugin to be used to carry out fencing requests + +.TP +.B foreground +. +do not fork into the background. + +.TP +.B wait_for_init +. +wait for the frontend and backends to become available rather than giving up immediately. +This replaces wait_for_backend in 0.2.x. + +.TP +.B module_path +. +the module path to search for plugins + +.SS listeners + +This section contains listener-specific configuration information; see the +section about listeners below. + +.SS backends + +This section contains listener-specific configuration information; see the +section about listeners below. + +.SS groups + +This section contains static maps of which virtual machines +may fence which other virtual machines; see the section +about groups below. + + +.SH LISTENERS + +There are various listeners available for fence_virtd, each one handles +decoding and authentication of a given fencing request. The following +configuration blocks belong in the \fBlisteners\fP section of fence_virt.conf + +.SS multicast +.TP +.B key_file +. +the shared key file to use (default: /etc/cluster/fence_xvm.key). + +.TP +.B hash +. +the weakest hashing algorithm allowed for client requests. Clients may send packets with stronger hashes than the one specified, but not weaker ones. (default: sha256, but could +be sha1, sha512, or none) + +.TP +.B auth +. +the hashing algorithm to use for the simplistic challenge-response authentication +(default: sha256, but could be sha1, sha512, or none) + +.TP +.B family +. +the IP family to use (default: ipv4, but may be ipv6) + +.TP +.B address +. +the multicast address to listen on (default: 225.0.0.12) + +.TP +.B port +. +the multicast port to listen on (default: 1229) + +.TP +.B interface +. +interface to listen on. By default, fence_virtd listens on all interfaces. +However, this causes problems in some environments where the host computer +is used as a gateway. + +.SS serial + +The serial listener plugin utilizes libvirt's serial (or VMChannel) +mapping to listen for requests. When using the serial listener, it is +necessary to add a serial port (preferably pointing to /dev/ttyS1) or +a channel (preferably pointing to 10.0.2.179:1229) to the +libvirt domain description. Note that only type +.B unix +, mode +.B bind +serial ports and channels are supported and each VM should have a +separate unique socket. Example libvirt XML: + +.in 8 + <\fBserial\fP type='unix'> + <source mode='bind' path='/sandbox/guests/fence_socket_molly'/> + <target port='1'/> + </serial> + <\fBchannel\fP type='unix'> + <source mode='bind' path='/sandbox/guests/fence_molly_vmchannel'/> + <target type='guestfwd' address='10.0.2.179' port='1229'/> + </channel> +.in 0 + +.TP +.B uri +. +the URI to use when connecting to libvirt by the serial plugin (optional). + +.TP +.B path +. +The same directory that is defined for the domain serial port path (From example above: /sandbox/guests). Sockets must reside in this directory in order to be considered valid. This can be used to prevent fence_virtd from using the wrong sockets. + +.TP +.B mode +. +This selects the type of sockets to register. Valid values are "serial" +(default) and "vmchannel". + +.SS tcp +The tcp listener operates similarly to the multicast listener but uses TCP sockets for communication instead of using multicast packets. + +.TP +.B key_file +. +the shared key file to use (default: /etc/cluster/fence_xvm.key). + +.TP +.B hash +. +the hashing algorithm to use for packet signing (default: sha256, but could +be sha1, sha512, or none) + +.TP +.B auth +. +the hashing algorithm to use for the simplistic challenge-response authentication +(default: sha256, but could be sha1, sha512, or none) + +.TP +.B family +. +the IP family to use (default: ipv4, but may be ipv6) + +.TP +.B address +. +the IP address to listen on (default: 127.0.0.1 for IPv4, ::1 for IPv6) + +.TP +.B port +. +the TCP port to listen on (default: 1229) + +.SS vsock +The vsock listener operates similarly to the multicast listener but uses virtual machine sockets (AF_VSOCK) for communication instead of using multicast packets. + +.TP +.B key_file +. +the shared key file to use (default: /etc/cluster/fence_xvm.key). + +.TP +.B hash +. +the hashing algorithm to use for packet signing (default: sha256, but could +be sha1, sha512, or none) + +.TP +.B auth +. +the hashing algorithm to use for the simplistic challenge-response authentication +(default: sha256, but could be sha1, sha512, or none) + +.TP +.B port +. +the vsock port to listen on (default: 1229) + +.SH BACKENDS + +There are various backends available for fence_virtd, each one handles +routing a fencing request to a hypervisor or management tool. The following +configuration blocks belong in the \fBbackends\fP section of fence_virt.conf + +.SS libvirt + +The libvirt plugin is the simplest plugin. It is used in environments where +routing fencing requests between multiple hosts is not required, for example +by a user running a cluster of virtual machines on a single desktop computer. + +.TP +.B uri +. +the URI to use when connecting to libvirt. + +All libvirt URIs are accepted and passed as-is. + +See https://libvirt.org/uri.html#remote-uris for examples. + +NOTE: When VMs are run as non-root user the socket path must be set as part +of the URI. + +Example: qemu:///session?socket=/run/user/<UID>/libvirt/virtqemud-sock + +.SS cpg + +The cpg plugin uses corosync CPG and libvirt to track virtual +machines and route fencing requests to the appropriate computer. + +.TP +.B uri +. +the URI to use when connecting to libvirt by the cpg plugin. + +.TP +.B name_mode +. +The cpg plugin, in order to retain compatibility with fence_xvm, +stores virtual machines in a certain way. The +default was to use 'name' when using fence_xvm and fence_xvmd, and so this +is still the default. However, it is strongly recommended to use 'uuid' +instead of 'name' in all cluster environments involving more than one +physical host in order to avoid the potential for name collisions. + +.SH GROUPS + +Fence_virtd supports static maps which allow grouping of VMs. The +groups are arbitrary and are checked at fence time. Any member of +a group may fence any other member. Hosts may be assigned to multiple +groups if desired. + +.SS group + +This defines a group. + +.TP +.B name +. +Optionally define the name of the group. Useful only for configuration +readability and debugging of configuration parsing. + +.TP +.B uuid +. +Defines UUID as a member of a group. It can be used multiple times +to specify both node name and UUID values that can be fenced. +When using the serial listener, the vm uuid is required and it is +recommended to add also the vm name. + +.TP +.B ip +. +Defines an IP which is allowed to send fencing requests +for members of this group (e.g. for multicast). It can be used +multiple times to allow more than 1 IP to send fencing requests to +the group. It is highly recommended that this be used in conjunction +with a key file. +When using the vsock listener, ip should contain the CID value assigned +by libvirt to the vm. +When using the serial listener, ip value is not used and can be omitted. + + +.SH EXAMPLE + + fence_virtd { + listener = "multicast"; + backend = "cpg"; + } + + # this is the listeners section + + listeners { + multicast { + key_file = "/etc/cluster/fence_xvm.key"; + } + } + + backends { + libvirt { + uri = "qemu:///system"; + } + } + + groups { + group { + name = "cluster1"; + ip = "192.168.1.1"; + ip = "192.168.1.2"; + uuid = "44179d3f-6c63-474f-a212-20c8b4b25b16"; + uuid = "1ce02c4b-dfa1-42cb-b5b1-f0b1091ece60"; + uuid = "node1"; + uuid = "node2"; + } + } + +.SH SEE ALSO +fence_virtd(8), fence_virt(8), fence_xvm(8), fence(8) diff --git a/agents/virt/man/fence_virtd.8 b/agents/virt/man/fence_virtd.8 new file mode 100644 index 0000000..56f6938 --- /dev/null +++ b/agents/virt/man/fence_virtd.8 @@ -0,0 +1,53 @@ +.TH FENCE_AGENT 8 2010-01-05 "fence_virtd (Fence Agent)" +.SH NAME +fence_virtd - Fencing host for virtual machines + +.SH DESCRIPTION +.P +fence_virtd is a host daemon designed to route fencing requests for +virtual machines. + +Fence_virt and fence_xvm talk to fence_virtd, which supports multiple backend plugins, including: + - libvirt for single-node operation + - Corosync CPG and libvirt when using Linux-cluster release 3.0.0 or later + - libvirt-qpid for multi-node, non-cluster operation + +For compatibility, fence_xvm from linux-cluster release 2 may talk to fence_virtd. + +.P +fence_virtd accepts a few options on the command line, but most options +are read from fence_virt.conf. + +.SH PARAMETERS +.TP +.B -d +. +Specify debug level, e.g. "-d99" + +.TP +.B -c +. +Interactively prompt user for configuration information + +.TP +.B -f +. +Specify an alternate configuration file instead of /etc/fence_virt.conf + +.TP +.B -F +. +Do not fork into background after starting (overrides any setting in fence_virt.conf) + +.TP +.B -w +. +Wait for backend to be available (overrides any setting in fence_virt.conf) + +.TP +.B -p +. +Specify the full path to the pid file used to record the active process pid. + +.SH SEE ALSO +fence_virt(8), fence_xvm(8), fence(8), fence_virt.conf(5) diff --git a/agents/virt/man/fence_xvm.8 b/agents/virt/man/fence_xvm.8 new file mode 100644 index 0000000..5d0cfdd --- /dev/null +++ b/agents/virt/man/fence_xvm.8 @@ -0,0 +1 @@ +.so man8/fence_virt.8 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; +} |