diff --git a/client/Makefile.in b/client/Makefile.in index 888bad1..2dbe2eb 100644 --- a/client/Makefile.in +++ b/client/Makefile.in @@ -18,7 +18,7 @@ TARGET=fence_virt # XVM/Multicast mode compatibility link LINK=fence_xvm -fence_virt_SOURCES = mcast.c serial.c main.c options.c +fence_virt_SOURCES = mcast.c serial.c main.c options.c tcp.c INCLUDES=-I../include `nss-config --cflags` `nspr-config --cflags` \ `xml2-config --cflags` diff --git a/client/main.c b/client/main.c index 5d261bd..65ee8b1 100644 --- a/client/main.c +++ b/client/main.c @@ -59,7 +59,7 @@ main(int argc, char **argv) my_options = "di:a:p:r:C:c:k:M:H:uo:t:?hV"; args.mode = MODE_MULTICAST; } else { - my_options = "dD:P:A:p:M:H:o:t:?hV"; + my_options = "dD:P:A:p:M:H:o:t:?hVT:C:c:k:"; args.mode = MODE_SERIAL; } @@ -105,6 +105,10 @@ main(int argc, char **argv) args.flags |= F_ERR; } + if (args.net.ipaddr) { + args.mode = MODE_TCP; + } + if (args.flags & F_ERR) { args_usage(argv[0], my_options, (argc == 1)); exit(1); @@ -122,6 +126,9 @@ main(int argc, char **argv) case MODE_SERIAL: ret = serial_fence_virt(&args); break; + case MODE_TCP: + ret = tcp_fence_virt(&args); + break; default: return 1; } diff --git a/client/mcast.c b/client/mcast.c index c416215..f896b98 100644 --- a/client/mcast.c +++ b/client/mcast.c @@ -301,9 +301,9 @@ mcast_fence_virt(fence_virt_args_t *args) case AUTH_SHA256: case AUTH_SHA512: if (args->net.family == PF_INET) { - lfd = ipv4_listen(args->net.port, 10); + lfd = ipv4_listen(NULL, args->net.port, 10); } else { - lfd = ipv6_listen(args->net.port, 10); + lfd = ipv6_listen(NULL, args->net.port, 10); } break; /*case AUTH_X509:*/ diff --git a/client/options.c b/client/options.c index 6a7772b..dc32a97 100644 --- a/client/options.c +++ b/client/options.c @@ -40,6 +40,7 @@ #include "xvm.h" #include "simple_auth.h" #include "mcast.h" +#include "tcp_listener.h" #include "options.h" #define SCHEMA_COMPAT '\xfe' @@ -103,6 +104,16 @@ assign_address(fence_virt_args_t *args, struct arg_info *arg, char *value) 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) @@ -398,6 +409,15 @@ static struct arg_info _arg_info[] = { "Multicast address (default=" IPV4_MCAST_DEFAULT " / " IPV6_MCAST_DEFAULT ")", assign_address }, + { 'T', "-T
", "ipaddr", + 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 }, + + { 'p', "-p ", "ipport", + 0, "string", "1229", + "TCP, Multicast, or VMChannel IP port (default=1229)", + assign_port }, { 'A', "-A
", "channel_address", 0, "string", "10.0.2.179", "VM Channel IP address (default=" DEFAULT_CHANNEL_IP ")", @@ -405,7 +425,7 @@ static struct arg_info _arg_info[] = { { 'p', "-p ", "ipport", 0, "string", "1229", - "Multicast or VMChannel IP port (default=1229)", + "TCP, Multicast, or VMChannel IP port (default=1229)", assign_port }, { 'I', "-I ", "interface", @@ -546,6 +566,7 @@ args_init(fence_virt_args_t *args) args->net.hash = DEFAULT_HASH; args->net.auth = DEFAULT_AUTH; args->net.addr = NULL; + args->net.ipaddr = NULL; args->net.port = DEFAULT_MCAST_PORT; args->net.ifindex = 0; args->net.family = PF_INET; diff --git a/client/serial.c b/client/serial.c index 2178111..05245ab 100644 --- a/client/serial.c +++ b/client/serial.c @@ -271,8 +271,10 @@ serial_fence_virt(fence_virt_args_t *args) swab_serial_req_t(&req); ret = _write_retry(fd, &req, sizeof(req), &tv); if (ret < sizeof(req)) { - if (ret < 0) + if (ret < 0) { + close(fd); return ret; + } printf("Failed to send request\n"); } @@ -285,14 +287,17 @@ serial_fence_virt(fence_virt_args_t *args) 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) + if (resp.magic != SERIAL_MAGIC) { + close(fd); return -1; + } ret = resp.response; if (resp.response == RESP_HOSTLIST) /* hostlist */ { /* ok read hostlist */ @@ -301,6 +306,5 @@ serial_fence_virt(fence_virt_args_t *args) } close(fd); - return ret; } diff --git a/client/tcp.c b/client/tcp.c new file mode 100644 index 0000000..fa9566c --- /dev/null +++ b/client/tcp.c @@ -0,0 +1,169 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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" + +void do_read_hostlist(int fd, int timeout); + +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 (tcp_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 (tcp_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; + } + + close(fd); + 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)); + 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"); + return 1; + } + + switch (args->net.auth) { + case AUTH_NONE: + case AUTH_SHA1: + case AUTH_SHA256: + case AUTH_SHA512: + return 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); + return 1; + } + + return 1; +} diff --git a/common/tcp.c b/common/tcp.c index 3d05628..46ff69c 100644 --- a/common/tcp.c +++ b/common/tcp.c @@ -32,10 +32,12 @@ #include #include #include +#include #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. @@ -56,27 +58,44 @@ set_cloexec(int fd) /** 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(uint16_t port, int backlog) +ipv6_listen(const char *addr_str, uint16_t port, int backlog) { struct sockaddr_in6 _sin6; int fd, ret; dbg_printf(4, "%s: Setting up ipv6 listen socket\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; - _sin6.sin6_addr = in6addr_any; + + 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(struct sockaddr_in6)); + memcpy(&_sin6.sin6_addr, &ss, sizeof(struct sockaddr_in6)); + } + + fd = socket(PF_INET6, SOCK_STREAM, 0); + if (fd < 0) + return -1; ret = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&ret, sizeof (ret)); @@ -106,25 +125,41 @@ ipv6_listen(uint16_t port, int backlog) /** 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(uint16_t port, int backlog) +ipv4_listen(const char *addr_str, uint16_t port, int backlog) { struct sockaddr_in _sin; int fd, ret; dbg_printf(4, "%s: Setting up ipv4 listen socket\n", __FUNCTION__); - fd = socket(PF_INET, SOCK_STREAM, 0); - if (fd < 0) - return -1; _sin.sin_family = PF_INET; _sin.sin_port = htons(port); - _sin.sin_addr.s_addr = htonl(INADDR_ANY); + + 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(struct sockaddr_in)); + } + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + return -1; ret = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&ret, sizeof (ret)); @@ -298,3 +333,40 @@ connect_nb(int fd, struct sockaddr *dest, socklen_t len, int timeout) 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/configure.in b/configure.in index 49dfc5c..93f9f11 100644 --- a/configure.in +++ b/configure.in @@ -108,6 +108,13 @@ AC_ARG_ENABLE(multicast-plugin, [ mod_multicast=$enableval ], [ mod_multicast=yes ]) AC_SUBST(mod_multicast) +# tcp plugin: Disabled by default +AC_ARG_ENABLE(tcp-plugin, +[AS_HELP_STRING([--enable-tcp-plugin], + [Enable tcp listener plugin])], +[ mod_tcp=$enableval ], [ mod_tcp=no ]) +AC_SUBST(mod_tcp) + # serial/libvirt plugin: Disabled by default AC_ARG_ENABLE(serial-plugin, [AS_HELP_STRING([--disable-serial-plugin], diff --git a/include/client.h b/include/client.h index 3cb3774..ede479f 100644 --- a/include/client.h +++ b/include/client.h @@ -1,6 +1,7 @@ #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); diff --git a/include/options.h b/include/options.h index 6fe19d8..2e0d764 100644 --- a/include/options.h +++ b/include/options.h @@ -35,7 +35,8 @@ typedef enum { MODE_MULTICAST = 0, /*MODE_BROADCAST = 1,*/ MODE_SERIAL = 2, - MODE_VMCHANNEL = 3 + MODE_VMCHANNEL = 3, + MODE_TCP = 4 } client_mode_t; typedef struct { @@ -49,6 +50,7 @@ typedef struct { struct network_args { char *addr; + char *ipaddr; char *key_file; int port; fence_hash_t hash; diff --git a/include/tcp.h b/include/tcp.h index addf987..609f3e9 100644 --- a/include/tcp.h +++ b/include/tcp.h @@ -21,7 +21,7 @@ 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(uint16_t port, int backlog); -int ipv6_listen(uint16_t port, int backlog); +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/man/fence_virt.conf.5 b/man/fence_virt.conf.5 index 5944a78..c9e8243 100644 --- a/man/fence_virt.conf.5 +++ b/man/fence_virt.conf.5 @@ -155,6 +155,52 @@ sockets. This selects the type of sockets to register. Valid values are "serial" (default) and "vmchannel". +.SS tcp +The tcp plugin was designed to be used with vios-proxy. vios-proxy uses a virtio-serial channel to proxy TCP connections between guests and a host. In order to use the tcp plugin, vios-proxy-host must be running on all the physical cluster nodes, and vios-proxy-guest must be running on all guest cluster nodes. Prior to running vios-proxy-host or vios-proxy-guest, the virtio-serial channel and host sockets must be configured for all guest domains. Example libvirt XML: + +.in 8 + <\fBcontroller\fP type='virtio-serial' index='0'> +
+ + + <\fBchannel\fP type='unix'> + + +
+ +.in 0 + +.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) + +.TP +.B port +. +the TCP port to listen on (default: 1229) .SH BACKENDS diff --git a/server/Makefile.in b/server/Makefile.in index 2bd55ca..969e34b 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -48,6 +48,7 @@ libvirt_qmf_so_SOURCES = uuid-test.c libvirt_qmf_cxx_so_SOURCES = libvirt-qmf.cpp pm_fence_so_SOURCES = pm-fence.c multicast_so_SOURCES = mcast.c history.c +tcp_so_SOURCES = tcp.c history.c checkpoint_so_SOURCES = virt.c vm_states.c history.c checkpoint.c cpg.c serial_so_SOURCES = virt-serial.c virt-sockets.c serial.c history.c @@ -63,6 +64,7 @@ mod_libvirt_qmf=@mod_libvirt_qmf@ mod_pm_fence=@mod_pm_fence@ mod_multicast=@mod_multicast@ mod_serial=@mod_serial@ +mod_tcp=@mod_tcp@ ################################ ifeq ($(with_modules),yes) @@ -91,6 +93,9 @@ endif ifneq ($(mod_serial),no) MODULES+=serial.so endif +ifneq ($(mod_tcp),no) +MODULES+=tcp.so +endif ifneq ($(mod_null),no) MODULES+=null.so endif @@ -127,6 +132,10 @@ ifneq ($(mod_serial),no) fence_virtd_SOURCES+=${serial_so_SOURCES} LIBS+=$(NSS_LIBS) $(XML_LIBS) endif +ifneq ($(mod_tcp),no) +fence_virtd_SOURCES+=${tcp_so_SOURCES} +LIBS+=$(NSS_LIBS) +endif ifneq ($(mod_null),no) fence_virtd_SOURCES+=${null_so_SOURCES} endif @@ -167,6 +176,9 @@ checkpoint.so: ${checkpoint_so_SOURCES:.c=.o} serial.so: ${serial_so_SOURCES:.c=.o} $(CC) -o $@ $^ $(LIBS) -shared $(VIRT_LIBS) $(UUID_LIBS) $(XML_LIBS) +tcp.so: ${tcp_so_SOURCES:.c=.o} + $(CC) -o $@ $^ $(LIBS) -shared $(NSS_LIBS) + %.o: %.c $(CC) $(CFLAGS) -c -o $@ $^ $(INCLUDES) diff --git a/server/config.c b/server/config.c index 996af7e..87d826e 100644 --- a/server/config.c +++ b/server/config.c @@ -324,6 +324,102 @@ listener_config_multicast(config_object_t *config) 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)); + } + + 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"; + } else if (inet_pton(AF_INET6, inp, &sin6) == 1) { + printf("\nUsing ipv6 as family.\n\n"); + family = "ipv6"; + } else { + printf("'%s' is not a valid IP address!\n", inp); + continue; + } + + } while (0); + 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); + } + + do { + text_input("TCP Listen Port", val, inp, sizeof(inp)); + + done = atoi(inp); + if (done <= 0 || done != (done & 0xffff)) { + printf("Port value '%s' is out of range\n", val); + continue; + } + + } while (0); + 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)); + } + + 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) + break; + continue; + } + break; + } + } while(0); + 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) @@ -469,6 +565,8 @@ listener_configure(config_object_t *config) 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 diff --git a/server/tcp.c b/server/tcp.c new file mode 100644 index 0000000..6c93f14 --- /dev/null +++ b/server/tcp.c @@ -0,0 +1,543 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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" + +#define NAME "tcp" +#define VERSION "0.1" + +#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_check(arg->map, arg->src, vm_uuid) == 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)); + strncpy((char *)hinfo.uuid, vm_uuid, sizeof(hinfo.uuid)); + 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 int +do_fence_request_tcp(int fd, fence_req_t *req, tcp_info *info) +{ + char ip_addr_src[1024]; + char response = 1; + struct tcp_hostlist_arg arg; + + /* Noops if auth == AUTH_NONE */ + if (tcp_response(fd, info->args.auth, info->key, info->key_len, 10) <= 0) { + printf("Failed to respond to challenge\n"); + close(fd); + return -1; + } + + if (tcp_challenge(fd, info->args.auth, info->key, info->key_len, 10) <= 0) { + printf("Remote failed challenge\n"); + close(fd); + return -1; + } + + dbg_printf(2, "Request %d seqno %d target %s\n", + req->request, req->seqno, 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(fd, &response, 1) < 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; + + 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) + return n; + + client_fd = accept(info->listen_sock, NULL, NULL); + 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, &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; + +#ifdef _MODULE + if (sc_get(config, "fence_virtd/@debug", value, sizeof(value))==0) + dset(atoi(value)); +#endif + + 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_TCP_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"); + return -1; + } else if (ret > 0) { + printf("%d errors found during configuration\n",ret); + 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"); + 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 = VERSION, + .init = tcp_init, + .dispatch = tcp_dispatch, + .cleanup = tcp_shutdown, +}; + + +#ifdef _MODULE +double +LISTENER_VER_SYM(void) +{ + return PLUGIN_VERSION_LISTENER; +} + +const listener_plugin_t * +LISTENER_INFO_SYM(void) +{ + return &tcp_plugin; +} +#else +static void __attribute__((constructor)) +tcp_register_plugin(void) +{ + plugin_reg_listener(&tcp_plugin); +} +#endif