From 6285e36e5af01b53930f59d275b2e33d7665842d Mon Sep 17 00:00:00 2001 From: Ben Walker Date: Tue, 23 Jan 2018 15:03:38 -0700 Subject: [PATCH] nvmf: Add RPC methods to manage allowed hosts Three new RPC methods are added to allow modification of the NVMe-oF subsystem allowed host whitelist at runtime: - nvmf_subsystem_add_host - nvmf_subsystem_remove_host - nvmf_subsystem_allow_any_host Change-Id: I5c98658f949dad013165c04497cca49867022ba0 Signed-off-by: Ben Walker Reviewed-on: https://review.gerrithub.io/396063 Tested-by: SPDK Automated Test System Reviewed-by: Jim Harris Reviewed-by: Changpeng Liu --- app/nvmf_tgt/nvmf_rpc.c | 222 ++++++++++++++++++++++++++++++++++++++++ doc/jsonrpc.md | 111 ++++++++++++++++++++ include/spdk/nvmf.h | 11 ++ lib/nvmf/subsystem.c | 60 ++++++++--- scripts/rpc.py | 16 +++ scripts/rpc/nvmf.py | 21 ++++ test/nvmf/rpc/rpc.sh | 35 ++++++- 7 files changed, 463 insertions(+), 13 deletions(-) diff --git a/app/nvmf_tgt/nvmf_rpc.c b/app/nvmf_tgt/nvmf_rpc.c index edcf0a273..3a78115e0 100644 --- a/app/nvmf_tgt/nvmf_rpc.c +++ b/app/nvmf_tgt/nvmf_rpc.c @@ -979,3 +979,225 @@ nvmf_rpc_subsystem_add_ns(struct spdk_jsonrpc_request *request, } } SPDK_RPC_REGISTER("nvmf_subsystem_add_ns", nvmf_rpc_subsystem_add_ns) + +enum nvmf_rpc_host_op { + NVMF_RPC_HOST_ADD, + NVMF_RPC_HOST_REMOVE, + NVMF_RPC_HOST_ALLOW_ANY, +}; + +struct nvmf_rpc_host_ctx { + struct spdk_jsonrpc_request *request; + + char *nqn; + char *host; + + enum nvmf_rpc_host_op op; + + bool allow_any_host; + + bool response_sent; +}; + +static const struct spdk_json_object_decoder nvmf_rpc_subsystem_host_decoder[] = { + {"nqn", offsetof(struct nvmf_rpc_host_ctx, nqn), spdk_json_decode_string}, + {"host", offsetof(struct nvmf_rpc_host_ctx, host), spdk_json_decode_string}, +}; + +static void +nvmf_rpc_host_ctx_free(struct nvmf_rpc_host_ctx *ctx) +{ + free(ctx->nqn); + free(ctx->host); + free(ctx); +} + +static void +nvmf_rpc_host_resumed(struct spdk_nvmf_subsystem *subsystem, + void *cb_arg, int status) +{ + struct nvmf_rpc_host_ctx *ctx = cb_arg; + struct spdk_jsonrpc_request *request; + struct spdk_json_write_ctx *w; + bool response_sent = ctx->response_sent; + + request = ctx->request; + nvmf_rpc_host_ctx_free(ctx); + + if (response_sent) { + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} + +static void +nvmf_rpc_host_paused(struct spdk_nvmf_subsystem *subsystem, + void *cb_arg, int status) +{ + struct nvmf_rpc_host_ctx *ctx = cb_arg; + int rc = -1; + + switch (ctx->op) { + case NVMF_RPC_HOST_ADD: + rc = spdk_nvmf_subsystem_add_host(subsystem, ctx->host); + break; + case NVMF_RPC_HOST_REMOVE: + rc = spdk_nvmf_subsystem_remove_host(subsystem, ctx->host); + break; + case NVMF_RPC_HOST_ALLOW_ANY: + rc = spdk_nvmf_subsystem_set_allow_any_host(subsystem, ctx->allow_any_host); + break; + } + + if (rc != 0) { + spdk_jsonrpc_send_error_response(ctx->request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Internal error"); + ctx->response_sent = true; + } + + if (spdk_nvmf_subsystem_resume(subsystem, nvmf_rpc_host_resumed, ctx)) { + if (!ctx->response_sent) { + spdk_jsonrpc_send_error_response(ctx->request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Internal error"); + } + nvmf_rpc_host_ctx_free(ctx); + return; + } +} + +static void +nvmf_rpc_subsystem_add_host(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct nvmf_rpc_host_ctx *ctx; + struct spdk_nvmf_subsystem *subsystem; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Out of memory"); + return; + } + + if (spdk_json_decode_object(params, nvmf_rpc_subsystem_host_decoder, + SPDK_COUNTOF(nvmf_rpc_subsystem_host_decoder), + ctx)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + ctx->request = request; + ctx->op = NVMF_RPC_HOST_ADD; + ctx->response_sent = false; + + subsystem = spdk_nvmf_tgt_find_subsystem(g_tgt.tgt, ctx->nqn); + if (!subsystem) { + SPDK_ERRLOG("Unable to find subsystem with NQN %s\n", ctx->nqn); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + if (spdk_nvmf_subsystem_pause(subsystem, nvmf_rpc_host_paused, ctx)) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Internal error"); + nvmf_rpc_host_ctx_free(ctx); + return; + } +} +SPDK_RPC_REGISTER("nvmf_subsystem_add_host", nvmf_rpc_subsystem_add_host) + +static void +nvmf_rpc_subsystem_remove_host(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct nvmf_rpc_host_ctx *ctx; + struct spdk_nvmf_subsystem *subsystem; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Out of memory"); + return; + } + + if (spdk_json_decode_object(params, nvmf_rpc_subsystem_host_decoder, + SPDK_COUNTOF(nvmf_rpc_subsystem_host_decoder), + ctx)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + ctx->request = request; + ctx->op = NVMF_RPC_HOST_REMOVE; + ctx->response_sent = false; + + subsystem = spdk_nvmf_tgt_find_subsystem(g_tgt.tgt, ctx->nqn); + if (!subsystem) { + SPDK_ERRLOG("Unable to find subsystem with NQN %s\n", ctx->nqn); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + if (spdk_nvmf_subsystem_pause(subsystem, nvmf_rpc_host_paused, ctx)) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Internal error"); + nvmf_rpc_host_ctx_free(ctx); + return; + } +} +SPDK_RPC_REGISTER("nvmf_subsystem_remove_host", nvmf_rpc_subsystem_remove_host) + + +static const struct spdk_json_object_decoder nvmf_rpc_subsystem_any_host_decoder[] = { + {"nqn", offsetof(struct nvmf_rpc_host_ctx, nqn), spdk_json_decode_string}, + {"allow_any_host", offsetof(struct nvmf_rpc_host_ctx, allow_any_host), spdk_json_decode_bool}, +}; + +static void +nvmf_rpc_subsystem_allow_any_host(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct nvmf_rpc_host_ctx *ctx; + struct spdk_nvmf_subsystem *subsystem; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Out of memory"); + return; + } + + if (spdk_json_decode_object(params, nvmf_rpc_subsystem_any_host_decoder, + SPDK_COUNTOF(nvmf_rpc_subsystem_any_host_decoder), + ctx)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + ctx->request = request; + ctx->op = NVMF_RPC_HOST_ALLOW_ANY; + ctx->response_sent = false; + + subsystem = spdk_nvmf_tgt_find_subsystem(g_tgt.tgt, ctx->nqn); + if (!subsystem) { + SPDK_ERRLOG("Unable to find subsystem with NQN %s\n", ctx->nqn); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + nvmf_rpc_host_ctx_free(ctx); + return; + } + + if (spdk_nvmf_subsystem_pause(subsystem, nvmf_rpc_host_paused, ctx)) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, "Internal error"); + nvmf_rpc_host_ctx_free(ctx); + return; + } +} +SPDK_RPC_REGISTER("nvmf_subsystem_allow_any_host", nvmf_rpc_subsystem_allow_any_host) diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index d052855a6..5e060abaa 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -422,3 +422,114 @@ Example response: "result": 3 } ~~~ + +## nvmf_subsystem_add_host method {#rpc_nvmf_subsystem_add_host} + +Add a host NQN to the whitelist of allowed hosts. + +### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +nqn | Required | string | Subsystem NQN +host | Required | string | Host NQN to add to the list of allowed host NQNs + +### Example + +Example request: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "nvmf_subsystem_add_host", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "host": "nqn.2016-06.io.spdk:host1" + } +} +~~~ + +Example response: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + +## nvmf_subsystem_remove_host method {#rpc_nvmf_subsystem_remove_host} + +Remove a host NQN from the whitelist of allowed hosts. + +### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +nqn | Required | string | Subsystem NQN +host | Required | string | Host NQN to remove from the list of allowed host NQNs + +### Example + +Example request: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "nvmf_subsystem_remove_host", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "host": "nqn.2016-06.io.spdk:host1" + } +} +~~~ + +Example response: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + +## nvmf_subsystem_allow_any_host method {#rpc_nvmf_subsystem_allow_any_host} + +Configure a subsystem to allow any host to connect or to enforce the host NQN whitelist. + +### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +nqn | Required | string | Subsystem NQN +allow_any_host | Required | boolean | Allow any host (`true`) or enforce allowed host whitelist (`false`). + +### Example + +Example request: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "nvmf_subsystem_allow_any_host", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "allow_any_host": true + } +} +~~~ + +Example response: + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ diff --git a/include/spdk/nvmf.h b/include/spdk/nvmf.h index 48321a1c3..d33373c9c 100644 --- a/include/spdk/nvmf.h +++ b/include/spdk/nvmf.h @@ -248,6 +248,17 @@ struct spdk_nvmf_subsystem *spdk_nvmf_subsystem_get_next(struct spdk_nvmf_subsys int spdk_nvmf_subsystem_add_host(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn); +/** + * Remove the given host NQN from the allowed hosts whitelist. + * + * May only be performed on subsystems in the PAUSED or INACTIVE states. + * + * \param subsystem Subsystem to remove host from + * \param host_nqn The NQN for the host + * \return 0 on success. Negated errno value on failure. + */ +int spdk_nvmf_subsystem_remove_host(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn); + /** * Set whether a subsystem should allow any host or only hosts in the allowed list. * diff --git a/lib/nvmf/subsystem.c b/lib/nvmf/subsystem.c index ab4af74d8..b77ae22ec 100644 --- a/lib/nvmf/subsystem.c +++ b/lib/nvmf/subsystem.c @@ -282,6 +282,14 @@ spdk_nvmf_subsystem_create(struct spdk_nvmf_tgt *tgt, return subsystem; } +static void +_spdk_nvmf_subsystem_remove_host(struct spdk_nvmf_subsystem *subsystem, struct spdk_nvmf_host *host) +{ + TAILQ_REMOVE(&subsystem->hosts, host, link); + free(host->nqn); + free(host); +} + void spdk_nvmf_subsystem_destroy(struct spdk_nvmf_subsystem *subsystem) { @@ -304,9 +312,7 @@ spdk_nvmf_subsystem_destroy(struct spdk_nvmf_subsystem *subsystem) } TAILQ_FOREACH_SAFE(host, &subsystem->hosts, link, host_tmp) { - TAILQ_REMOVE(&subsystem->hosts, host, link); - free(host->nqn); - free(host); + _spdk_nvmf_subsystem_remove_host(subsystem, host); } TAILQ_FOREACH_SAFE(ctrlr, &subsystem->ctrlrs, link, ctrlr_tmp) { @@ -558,6 +564,20 @@ spdk_nvmf_subsystem_get_next(struct spdk_nvmf_subsystem *subsystem) return NULL; } +static struct spdk_nvmf_host * +_spdk_nvmf_subsystem_find_host(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn) +{ + struct spdk_nvmf_host *host = NULL; + + TAILQ_FOREACH(host, &subsystem->hosts, link) { + if (strcmp(hostnqn, host->nqn) == 0) { + return host; + } + } + + return NULL; +} + int spdk_nvmf_subsystem_add_host(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn) { @@ -572,6 +592,11 @@ spdk_nvmf_subsystem_add_host(struct spdk_nvmf_subsystem *subsystem, const char * return -EAGAIN; } + if (_spdk_nvmf_subsystem_find_host(subsystem, hostnqn)) { + /* This subsystem already allows the specified host. */ + return 0; + } + host = calloc(1, sizeof(*host)); if (!host) { return -ENOMEM; @@ -588,6 +613,25 @@ spdk_nvmf_subsystem_add_host(struct spdk_nvmf_subsystem *subsystem, const char * return 0; } +int +spdk_nvmf_subsystem_remove_host(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn) +{ + struct spdk_nvmf_host *host; + + if (!(subsystem->state == SPDK_NVMF_SUBSYSTEM_INACTIVE || + subsystem->state == SPDK_NVMF_SUBSYSTEM_PAUSED)) { + return -EAGAIN; + } + + host = _spdk_nvmf_subsystem_find_host(subsystem, hostnqn); + if (host == NULL) { + return -ENOENT; + } + + _spdk_nvmf_subsystem_remove_host(subsystem, host); + return 0; +} + int spdk_nvmf_subsystem_set_allow_any_host(struct spdk_nvmf_subsystem *subsystem, bool allow_any_host) { @@ -610,8 +654,6 @@ spdk_nvmf_subsystem_get_allow_any_host(const struct spdk_nvmf_subsystem *subsyst bool spdk_nvmf_subsystem_host_allowed(struct spdk_nvmf_subsystem *subsystem, const char *hostnqn) { - struct spdk_nvmf_host *host; - if (!hostnqn) { return false; } @@ -620,13 +662,7 @@ spdk_nvmf_subsystem_host_allowed(struct spdk_nvmf_subsystem *subsystem, const ch return true; } - TAILQ_FOREACH(host, &subsystem->hosts, link) { - if (strcmp(hostnqn, host->nqn) == 0) { - return true; - } - } - - return false; + return _spdk_nvmf_subsystem_find_host(subsystem, hostnqn) != NULL; } struct spdk_nvmf_host * diff --git a/scripts/rpc.py b/scripts/rpc.py index 769859f6d..9c5799408 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -378,6 +378,22 @@ if __name__ == "__main__": p.add_argument('-e', '--eui64', help='Namespace EUI-64 identifier (optional)') p.set_defaults(func=rpc.nvmf.nvmf_subsystem_add_ns) + p = subparsers.add_parser('nvmf_subsystem_add_host', help='Add a host to an NVMe-oF subsystem') + p.add_argument('nqn', help='NVMe-oF subsystem NQN') + p.add_argument('host', help='Host NQN to allow') + p.set_defaults(func=rpc.nvmf.nvmf_subsystem_add_host) + + p = subparsers.add_parser('nvmf_subsystem_remove_host', help='Remove a host from an NVMe-oF subsystem') + p.add_argument('nqn', help='NVMe-oF subsystem NQN') + p.add_argument('host', help='Host NQN to remove') + p.set_defaults(func=rpc.nvmf.nvmf_subsystem_remove_host) + + p = subparsers.add_parser('nvmf_subsystem_allow_any_host', help='Allow any host to connect to the subsystem') + p.add_argument('nqn', help='NVMe-oF subsystem NQN') + p.add_argument('-e', '--enable', action='store_true', help='Enable allowing any host') + p.add_argument('-d', '--disable', action='store_true', help='Disable allowing any host') + p.set_defaults(func=rpc.nvmf.nvmf_subsystem_allow_any_host) + # pmem p = subparsers.add_parser('create_pmem_pool', help='Create pmem pool') p.add_argument('pmem_file', help='Path to pmemblk pool file') diff --git a/scripts/rpc/nvmf.py b/scripts/rpc/nvmf.py index b0f4e83a4..e105f652f 100755 --- a/scripts/rpc/nvmf.py +++ b/scripts/rpc/nvmf.py @@ -76,6 +76,27 @@ def nvmf_subsystem_add_ns(args): args.client.call('nvmf_subsystem_add_ns', params) +def nvmf_subsystem_add_host(args): + params = {'nqn': args.nqn, + 'host': args.host} + + args.client.call('nvmf_subsystem_add_host', params) + + +def nvmf_subsystem_remove_host(args): + params = {'nqn': args.nqn, + 'host': args.host} + + args.client.call('nvmf_subsystem_remove_host', params) + + +def nvmf_subsystem_allow_any_host(args): + params = {'nqn': args.nqn} + params['allow_any_host'] = False if args.disable else True + + args.client.call('nvmf_subsystem_allow_any_host', params) + + def delete_nvmf_subsystem(args): params = {'nqn': args.subsystem_nqn} args.client.call('delete_nvmf_subsystem', params) diff --git a/test/nvmf/rpc/rpc.sh b/test/nvmf/rpc/rpc.sh index 81fb870c2..c75923e4f 100755 --- a/test/nvmf/rpc/rpc.sh +++ b/test/nvmf/rpc/rpc.sh @@ -39,14 +39,47 @@ MALLOC_BLOCK_SIZE=512 bdevs="$bdevs $($rpc_py construct_malloc_bdev $MALLOC_BDEV_SIZE $MALLOC_BLOCK_SIZE)" +# Disallow host NQN and make sure connect fails +$rpc_py construct_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 '' '' -s SPDK00000000000001 +for bdev in $bdevs; do + $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 $bdev +done +$rpc_py nvmf_subsystem_allow_any_host -d nqn.2016-06.io.spdk:cnode1 +$rpc_py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t RDMA -a $NVMF_FIRST_TARGET_IP -s $NVMF_PORT + +modprobe -v nvme-rdma +trap "killprocess $pid; nvmfcleanup; exit 1" SIGINT SIGTERM EXIT + +# This connect should fail - the host NQN is not allowed +! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +# Add the host NQN and verify that the connect succeeds +$rpc_py nvmf_subsystem_add_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 +nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" +nvme disconnect -n nqn.2016-06.io.spdk:cnode1 + +# Remove the host and verify that the connect fails +$rpc_py nvmf_subsystem_remove_host nqn.2016-06.io.spdk:cnode1 nqn.2016-06.io.spdk:host1 +! nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" + +# Allow any host and verify that the connect succeeds +$rpc_py nvmf_subsystem_allow_any_host -e nqn.2016-06.io.spdk:cnode1 +nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -q nqn.2016-06.io.spdk:host1 -a "$NVMF_FIRST_TARGET_IP" -s "$NVMF_PORT" +nvme disconnect -n nqn.2016-06.io.spdk:cnode1 + +$rpc_py delete_nvmf_subsystem nqn.2016-06.io.spdk:cnode1 +nvmfcleanup +trap "killprocess $pid; exit 1" SIGINT SIGTERM EXIT + # do frequent add delete. for i in `seq 1 $times` do j=0 for bdev in $bdevs; do let j=j+1 - $rpc_py construct_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j '' '' -a -s SPDK00000000000001 + $rpc_py construct_nvmf_subsystem nqn.2016-06.io.spdk:cnode$j '' '' -s SPDK00000000000001 $rpc_py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode$j $bdev + $rpc_py nvmf_subsystem_allow_any_host nqn.2016-06.io.spdk:cnode$j done n=$j