Skip to content
Permalink
Browse files

Implement .preconnect callback.

This is called just after TCP connection but before any NBD
negotiation or TLS authentication.  It can be used for early
whitelisting of connections or, with more care, for rate limiting of
connections.  You have to be careful of denial of service attacks
here.

The callback can also be filtered in filters.

This is so far only available from the C API.
  • Loading branch information
rwmjones committed Nov 29, 2019
1 parent a5bd5b5 commit 33e1ed2ed9d5cbf84d7f03462c67131acacf808a
6 TODO
@@ -73,12 +73,6 @@ General ideas for improvements
name(s) that a plugin might want to support. Probably we should
deprecate the -e option entirely since it does nothing useful.

* Add plugin "connect" method. This would be called on a connection
before handshaking or TLS negotiation, and could be used (with
nbdkit_peer_name) to accept or reject connections based on IP
address, rather like a poor man's TCP wrappers. See also commit
c05686f9577f.

* Background thread for filters. Some filters (readahead, cache and
proposed scan filter - see below) could be more effective if they
were able to defer work to a background thread. However it's not as
@@ -126,8 +126,8 @@ which is required.

=head1 NEXT PLUGIN

F<nbdkit-filter.h> defines three function types
(C<nbdkit_next_config>, C<nbdkit_next_config_complete>,
F<nbdkit-filter.h> defines some function types (C<nbdkit_next_config>,
C<nbdkit_next_config_complete>, C<nbdkit_next_preconnect>,
C<nbdkit_next_open>) and a structure called C<struct nbdkit_next_ops>.
These abstract the next plugin or filter in the chain. There is also
an opaque pointer C<nxdata> which must be passed along when calling
@@ -284,6 +284,17 @@ filter doesn't slow down other filters or plugins.
If there is an error, C<.thread_model> should call C<nbdkit_error>
with an error message and return C<-1>.

=head2 C<.preconnect>

int (*preconnect) (nbdkit_next_preconnect *next, void *nxdata,
int readonly);

This intercepts the plugin C<.preconnect> method and can be used to
filter access to the server.

If there is an error, C<.preconnect> should call C<nbdkit_error> with
an error message and return C<-1>.

=head2 C<.open>

void * (*open) (nbdkit_next_open *next, void *nxdata,
@@ -142,6 +142,11 @@ before any connections are accepted. However, during C<nbdkit
C<.config_complete> (so a plugin which determines the results from a
script must be prepared for a missing script).

=item C<.preconnect>

Called when a TCP connection has been made to the server. This
happens early, before NBD or TLS negotiation.

=item C<.open>

A new client has connected and finished the NBD handshake. TLS
@@ -166,11 +171,11 @@ whether particular flags will ever be passed to a data callback.

The client has disconnected.

=item C<.open> ... C<.close>
=item C<.preconnect>, C<.open> ... C<.close>

The sequence C<.open> ... C<.close> can be called repeatedly over the
lifetime of the plugin, and can be called in parallel (depending on
the thread model).
The sequence C<.preconnect>, C<.open> ... C<.close> can be called
repeatedly over the lifetime of the plugin, and can be called in
parallel (depending on the thread model).

=item C<.unload>

@@ -438,6 +443,29 @@ are silently ignored.
If there is an error, C<.thread_model> should call C<nbdkit_error>
with an error message and return C<-1>.

=head2 C<.preconnect>

int preconnect (int readonly);

This optional callback is called when a TCP connection has been made
to the server. This happens early, before NBD or TLS negotiation. If
TLS authentication is required to access the server, then it has
B<not> been negotiated at this point.

For security reasons (to avoid denial of service attacks) this
callback should be written to be as fast and take as few resources as
possible. If you use this callback, only use it to do basic access
control, such as checking C<nbdkit_peer_name> against a whitelist (see
L</PEER NAME>). It may be better to do access control outside the
server, for example using TCP wrappers or a firewall.

The C<readonly> flag informs the plugin that the server was started
with the I<-r> flag on the command line.

Returning C<0> will allow the connection to continue. If there is an
error or you want to deny the connection, call C<nbdkit_error> with an
error message and return C<-1>.

=head2 C<.open>

void *open (int readonly);
@@ -459,7 +487,9 @@ false from the C<.can_write> callback. So if your plugin can only
serve read-only, you can ignore this parameter.

This callback is called after the NBD handshake has completed, which
includes TLS authentication (if required).
includes TLS authentication (if required). If the plugin defines a
C<.preconnect> callback, then it must be called and return with
success before C<.open> is called.

If there is an error, C<.open> should call C<nbdkit_error> with an
error message and return C<NULL>.
@@ -64,8 +64,8 @@ extern struct nbdkit_extent nbdkit_get_extent (const struct nbdkit_extents *,
typedef int nbdkit_next_config (void *nxdata,
const char *key, const char *value);
typedef int nbdkit_next_config_complete (void *nxdata);
typedef int nbdkit_next_open (void *nxdata,
int readonly);
typedef int nbdkit_next_preconnect (void *nxdata, int readonly);
typedef int nbdkit_next_open (void *nxdata, int readonly);

struct nbdkit_next_ops {
/* Performs close + open on the underlying chain.
@@ -129,6 +129,7 @@ struct nbdkit_filter {
int (*config_complete) (nbdkit_next_config_complete *next, void *nxdata);
const char *config_help;
int (*thread_model) (void);
int (*preconnect) (nbdkit_next_preconnect *next, void *nxdata, int readonly);

void * (*open) (nbdkit_next_open *next, void *nxdata,
int readonly);
@@ -134,6 +134,8 @@ struct nbdkit_plugin {
int (*thread_model) (void);

int (*can_fast_zero) (void *handle);

int (*preconnect) (int readonly);
};

extern void nbdkit_set_error (int err);
@@ -159,6 +159,9 @@ handle_single_connection (int sockin, int sockout)
plugin_name = "(unknown)";
threadlocal_set_name (plugin_name);

if (backend && backend->preconnect (backend, conn, read_only) == -1)
goto done;

/* NBD handshake.
*
* Note that this calls the backend .open callback when it is safe
@@ -181,6 +181,27 @@ filter_config_complete (struct backend *b)
b->next->config_complete (b->next);
}

static int
next_preconnect (void *nxdata, int readonly)
{
struct b_conn *b_conn = nxdata;
return b_conn->b->preconnect (b_conn->b, b_conn->conn, readonly);
}

static int
filter_preconnect (struct backend *b, struct connection *conn, int readonly)
{
struct backend_filter *f = container_of (b, struct backend_filter, backend);
struct b_conn nxdata = { .b = b->next, .conn = conn };

debug ("%s: preconnect", b->name);

if (f->filter.preconnect)
return f->filter.preconnect (next_preconnect, &nxdata, readonly);
else
return b->next->preconnect (b->next, conn, readonly);
}

/* magic_config_key only applies to plugins, so this passes the
* request through to the plugin (hence the name).
*/
@@ -668,6 +689,7 @@ static struct backend filter_functions = {
.config = filter_config,
.config_complete = filter_config_complete,
.magic_config_key = plugin_magic_config_key,
.preconnect = filter_preconnect,
.open = filter_open,
.prepare = filter_prepare,
.finalize = filter_finalize,
@@ -335,6 +335,7 @@ struct backend {
void (*config) (struct backend *, const char *key, const char *value);
void (*config_complete) (struct backend *);
const char *(*magic_config_key) (struct backend *);
int (*preconnect) (struct backend *, struct connection *conn, int readonly);
void *(*open) (struct backend *, struct connection *conn, int readonly);
int (*prepare) (struct backend *, struct connection *conn, void *handle,
int readonly);
@@ -158,6 +158,7 @@ plugin_dump_fields (struct backend *b)
HAS (config);
HAS (config_complete);
HAS (config_help);
HAS (preconnect);
HAS (open);
HAS (close);
HAS (get_size);
@@ -233,6 +234,19 @@ plugin_magic_config_key (struct backend *b)
return p->plugin.magic_config_key;
}

static int
plugin_preconnect (struct backend *b, struct connection *conn, int readonly)
{
struct backend_plugin *p = container_of (b, struct backend_plugin, backend);

debug ("%s: preconnect", b->name);

if (!p->plugin.preconnect)
return 0;

return p->plugin.preconnect (readonly);
}

static void *
plugin_open (struct backend *b, struct connection *conn, int readonly)
{
@@ -656,6 +670,7 @@ static struct backend plugin_functions = {
.config = plugin_config,
.config_complete = plugin_config_complete,
.magic_config_key = plugin_magic_config_key,
.preconnect = plugin_preconnect,
.open = plugin_open,
.prepare = plugin_prepare,
.finalize = plugin_finalize,
@@ -74,6 +74,14 @@ test_layers_filter_config_complete (nbdkit_next_config_complete *next,
#define test_layers_filter_config_help \
"test_layers_" layer "_config_help"

static int
test_layers_filter_preconnect (nbdkit_next_preconnect *next,
void *nxdata, int readonly)
{
DEBUG_FUNCTION;
return next (nxdata, readonly);
}

static void *
test_layers_filter_open (nbdkit_next_open *next, void *nxdata, int readonly)
{
@@ -267,6 +275,7 @@ static struct nbdkit_filter filter = {
.config = test_layers_filter_config,
.config_complete = test_layers_filter_config_complete,
.config_help = test_layers_filter_config_help,
.preconnect = test_layers_filter_preconnect,
.open = test_layers_filter_open,
.close = test_layers_filter_close,
.prepare = test_layers_filter_prepare,
@@ -72,6 +72,13 @@ test_layers_plugin_config_complete (void)

#define test_layers_plugin_config_help "test_layers_plugin_config_help"

static int
test_layers_plugin_preconnect (int readonly)
{
DEBUG_FUNCTION;
return 0;
}

static void *
test_layers_plugin_open (int readonly)
{
@@ -224,6 +231,7 @@ static struct nbdkit_plugin plugin = {
.config = test_layers_plugin_config,
.config_complete = test_layers_plugin_config_complete,
.config_help = test_layers_plugin_config_help,
.preconnect = test_layers_plugin_preconnect,
.open = test_layers_plugin_open,
.close = test_layers_plugin_close,
.get_size = test_layers_plugin_get_size,
@@ -288,6 +288,20 @@ main (int argc, char *argv[])
"test_layers_plugin_config_complete",
NULL);

/* preconnect methods called in outer-to-inner order, complete
* in inner-to-outer order.
*/
log_verify_seen_in_order
("testlayersfilter3: preconnect",
"filter3: test_layers_filter_preconnect",
"testlayersfilter2: preconnect",
"filter2: test_layers_filter_preconnect",
"testlayersfilter1: preconnect",
"filter1: test_layers_filter_preconnect",
"testlayersplugin: preconnect",
"test_layers_plugin_preconnect",
NULL);

/* open methods called in outer-to-inner order, but thanks to next
* pointer, complete in inner-to-outer order. */
log_verify_seen_in_order

0 comments on commit 33e1ed2

Please sign in to comment.
You can’t perform that action at this time.