Also try to bind on IPv6 to check if the port is occupied. Change the mocked bind in the test to return EADDRINUSE for some ports only for the IPv4/IPv6 socket if we're testing on a host with IPv6 compiled in. Also mock socket() to make it fail with EAFNOTSUPPORTED if LIBVIRT_TEST_IPV4ONLY is set in the environment, to simulate a host without IPv6 support in the kernel. The tests are repeated again with this variable set. https://bugzilla.redhat.com/show_bug.cgi?id=1025407 --- src/util/virportallocator.c | 46 +++++++++++++++++++++++++----- tests/virportallocatortest.c | 68 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/util/virportallocator.c b/src/util/virportallocator.c index 5d51434..06174b0 100644 --- a/src/util/virportallocator.c +++ b/src/util/virportallocator.c @@ -100,21 +100,45 @@ virPortAllocatorPtr virPortAllocatorNew(const char *name, } static int virPortAllocatorBindToPort(bool *used, - unsigned short port) + unsigned short port, + int family) { - struct sockaddr_in addr = { + struct sockaddr_in6 addr6 = { + .sin6_family = AF_INET6, + .sin6_port = htons(port), + .sin6_addr = IN6ADDR_ANY_INIT, + }; + struct sockaddr_in addr4 = { .sin_family = AF_INET, .sin_port = htons(port), .sin_addr.s_addr = htonl(INADDR_ANY) }; + struct sockaddr* addr; + size_t addrlen; + int v6only = 1; int reuse = 1; int ret = -1; int fd = -1; + bool ipv6 = false; + + if (family == AF_INET6) { + addr = (struct sockaddr*)&addr6; + addrlen = sizeof(addr6); + ipv6 = true; + } else if (family == AF_INET) { + addr = (struct sockaddr*)&addr4; + addrlen = sizeof(addr4); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown family %d"), family); + return -1; + } *used = false; - fd = socket(PF_INET, SOCK_STREAM, 0); + fd = socket(family, SOCK_STREAM, 0); if (fd < 0) { + if (errno == EAFNOSUPPORT) + return 0; virReportSystemError(errno, "%s", _("Unable to open test socket")); goto cleanup; } @@ -126,7 +150,14 @@ static int virPortAllocatorBindToPort(bool *used, goto cleanup; } - if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (ipv6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&v6only, + sizeof(v6only)) < 0) { + virReportSystemError(errno, "%s", + _("Unable to set IPV6_V6ONLY flag")); + goto cleanup; + } + + if (bind(fd, addr, addrlen) < 0) { if (errno == EADDRINUSE) { *used = true; ret = 0; @@ -152,7 +183,7 @@ int virPortAllocatorAcquire(virPortAllocatorPtr pa, virObjectLock(pa); for (i = pa->start; i <= pa->end && !*port; i++) { - bool used = false; + bool used = false, v6used = false; if (virBitmapGetBit(pa->bitmap, i - pa->start, &used) < 0) { @@ -164,10 +195,11 @@ int virPortAllocatorAcquire(virPortAllocatorPtr pa, if (used) continue; - if (virPortAllocatorBindToPort(&used, i) < 0) + if (virPortAllocatorBindToPort(&v6used, i, AF_INET6) < 0 || + virPortAllocatorBindToPort(&used, i, AF_INET) < 0) goto cleanup; - if (!used) { + if (!used && !v6used) { /* Add port to bitmap of reserved ports */ if (virBitmapSetBit(pa->bitmap, i - pa->start) < 0) { diff --git a/tests/virportallocatortest.c b/tests/virportallocatortest.c index 721356e..7fe18df 100644 --- a/tests/virportallocatortest.c +++ b/tests/virportallocatortest.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Red Hat, Inc. + * Copyright (C) 2013-2014 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 @@ -19,6 +19,8 @@ */ #include <config.h> +#include <stdlib.h> +#include "virfile.h" #ifdef MOCK_HELPER # include "internal.h" @@ -26,6 +28,47 @@ # include <errno.h> # include <arpa/inet.h> # include <netinet/in.h> +# include <stdio.h> +# include <dlfcn.h> + +static bool host_has_ipv6 = false; +static int (*realsocket)(int domain, int type, int protocol); + +static void init_syms(void) +{ + int fd; + + if (realsocket) + return; + + realsocket = dlsym(RTLD_NEXT, "socket"); + + if (!realsocket) { + fprintf(stderr, "Unable to find 'socket' symbol\n"); + abort(); + } + + fd = realsocket(AF_INET6, SOCK_STREAM, 0); + if (fd < 0) + return; + + host_has_ipv6 = true; + VIR_FORCE_CLOSE(fd); +} + +int socket(int domain, + int type, + int protocol) +{ + init_syms(); + + if (getenv("LIBVIRT_TEST_IPV4ONLY") && domain == AF_INET6) { + errno = EAFNOSUPPORT; + return -1; + } + + return realsocket(domain, type, protocol); +} int bind(int sockfd ATTRIBUTE_UNUSED, const struct sockaddr *addr, @@ -35,6 +78,19 @@ int bind(int sockfd ATTRIBUTE_UNUSED, memcpy(&saddr, addr, sizeof(saddr)); + if (host_has_ipv6 && !getenv("LIBVIRT_TEST_IPV4ONLY")) { + if (saddr.sin_port == htons(5900) || + (saddr.sin_family == AF_INET && + saddr.sin_port == htons(5904)) || + (saddr.sin_family == AF_INET6 && + (saddr.sin_port == htons(5905) || + saddr.sin_port == htons(5906)))) { + errno = EADDRINUSE; + return -1; + } + return 0; + } + if (saddr.sin_port == htons(5900) || saddr.sin_port == htons(5904) || saddr.sin_port == htons(5905) || @@ -47,13 +103,11 @@ int bind(int sockfd ATTRIBUTE_UNUSED, } #else -# include <stdlib.h> # include "testutils.h" # include "virutil.h" # include "virerror.h" # include "viralloc.h" -# include "virfile.h" # include "virlog.h" # include "virportallocator.h" # include "virstring.h" @@ -195,6 +249,14 @@ mymain(void) if (virtTestRun("Test alloc reuse", testAllocReuse, NULL) < 0) ret = -1; + setenv("LIBVIRT_TEST_IPV4ONLY", "really", 1); + + if (virtTestRun("Test IPv4-only alloc all", testAllocAll, NULL) < 0) + ret = -1; + + if (virtTestRun("Test IPv4-only alloc reuse", testAllocReuse, NULL) < 0) + ret = -1; + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 1.8.3.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list