Start of a trivial test case for the socket APIs. Only tests simple server setup & client connect for UNIX sockets so far * tests/Makefile.am: Add socket test * tests/virnetsockettest.c: New test case * tests/testutils.c: Avoid overriding LIBVIRT_DEBUG settings * tests/ssh.c: Dumb helper program for SSH tunnelling tests --- configure.ac | 2 +- tests/.gitignore | 2 + tests/Makefile.am | 14 ++- tests/ssh.c | 54 +++++ tests/testutils.c | 8 +- tests/virnetsockettest.c | 529 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 604 insertions(+), 5 deletions(-) create mode 100644 tests/ssh.c create mode 100644 tests/virnetsockettest.c diff --git a/configure.ac b/configure.ac index 81bad91..737a476 100644 --- a/configure.ac +++ b/configure.ac @@ -134,7 +134,7 @@ LIBS=$old_libs dnl Availability of various common headers (non-fatal if missing). AC_CHECK_HEADERS([pwd.h paths.h regex.h sys/syslimits.h sys/un.h \ sys/poll.h syslog.h mntent.h net/ethernet.h linux/magic.h \ - sys/un.h sys/syscall.h netinet/tcp.h fnmatch.h]) + sys/un.h sys/syscall.h netinet/tcp.h fnmatch.h ifaddrs.h]) AC_CHECK_LIB([intl],[gettext],[]) diff --git a/tests/.gitignore b/tests/.gitignore index e3906f0..e272cf6 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,6 +1,7 @@ *.exe .deps .libs +ssh commandhelper commandhelper.log commandhelper.pid @@ -30,6 +31,7 @@ statstest storagepoolxml2xmltest storagevolxml2xmltest virbuftest +virnetsockettest virshtest vmx2xmltest xencapstest diff --git a/tests/Makefile.am b/tests/Makefile.am index 5896442..abf8f30 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -77,7 +77,12 @@ EXTRA_DIST = \ check_PROGRAMS = virshtest conftest sockettest \ nodeinfotest qparamtest virbuftest \ - commandtest commandhelper seclabeltest + commandtest commandhelper seclabeltest \ + virnetsockettest ssh + +# This is a fake SSH we use from virnetsockettest +ssh_SOURCES = ssh.c +ssh_LDADD = $(COVERAGE_LDFLAGS) if WITH_XEN check_PROGRAMS += xml2sexprtest sexpr2xmltest \ @@ -159,6 +164,7 @@ TESTS = virshtest \ sockettest \ commandtest \ seclabeltest \ + virnetsockettest \ $(test_scripts) if WITH_XEN @@ -361,6 +367,12 @@ commandhelper_SOURCES = \ commandhelper_CFLAGS = -Dabs_builddir="\"`pwd`\"" commandhelper_LDADD = $(LDADDS) +virnetsockettest_SOURCES = \ + virnetsockettest.c testutils.h testutils.c +virnetsockettest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" +virnetsockettest_LDADD = $(LDADDS) + + seclabeltest_SOURCES = \ seclabeltest.c seclabeltest_LDADD = ../src/libvirt_driver_security.la $(LDADDS) diff --git a/tests/ssh.c b/tests/ssh.c new file mode 100644 index 0000000..ceaa29f --- /dev/null +++ b/tests/ssh.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2006-2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <stdio.h> +#include "internal.h" + +int main(int argc, char **argv) +{ + int i; + int failConnect = 0; /* Exit -1, with no data on stdout, msg on stderr */ + int dieEarly = 0; /* Exit -1, with partial data on stdout, msg on stderr */ + + for (i = 1 ; i < argc ; i++) { + if (STREQ(argv[i], "nosuchhost")) + failConnect = 1; + else if (STREQ(argv[i], "crashinghost")) + dieEarly = 1; + } + + if (failConnect) { + fprintf(stderr, "%s", "Cannot connect to host nosuchhost\n"); + return -1; + } + + if (dieEarly) { + printf("%s\n", "Hello World"); + fprintf(stderr, "%s", "Hangup from host\n"); + return -1; + } + + for (i = 1 ; i < argc ; i++) + printf("%s%c", argv[i], i == (argc -1) ? '\n' : ' '); + + return 0; +} diff --git a/tests/testutils.c b/tests/testutils.c index 3110457..9b3cf59 100644 --- a/tests/testutils.c +++ b/tests/testutils.c @@ -495,9 +495,11 @@ int virtTestMain(int argc, return 1; virLogSetFromEnv(); - if (virLogDefineOutput(virtTestLogOutput, virtTestLogClose, &testLog, - 0, 0, NULL, 0) < 0) - return 1; + if (!getenv("LIBVIRT_DEBUG") && !virLogGetNbOutputs()) { + if (virLogDefineOutput(virtTestLogOutput, virtTestLogClose, &testLog, + 0, 0, NULL, 0) < 0) + return 1; + } #if TEST_OOM if ((oomStr = getenv("VIR_TEST_OOM")) != NULL) { diff --git a/tests/virnetsockettest.c b/tests/virnetsockettest.c new file mode 100644 index 0000000..39d496a --- /dev/null +++ b/tests/virnetsockettest.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2006-2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <stdlib.h> +#include <signal.h> +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif + +#include "testutils.h" +#include "util.h" +#include "virterror_internal.h" +#include "memory.h" +#include "logging.h" +#include "ignore-value.h" +#include "files.h" + +#include "rpc/virnetsocket.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +static char *argv0; +static char cwd[PATH_MAX]; + +#if HAVE_IFADDRS_H +#define BASE_PORT 5672 + +static int +checkProtocols(bool *hasIPv4, bool *hasIPv6, + int *freePort) +{ + struct ifaddrs *ifaddr = NULL, *ifa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + int s4 = -1, s6 = -1; + int i; + int ret = -1; + + *hasIPv4 = *hasIPv6 = false; + *freePort = 0; + + if (getifaddrs(&ifaddr) < 0) + goto cleanup; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + + if (ifa->ifa_addr->sa_family == AF_INET) + *hasIPv4 = true; + if (ifa->ifa_addr->sa_family == AF_INET6) + *hasIPv6 = true; + } + + VIR_DEBUG("Protocols: v4 %d v6 %d\n", *hasIPv4, *hasIPv6); + + freeifaddrs(ifaddr); + + for (i = 0 ; i < 50 ; i++) { + int only = 1; + if ((s4 = socket(AF_INET, SOCK_STREAM, 0)) < 0) + goto cleanup; + + if ((s6 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) + goto cleanup; + + if (setsockopt(s6, IPPROTO_IPV6, IPV6_V6ONLY, &only, sizeof(only)) < 0) + goto cleanup; + + memset(&in4, 0, sizeof(in4)); + memset(&in6, 0, sizeof(in6)); + + in4.sin_family = AF_INET; + in4.sin_port = htons(BASE_PORT + i); + in4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + in6.sin6_family = AF_INET6; + in6.sin6_port = htons(BASE_PORT + i); + in6.sin6_addr = in6addr_loopback; + + if (bind(s4, (struct sockaddr *)&in4, sizeof(in4)) < 0) { + if (errno == EADDRINUSE) { + VIR_FORCE_CLOSE(s4); + VIR_FORCE_CLOSE(s6); + continue; + } + goto cleanup; + } + if (bind(s6, (struct sockaddr *)&in6, sizeof(in6)) < 0) { + if (errno == EADDRINUSE) { + VIR_FORCE_CLOSE(s4); + VIR_FORCE_CLOSE(s6); + continue; + } + goto cleanup; + } + + *freePort = BASE_PORT + i; + break; + } + + VIR_DEBUG("Choose port %d\n", *freePort); + + ret = 0; + +cleanup: + VIR_FORCE_CLOSE(s4); + VIR_FORCE_CLOSE(s6); + return ret; +} + + +struct testTCPData { + const char *lnode; + int port; + const char *cnode; +}; + +static int testSocketTCPAccept(const void *opaque) +{ + virNetSocketPtr *lsock = NULL; /* Listen socket */ + size_t nlsock = 0, i; + virNetSocketPtr ssock = NULL; /* Server socket */ + virNetSocketPtr csock = NULL; /* Client socket */ + const struct testTCPData *data = opaque; + int ret = -1; + char portstr[100]; + + snprintf(portstr, sizeof(portstr), "%d", data->port); + + if (virNetSocketNewListenTCP(data->lnode, portstr, &lsock, &nlsock) < 0) + goto cleanup; + + for (i = 0 ; i < nlsock ; i++) { + if (virNetSocketListen(lsock[i]) < 0) + goto cleanup; + } + + if (virNetSocketNewConnectTCP(data->cnode, portstr, &csock) < 0) + goto cleanup; + + virNetSocketFree(csock); + + for (i = 0 ; i < nlsock ; i++) { + if (virNetSocketAccept(lsock[i], &ssock) != -1 && ssock) { + char c = 'a'; + if (virNetSocketWrite(ssock, &c, 1) != -1 && + virNetSocketRead(ssock, &c, 1) != -1) { + VIR_DEBUG0("Unexpected client socket present"); + goto cleanup; + } + } + virNetSocketFree(ssock); + ssock = NULL; + } + + ret = 0; + +cleanup: + virNetSocketFree(ssock); + for (i = 0 ; i < nlsock ; i++) + virNetSocketFree(lsock[i]); + VIR_FREE(lsock); + return ret; +} +#endif + + +#ifndef WIN32 +static int testSocketUNIXAccept(const void *data ATTRIBUTE_UNUSED) +{ + virNetSocketPtr lsock = NULL; /* Listen socket */ + virNetSocketPtr ssock = NULL; /* Server socket */ + virNetSocketPtr csock = NULL; /* Client socket */ + int ret = -1; + + char *path; + if (argv0[0] == '/') { + if (virAsprintf(&path, "%s-test.sock", argv0) < 0) { + virReportOOMError(); + goto cleanup; + } + } else { + if (virAsprintf(&path, "%s/%s-test.sock", cwd, argv0) < 0) { + virReportOOMError(); + goto cleanup; + } + } + + if (virNetSocketNewListenUNIX(path, 0700, getgid(), &lsock) < 0) + goto cleanup; + + if (virNetSocketListen(lsock) < 0) + goto cleanup; + + if (virNetSocketNewConnectUNIX(path, false, NULL, &csock) < 0) + goto cleanup; + + virNetSocketFree(csock); + + if (virNetSocketAccept(lsock, &ssock) != -1) { + char c = 'a'; + if (virNetSocketWrite(ssock, &c, 1) != -1) { + VIR_DEBUG0("Unexpected client socket present"); + goto cleanup; + } + } + + ret = 0; + +cleanup: + VIR_FREE(path); + virNetSocketFree(lsock); + virNetSocketFree(ssock); + return ret; +} + + +static int testSocketUNIXAddrs(const void *data ATTRIBUTE_UNUSED) +{ + virNetSocketPtr lsock = NULL; /* Listen socket */ + virNetSocketPtr ssock = NULL; /* Server socket */ + virNetSocketPtr csock = NULL; /* Client socket */ + int ret = -1; + + char *path; + if (argv0[0] == '/') { + if (virAsprintf(&path, "%s-test.sock", argv0) < 0) { + virReportOOMError(); + goto cleanup; + } + } else { + if (virAsprintf(&path, "%s/%s-test.sock", cwd, argv0) < 0) { + virReportOOMError(); + goto cleanup; + } + } + + if (virNetSocketNewListenUNIX(path, 0700, getgid(), &lsock) < 0) + goto cleanup; + + if (STRNEQ(virNetSocketLocalAddrString(lsock), "127.0.0.1;0")) { + VIR_DEBUG0("Unexpected local address"); + goto cleanup; + } + + if (virNetSocketRemoteAddrString(lsock) != NULL) { + VIR_DEBUG0("Unexpected remote address"); + goto cleanup; + } + + if (virNetSocketListen(lsock) < 0) + goto cleanup; + + if (virNetSocketNewConnectUNIX(path, false, NULL, &csock) < 0) + goto cleanup; + + if (STRNEQ(virNetSocketLocalAddrString(csock), "127.0.0.1;0")) { + VIR_DEBUG0("Unexpected local address"); + goto cleanup; + } + + if (STRNEQ(virNetSocketRemoteAddrString(csock), "127.0.0.1;0")) { + VIR_DEBUG0("Unexpected local address"); + goto cleanup; + } + + + if (virNetSocketAccept(lsock, &ssock) < 0) { + VIR_DEBUG0("Unexpected client socket missing"); + goto cleanup; + } + + + if (STRNEQ(virNetSocketLocalAddrString(ssock), "127.0.0.1;0")) { + VIR_DEBUG0("Unexpected local address"); + goto cleanup; + } + + if (STRNEQ(virNetSocketRemoteAddrString(ssock), "127.0.0.1;0")) { + VIR_DEBUG0("Unexpected local address"); + goto cleanup; + } + + + ret = 0; + +cleanup: + VIR_FREE(path); + virNetSocketFree(lsock); + virNetSocketFree(ssock); + virNetSocketFree(csock); + return ret; +} + +static int testSocketCommandNormal(const void *data ATTRIBUTE_UNUSED) +{ + virNetSocketPtr csock = NULL; /* Client socket */ + char buf[100]; + size_t i; + int ret = -1; + virCommandPtr cmd = virCommandNewArgList("/bin/cat", "/dev/zero", NULL); + virCommandAddEnvPassCommon(cmd); + + if (virNetSocketNewConnectCommand(cmd, &csock) < 0) + goto cleanup; + + virNetSocketSetBlocking(csock, true); + + if (virNetSocketRead(csock, buf, sizeof(buf)) < 0) + goto cleanup; + + for (i = 0 ; i < sizeof(buf) ; i++) + if (buf[i] != '\0') + goto cleanup; + + ret = 0; + +cleanup: + virNetSocketFree(csock); + return ret; +} + +static int testSocketCommandFail(const void *data ATTRIBUTE_UNUSED) +{ + virNetSocketPtr csock = NULL; /* Client socket */ + char buf[100]; + int ret = -1; + virCommandPtr cmd = virCommandNewArgList("/bin/cat", "/dev/does-not-exist", NULL); + virCommandAddEnvPassCommon(cmd); + + if (virNetSocketNewConnectCommand(cmd, &csock) < 0) + goto cleanup; + + virNetSocketSetBlocking(csock, true); + + if (virNetSocketRead(csock, buf, sizeof(buf)) == 0) + goto cleanup; + + ret = 0; + +cleanup: + virNetSocketFree(csock); + return ret; +} + +struct testSSHData { + const char *nodename; + const char *service; + const char *binary; + const char *username; + bool noTTY; + const char *netcat; + const char *path; + + const char *expectOut; + bool failConnect; + bool dieEarly; +}; + +static int testSocketSSH(const void *opaque) +{ + const struct testSSHData *data = opaque; + virNetSocketPtr csock = NULL; /* Client socket */ + int ret = -1; + char buf[1024]; + + if (virNetSocketNewConnectSSH(data->nodename, + data->service, + data->binary, + data->username, + data->noTTY, + data->netcat, + data->path, + &csock) < 0) + goto cleanup; + + virNetSocketSetBlocking(csock, true); + + if (data->failConnect) { + if (virNetSocketRead(csock, buf, sizeof(buf)-1) >= 0) + goto cleanup; + } else { + ssize_t rv; + if ((rv = virNetSocketRead(csock, buf, sizeof(buf)-1)) < 0) + goto cleanup; + buf[rv] = '\0'; + + if (!STREQ(buf, data->expectOut)) { + virtTestDifference(stderr, data->expectOut, buf); + goto cleanup; + } + + if (data->dieEarly && + virNetSocketRead(csock, buf, sizeof(buf)-1) >= 0) + goto cleanup; + } + + ret = 0; + +cleanup: + virNetSocketFree(csock); + return ret; +} + +#endif + + +static int +mymain(int argc, char **argv) +{ + int ret = 0; +#ifdef HAVE_IFADDRS_H + bool hasIPv4, hasIPv6; + int freePort; +#endif + + argv0 = argv[0]; + + if (argc > 1) { + fprintf(stderr, "Usage: %s\n", argv0); + return (EXIT_FAILURE); + } + + signal(SIGPIPE, SIG_IGN); + + if (!(getcwd(cwd, sizeof(cwd)))) + return (EXIT_FAILURE); + +#ifdef HAVE_IFADDRS_H + if (checkProtocols(&hasIPv4, &hasIPv6, &freePort) < 0) { + fprintf(stderr, "Cannot identify IPv4/6 availability\n"); + return (EXIT_FAILURE); + } + + if (hasIPv4) { + struct testTCPData tcpData = { "127.0.0.1", freePort, "127.0.0.1" }; + if (virtTestRun("Socket TCP/IPv4 Accept", 1, testSocketTCPAccept, &tcpData) < 0) + ret = -1; + } + if (hasIPv6) { + struct testTCPData tcpData = { "::1", freePort, "::1" }; + if (virtTestRun("Socket TCP/IPv6 Accept", 1, testSocketTCPAccept, &tcpData) < 0) + ret = -1; + } + if (hasIPv6 && hasIPv4) { + struct testTCPData tcpData = { NULL, freePort, "127.0.0.1" }; + if (virtTestRun("Socket TCP/IPv4+IPv6 Accept", 1, testSocketTCPAccept, &tcpData) < 0) + ret = -1; + + tcpData.cnode = "::1"; + if (virtTestRun("Socket TCP/IPv4+IPv6 Accept", 1, testSocketTCPAccept, &tcpData) < 0) + ret = -1; + } +#endif + +#ifndef WIN32 + if (virtTestRun("Socket UNIX Accept", 1, testSocketUNIXAccept, NULL) < 0) + ret = -1; + + if (virtTestRun("Socket UNIX Addrs", 1, testSocketUNIXAddrs, NULL) < 0) + ret = -1; + + if (virtTestRun("Socket External Command /dev/zero", 1, testSocketCommandNormal, NULL) < 0) + ret = -1; + if (virtTestRun("Socket External Command /dev/does-not-exist", 1, testSocketCommandFail, NULL) < 0) + ret = -1; + + struct testSSHData sshData1 = { + .nodename = "somehost", + .path = "/tmp/socket", + .expectOut = "somehost nc -U /tmp/socket\n", + }; + if (virtTestRun("SSH test 1", 1, testSocketSSH, &sshData1) < 0) + ret = -1; + + struct testSSHData sshData2 = { + .nodename = "somehost", + .service = "9000", + .username = "fred", + .netcat = "netcat", + .noTTY = true, + .path = "/tmp/socket", + .expectOut = "-p 9000 -l fred -T -o BatchMode=yes -e none somehost netcat -U /tmp/socket\n", + }; + if (virtTestRun("SSH test 2", 1, testSocketSSH, &sshData2) < 0) + ret = -1; + + struct testSSHData sshData3 = { + .nodename = "nosuchhost", + .path = "/tmp/socket", + .failConnect = true, + }; + if (virtTestRun("SSH test 3", 1, testSocketSSH, &sshData3) < 0) + ret = -1; + + struct testSSHData sshData4 = { + .nodename = "crashyhost", + .path = "/tmp/socket", + .expectOut = "crashyhost nc -U /tmp/socket\n", + .dieEarly = true, + }; + if (virtTestRun("SSH test 4", 1, testSocketSSH, &sshData4) < 0) + ret = -1; + +#endif + + return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +VIRT_TEST_MAIN(mymain) -- 1.7.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list