From 9c9d4d32d318d40870deed7655b07ccfad9b66ce Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 9 Jan 2013 15:11:50 +0000 Subject: [PATCH] Add a port allocator class Introduce a virPortAllocator for managing TCP port allocations. Signed-off-by: Daniel P. Berrange --- .gitignore | 1 + po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 6 ++ src/util/virportallocator.c | 189 ++++++++++++++++++++++++++++++++++ src/util/virportallocator.h | 40 ++++++++ tests/Makefile.am | 17 ++- tests/virportallocatortest.c | 194 +++++++++++++++++++++++++++++++++++ 8 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 src/util/virportallocator.c create mode 100644 src/util/virportallocator.h create mode 100644 tests/virportallocatortest.c diff --git a/.gitignore b/.gitignore index be83e28f91..291d8347e6 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ /tests/virkeyfiletest /tests/virlockspacetest /tests/virnet*test +/tests/virportallocatortest /tests/virshtest /tests/virstringtest /tests/virtimetest diff --git a/po/POTFILES.in b/po/POTFILES.in index fb4312094b..bd2c02e4c1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -167,6 +167,7 @@ src/util/virnodesuspend.c src/util/virobject.c src/util/virpci.c src/util/virpidfile.c +src/util/virportallocator.c src/util/virprocess.c src/util/virrandom.c src/util/virsexpr.c diff --git a/src/Makefile.am b/src/Makefile.am index b1318c55a8..070a089e92 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -97,6 +97,7 @@ UTIL_SOURCES = \ util/virobject.c util/virobject.h \ util/virpci.c util/virpci.h \ util/virpidfile.c util/virpidfile.h \ + util/virportallocator.c util/virportallocator.h \ util/virprocess.c util/virprocess.h \ util/virrandom.h util/virrandom.c \ util/virsexpr.c util/virsexpr.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0f2950a94b..521f8e045c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1798,6 +1798,12 @@ virPidFileWrite; virPidFileWritePath; +# virportallocator.h +virPortAllocatorAcquire; +virPortAllocatorNew; +virPortAllocatorRelease; + + # virprocess.h virProcessAbort; virProcessGetAffinity; diff --git a/src/util/virportallocator.c b/src/util/virportallocator.c new file mode 100644 index 0000000000..033aee4702 --- /dev/null +++ b/src/util/virportallocator.c @@ -0,0 +1,189 @@ +/* + * virportallocator.c: Allocate & track TCP port allocations + * + * Copyright (C) 2013 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, see + * . + * + */ + +#include + +#include +#include + +#include "viralloc.h" +#include "virbitmap.h" +#include "virportallocator.h" +#include "virthread.h" +#include "virerror.h" +#include "virfile.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +struct _virPortAllocator { + virObjectLockable parent; + virBitmapPtr bitmap; + + unsigned short start; + unsigned short end; +}; + +static virClassPtr virPortAllocatorClass; + +static void +virPortAllocatorDispose(void *obj) +{ + virPortAllocatorPtr pa = obj; + + virBitmapFree(pa->bitmap); +} + +static int virPortAllocatorOnceInit(void) +{ + if (!(virPortAllocatorClass = virClassNew(virClassForObjectLockable(), + "virPortAllocator", + sizeof(virPortAllocator), + virPortAllocatorDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virPortAllocator) + +virPortAllocatorPtr virPortAllocatorNew(unsigned short start, + unsigned short end) +{ + virPortAllocatorPtr pa; + + if (start >= end) { + virReportInvalidArg(start, "start port %d must be less than end port %d", + start, end); + return NULL; + } + + if (virPortAllocatorInitialize() < 0) + return NULL; + + if (!(pa = virObjectLockableNew(virPortAllocatorClass))) + return NULL; + + pa->start = start; + pa->end = end; + + if (!(pa->bitmap = virBitmapNew(end-start))) { + virReportOOMError(); + virObjectUnref(pa); + return NULL; + } + + return pa; +} + +int virPortAllocatorAcquire(virPortAllocatorPtr pa, + unsigned short *port) +{ + int ret = -1; + unsigned short i; + int fd = -1; + + *port = 0; + virObjectLock(pa); + + for (i = pa->start ; i < pa->end && !*port; i++) { + int reuse = 1; + struct sockaddr_in addr; + bool used = false; + + if (virBitmapGetBit(pa->bitmap, + i - pa->start, &used) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to query port %d"), i); + goto cleanup; + } + + if (used) + continue; + + addr.sin_family = AF_INET; + addr.sin_port = htons(i); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + virReportSystemError(errno, "%s", + _("Unable to open test socket")); + goto cleanup; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&reuse, sizeof(reuse)) < 0) { + virReportSystemError(errno, "%s", + _("Unable to set socket reuse addr flag")); + goto cleanup; + } + + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (errno != EADDRINUSE) { + virReportSystemError(errno, + _("Unable to bind to port %d"), i); + goto cleanup; + } + /* In use, try next */ + VIR_FORCE_CLOSE(fd); + } else { + /* Add port to bitmap of reserved ports */ + if (virBitmapSetBit(pa->bitmap, + i - pa->start) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to reserve port %d"), i); + goto cleanup; + } + *port = i; + } + } + + ret = 0; +cleanup: + virObjectUnlock(pa); + VIR_FORCE_CLOSE(fd); + return ret; +} + +int virPortAllocatorRelease(virPortAllocatorPtr pa, + unsigned short port) +{ + int ret = -1; + virObjectLock(pa); + + if (port < pa->start || + port >= pa->end) { + virReportInvalidArg(port, "port %d must be in range (%d, %d)", + port, pa->start, pa->end); + goto cleanup; + } + + if (virBitmapClearBit(pa->bitmap, + port - pa->start) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to release port %d"), + port); + goto cleanup; + } + + ret = 0; +cleanup: + virObjectUnlock(pa); + return ret; +} diff --git a/src/util/virportallocator.h b/src/util/virportallocator.h new file mode 100644 index 0000000000..a5e68f7c44 --- /dev/null +++ b/src/util/virportallocator.h @@ -0,0 +1,40 @@ +/* + * virportallocator.h: Allocate & track TCP port allocations + * + * Copyright (C) 2013 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, see + * . + * + */ + +#ifndef __VIR_PORT_ALLOCATOR_H__ +# define __VIR_PORT_ALLOCATOR_H__ + +# include "internal.h" +# include "virobject.h" + +typedef struct _virPortAllocator virPortAllocator; +typedef virPortAllocator *virPortAllocatorPtr; + +virPortAllocatorPtr virPortAllocatorNew(unsigned short start, + unsigned short end); + +int virPortAllocatorAcquire(virPortAllocatorPtr pa, + unsigned short *port); + +int virPortAllocatorRelease(virPortAllocatorPtr pa, + unsigned short port); + +#endif /* __VIR_PORT_ALLOCATOR_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 61b0a0c2ff..9da0b4a23f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -98,6 +98,7 @@ test_programs = virshtest sockettest \ virbitmaptest \ virlockspacetest \ virstringtest \ + virportallocatortest \ sysinfotest \ $(NULL) @@ -235,7 +236,9 @@ endif EXTRA_DIST += $(test_scripts) -test_libraries = libshunload.la +test_libraries = libshunload.la \ + libvirportallocatormock.la \ + $(NULL) if WITH_QEMU test_libraries += libqemumonitortestutils.la endif @@ -565,6 +568,18 @@ virlockspacetest_SOURCES = \ virlockspacetest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) virlockspacetest_LDADD = $(LDADDS) +virportallocatortest_SOURCES = \ + virportallocatortest.c testutils.h testutils.c +virportallocatortest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) +virportallocatortest_LDADD = $(LDADDS) + +libvirportallocatormock_la_SOURCES = \ + virportallocatortest.c +libvirportallocatormock_la_CFLAGS = $(AM_CFLAGS) -DMOCK_HELPER=1 +libvirportallocatormock_la_LDFLAGS = -module -avoid-version \ + -rpath /evil/libtool/hack/to/force/shared/lib/creation + + viruritest_SOURCES = \ viruritest.c testutils.h testutils.c viruritest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) diff --git a/tests/virportallocatortest.c b/tests/virportallocatortest.c new file mode 100644 index 0000000000..93577d7ff7 --- /dev/null +++ b/tests/virportallocatortest.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2013 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, see + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#ifdef MOCK_HELPER +# include "internal.h" +# include +# include +# include + +int bind(int sockfd ATTRIBUTE_UNUSED, + const struct sockaddr *addr, + socklen_t addrlen ATTRIBUTE_UNUSED) +{ + struct sockaddr_in *saddr = (struct sockaddr_in *)addr; + + if (saddr->sin_port == htons(5900) || + saddr->sin_port == htons(5904) || + saddr->sin_port == htons(5905) || + saddr->sin_port == htons(5906)) { + errno = EADDRINUSE; + return -1; + } + + return 0; +} + +#else +# include + +# include "testutils.h" +# include "virutil.h" +# include "virerror.h" +# include "viralloc.h" +# include "virlog.h" + +# include "virportallocator.h" + +# define VIR_FROM_THIS VIR_FROM_RPC + + +static int testAllocAll(const void *args ATTRIBUTE_UNUSED) +{ + virPortAllocatorPtr alloc = virPortAllocatorNew(5900, 5910); + int ret = -1; + unsigned short p1, p2, p3, p4, p5, p6, p7; + + if (virPortAllocatorAcquire(alloc, &p1) < 0) + goto cleanup; + if (p1 != 5901) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5901, got %d", p1); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p2) < 0) + goto cleanup; + if (p2 != 5902) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5902, got %d", p2); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p3) < 0) + goto cleanup; + if (p3 != 5903) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5903, got %d", p3); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p4) < 0) + goto cleanup; + if (p4 != 5907) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5907, got %d", p4); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p5) < 0) + goto cleanup; + if (p5 != 5908) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5908, got %d", p5); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p6) < 0) + goto cleanup; + if (p6 != 5909) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5909, got %d", p6); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p7) < 0) + goto cleanup; + if (p7 != 0) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 0, got %d", p7); + goto cleanup; + } + + ret = 0; +cleanup: + virObjectUnref(alloc); + return ret; +} + + + +static int testAllocReuse(const void *args ATTRIBUTE_UNUSED) +{ + virPortAllocatorPtr alloc = virPortAllocatorNew(5900, 5910); + int ret = -1; + unsigned short p1, p2, p3, p4; + + if (virPortAllocatorAcquire(alloc, &p1) < 0) + goto cleanup; + if (p1 != 5901) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5901, got %d", p1); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p2) < 0) + goto cleanup; + if (p2 != 5902) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5902, got %d", p2); + goto cleanup; + } + + if (virPortAllocatorAcquire(alloc, &p3) < 0) + goto cleanup; + if (p3 != 5903) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5903, got %d", p3); + goto cleanup; + } + + + if (virPortAllocatorRelease(alloc, p2) < 0) + goto cleanup; + + if (virPortAllocatorAcquire(alloc, &p4) < 0) + goto cleanup; + if (p4 != 5902) { + if (virTestGetDebug()) + fprintf(stderr, "Expected 5902, got %d", p4); + goto cleanup; + } + + ret = 0; +cleanup: + virObjectUnref(alloc); + return ret; +} + + +static int +mymain(void) +{ + int ret = 0; + + if (virtTestRun("Test alloc all", 1, testAllocAll, NULL) < 0) + ret = -1; + + if (virtTestRun("Test alloc reuse", 1, testAllocReuse, NULL) < 0) + ret = -1; + + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/libvirportallocatormock.so") +#endif