Add a TCP listener plugin for use with viosproxy

Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
This commit is contained in:
Ryan McCabe 2012-06-01 15:22:02 -04:00
parent 52381ce2b7
commit f61626c108
15 changed files with 1003 additions and 21 deletions

View File

@ -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`

View File

@ -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;
}

View File

@ -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:*/

View File

@ -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 <address>", "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 <port>", "ipport",
0, "string", "1229",
"TCP, Multicast, or VMChannel IP port (default=1229)",
assign_port },
{ 'A', "-A <address>", "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 <port>", "ipport",
0, "string", "1229",
"Multicast or VMChannel IP port (default=1229)",
"TCP, Multicast, or VMChannel IP port (default=1229)",
assign_port },
{ 'I', "-I <interface>", "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;

View File

@ -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;
}

169
client/tcp.c Normal file
View File

@ -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 <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 "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;
}

View File

@ -32,10 +32,12 @@
#include <fcntl.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.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.
@ -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;
}

View File

@ -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],

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
</controller>
<\fBchannel\fP type='unix'>
<source mode='bind' path='/sandbox/fence_virt/guests/fence_socket_guest1' id='guest1'/>
<target type='virtio' name='org.redhat.fencevirt.node.1'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
.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

View File

@ -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)

View File

@ -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

543
server/tcp.c Normal file
View File

@ -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 <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <nss.h>
#include <list.h>
#include <simpleconfig.h>
#include <static_map.h>
#include <server_plugin.h>
#include <history.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"
#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