--- docs/nbdkit-service.pod | 1 + filters/exitlast/nbdkit-exitlast-filter.pod | 1 + filters/ip/nbdkit-ip-filter.pod | 1 + filters/limit/nbdkit-limit-filter.pod | 61 +++++++++ filters/rate/nbdkit-rate-filter.pod | 1 + configure.ac | 2 + filters/limit/Makefile.am | 66 ++++++++++ tests/Makefile.am | 3 + filters/limit/limit.c | 129 ++++++++++++++++++++ tests/test-limit.sh | 81 ++++++++++++ TODO | 2 - 11 files changed, 346 insertions(+), 2 deletions(-) diff --git a/docs/nbdkit-service.pod b/docs/nbdkit-service.pod index ecc8e94b..62ce7831 100644 --- a/docs/nbdkit-service.pod +++ b/docs/nbdkit-service.pod @@ -145,6 +145,7 @@ L</SOCKET ACTIVATION>. L<nbdkit(1)>, L<nbdkit-exitlast-filter(1)>, L<nbdkit-ip-filter(1)>, +L<nbdkit-limit-filter(1)>, L<systemd(1)>, L<systemd.socket(5)>, L<syslog(3)>, diff --git a/filters/exitlast/nbdkit-exitlast-filter.pod b/filters/exitlast/nbdkit-exitlast-filter.pod index e0fbd6ea..207ad4c8 100644 --- a/filters/exitlast/nbdkit-exitlast-filter.pod +++ b/filters/exitlast/nbdkit-exitlast-filter.pod @@ -43,6 +43,7 @@ C<nbdkit-exitlast-filter> first appeared in nbdkit 1.20. L<nbdkit(1)>, L<nbdkit-ip-filter(1)>, +L<nbdkit-limit-filter(1)>, L<nbdkit-rate-filter(1)>, L<nbdkit-captive(1)>, L<nbdkit-service(1)>, diff --git a/filters/ip/nbdkit-ip-filter.pod b/filters/ip/nbdkit-ip-filter.pod index 1621ebec..8ff98651 100644 --- a/filters/ip/nbdkit-ip-filter.pod +++ b/filters/ip/nbdkit-ip-filter.pod @@ -146,6 +146,7 @@ C<nbdkit-ip-filter> first appeared in nbdkit 1.18. L<nbdkit(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-limit-filter(1)>, L<nbdkit-filter(3)>. =head1 AUTHORS diff --git a/filters/limit/nbdkit-limit-filter.pod b/filters/limit/nbdkit-limit-filter.pod new file mode 100644 index 00000000..e334decb --- /dev/null +++ b/filters/limit/nbdkit-limit-filter.pod @@ -0,0 +1,61 @@ +=head1 NAME + +nbdkit-limit-filter - limit number of clients that can connect + +=head1 SYNOPSIS + + nbdkit --filter=limit PLUGIN [limit=N] + +=head1 DESCRIPTION + +C<nbdkit-limit-filter> is an nbdkit filter that limits the number of +clients which can connect concurrently. If more than C<limit=N> +(default: 1) clients try to connect at the same time then later +clients are rejected. + +=head1 PARAMETERS + +=over 4 + +=item B<limit=>N + +Limit the number of concurrent clients to C<N>. This parameter is +optional. If not specified then the limit defaults to 1. You can +also set this to 0 to make the number of clients unlimited (ie. +disable the filter). + +=back + +=head1 FILES + +=over 4 + +=item F<$filterdir/nbdkit-limit-filter.so> + +The filter. + +Use C<nbdkit --dump-config> to find the location of C<$filterdir>. + +=back + +=head1 VERSION + +C<nbdkit-limit-filter> first appeared in nbdkit 1.20. + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-exitlast-filter(1)>, +L<nbdkit-ip-filter(1)>, +L<nbdkit-noparallel-filter(1)>, +L<nbdkit-rate-filter(1)>, +L<nbdkit-filter(3)>, +L<nbdkit-plugin(3)>. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 2020 Red Hat Inc. diff --git a/filters/rate/nbdkit-rate-filter.pod b/filters/rate/nbdkit-rate-filter.pod index acf7d8b0..fc5f211d 100644 --- a/filters/rate/nbdkit-rate-filter.pod +++ b/filters/rate/nbdkit-rate-filter.pod @@ -130,6 +130,7 @@ L<nbdkit(1)>, L<nbdkit-blocksize-filter(1)>, L<nbdkit-delay-filter(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-limit-filter(1)>, L<nbdkit-filter(3)>, L<iptables(8)>, L<tc(8)>. diff --git a/configure.ac b/configure.ac index 0b980238..2db4b546 100644 --- a/configure.ac +++ b/configure.ac @@ -102,6 +102,7 @@ filters="\ extentlist \ fua \ ip \ + limit \ log \ nocache \ noextents \ @@ -1029,6 +1030,7 @@ AC_CONFIG_FILES([Makefile filters/extentlist/Makefile filters/fua/Makefile filters/ip/Makefile + filters/limit/Makefile filters/log/Makefile filters/nocache/Makefile filters/noextents/Makefile diff --git a/filters/limit/Makefile.am b/filters/limit/Makefile.am new file mode 100644 index 00000000..14d53ec4 --- /dev/null +++ b/filters/limit/Makefile.am @@ -0,0 +1,66 @@ +# nbdkit +# Copyright (C) 2019-2020 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +include $(top_srcdir)/common-rules.mk + +EXTRA_DIST = nbdkit-limit-filter.pod + +filter_LTLIBRARIES = nbdkit-limit-filter.la + +nbdkit_limit_filter_la_SOURCES = \ + limit.c \ + $(top_srcdir)/include/nbdkit-filter.h \ + $(NULL) + +nbdkit_limit_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/utils \ + $(NULL) +nbdkit_limit_filter_la_CFLAGS = $(WARNINGS_CFLAGS) +nbdkit_limit_filter_la_LDFLAGS = \ + -module -avoid-version -shared \ + -Wl,--version-script=$(top_srcdir)/filters/filters.syms \ + $(NULL) +nbdkit_limit_filter_la_LIBADD = \ + $(top_builddir)/common/utils/libutils.la \ + $(NULL) + +if HAVE_POD + +man_MANS = nbdkit-limit-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-limit-filter.1: nbdkit-limit-filter.pod + $(PODWRAPPER) --section=1 --man $@ \ + --html $(top_builddir)/html/$@.html \ + $< + +endif HAVE_POD diff --git a/tests/Makefile.am b/tests/Makefile.am index 2ac8fb31..324339c1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1064,6 +1064,9 @@ TESTS += test-fua.sh # ip filter test. TESTS += test-ip-filter.sh +# limit filter test. +TESTS += test-limit.sh + # log filter test. TESTS += test-log.sh diff --git a/filters/limit/limit.c b/filters/limit/limit.c new file mode 100644 index 00000000..563481fa --- /dev/null +++ b/filters/limit/limit.c @@ -0,0 +1,129 @@ +/* nbdkit + * Copyright (C) 2019-2020 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> + +#include <nbdkit-filter.h> + +#include "cleanup.h" + +/* Counts client connections. */ +static unsigned connections; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +/* Client limit (0 = filter is disabled). */ +static unsigned limit = 1; + +static int +limit_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + const char *key, const char *value) +{ + if (strcmp (key, "limit") == 0) { + if (nbdkit_parse_unsigned ("limit", value, &limit) == -1) + return -1; + return 0; + } + + return next (nxdata, key, value); +} + +static void +too_many_clients_error (void) +{ + nbdkit_error ("limit: too many clients connected, connection rejected"); +} + +/* We limit connections in the preconnect stage (in particular before + * any heavyweight NBD or TLS negotiations has been done). However we + * count connections in the open/close calls since clients can drop + * out between preconnect and open. + */ +static int +limit_preconnect (nbdkit_next_preconnect *next, nbdkit_backend *nxdata, + int readonly) +{ + if (next (nxdata, readonly) == -1) + return -1; + + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + + if (limit > 0 && connections >= limit) { + too_many_clients_error (); + return -1; + } + + return 0; +} + +static void * +limit_open (nbdkit_next_open *next, nbdkit_backend *nxdata, + int readonly) +{ + if (next (nxdata, readonly) == -1) + return NULL; + + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + + /* We have to check again because clients can artificially slow down + * the NBD negotiation in order to bypass the limit otherwise. + */ + if (limit > 0 && connections >= limit) { + too_many_clients_error (); + return NULL; + } + + connections++; + return NBDKIT_HANDLE_NOT_NEEDED; +} + +static void +limit_close (void *handle) +{ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + connections--; +} + +static struct nbdkit_filter filter = { + .name = "limit", + .longname = "nbdkit limit filter", + .config = limit_config, + .preconnect = limit_preconnect, + .open = limit_open, + .close = limit_close, +}; + +NBDKIT_REGISTER_FILTER(filter) diff --git a/tests/test-limit.sh b/tests/test-limit.sh new file mode 100755 index 00000000..aa7c2b19 --- /dev/null +++ b/tests/test-limit.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2019-2020 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +source ./functions.sh +set -e +set -x + +requires nbdsh --version + +sock=`mktemp -u` +files="limit.pid $sock" +cleanup_fn rm -f $files + +# Start nbdkit with the limit filter and a limit of 2 clients. +start_nbdkit -P limit.pid -U $sock --filter=limit memory size=1M limit=2 + +export sock + +nbdsh -c - <<'EOF' +import os +import sys +import time + +sock = os.environ["sock"] + +# It should be possible to connect two clients. +# Note that nbdsh creates the ‘h’ handle implicitly. +h.connect_unix (sock) +h2 = nbd.NBD () +h2.connect_unix (sock) + +# A third connection is expected to fail. +try: + h3 = nbd.NBD () + h3.connect_unix (sock) + # This should not happen. + sys.exit (1) +except nbd.Error: + pass + +# Close one of the existing connections. +del h2 + +# There's a possible race between closing the client socket +# and nbdkit noticing and closing the connection. +time.sleep (5) + +# Now a new connection should be possible. +h4 = nbd.NBD () +h4.connect_unix (sock) + +EOF diff --git a/TODO b/TODO index cd2e47a5..e094c332 100644 --- a/TODO +++ b/TODO @@ -10,8 +10,6 @@ General ideas for improvements sizes and threads, as that should make it easier to identify systematic issues. -* Limit number of incoming connections (like qemu-nbd -e). - * For parallel plugins, only create threads on demand from parallel client requests, rather than pre-creating all threads at connection time, up to the thread pool size limit. Of course, once created, a -- 2.25.0