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